From 432c369a66e985732584b72ae0f881ca4ce60666 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 15:21:17 -0400 Subject: [PATCH 001/102] xo-jit: initial commit (codegen constants + primitives, sort of) --- .gitignore | 8 +++ CMakeLists.txt | 36 ++++++++++++ LICENSE | 29 +++++++++ cmake/xo-bootstrap-macros.cmake | 35 +++++++++++ cmake/xo_jitConfig.cmake.in | 6 ++ example/CMakeLists.txt | 1 + example/ex1/CMakeLists.txt | 12 ++++ example/ex1/ex1.cpp | 56 ++++++++++++++++++ include/xo/jit/Jit.hpp | 76 ++++++++++++++++++++++++ src/jit/CMakeLists.txt | 35 +++++++++++ src/jit/Jit.cpp | 100 ++++++++++++++++++++++++++++++++ 11 files changed, 394 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100755 cmake/xo-bootstrap-macros.cmake create mode 100644 cmake/xo_jitConfig.cmake.in create mode 100644 example/CMakeLists.txt create mode 100644 example/ex1/CMakeLists.txt create mode 100644 example/ex1/ex1.cpp create mode 100644 include/xo/jit/Jit.hpp create mode 100644 src/jit/CMakeLists.txt create mode 100644 src/jit/Jit.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f3b23fc3 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..094529c0 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +# 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) + +# ---------------------------------------------------------------- +# docs targets depend on all the other library/utest targets +# +#add_subdirectory(docs) + +# end CMakeLists.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 00000000..aba31169 --- /dev/null +++ b/cmake/xo-bootstrap-macros.cmake @@ -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() diff --git a/cmake/xo_jitConfig.cmake.in b/cmake/xo_jitConfig.cmake.in new file mode 100644 index 00000000..26d2315a --- /dev/null +++ b/cmake/xo_jitConfig.cmake.in @@ -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@") diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/example/ex1/CMakeLists.txt b/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..9406c959 --- /dev/null +++ b/example/ex1/CMakeLists.txt @@ -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 diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp new file mode 100644 index 00000000..571b288f --- /dev/null +++ b/example/ex1/ex1.cpp @@ -0,0 +1,56 @@ +/** @file ex1.cpp **/ + +#include "xo/jit/Jit.hpp" +#include "xo/expression/Constant.hpp" +#include "xo/expression/Primitive.hpp" +#include + +int +main() { + using xo::jit::Jit; + using xo::ast::make_constant; + using xo::ast::make_primitive; + using xo::xtag; + using std::cerr; + using std::endl; + + //using xo::ast::make_constant; + + auto jit = Jit::make(); + + { + auto expr = make_constant(7.0); + + auto llvm_ircode = jit->codegen(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); + + auto llvm_ircode = jit->codegen(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; + } + } +} + +/** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp new file mode 100644 index 00000000..f7adf0cc --- /dev/null +++ b/include/xo/jit/Jit.hpp @@ -0,0 +1,76 @@ +/** @file Jit.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +//#include + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/expression/Expression.hpp" +#include "xo/expression/ConstantInterface.hpp" +#include "xo/expression/PrimitiveInterface.hpp" +#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" + +namespace xo { + namespace jit { + /** @class Jit + * @brief just-in-time compiler for EGAD + * + * TODO: make module name a parameter? + **/ + class Jit : public ref::Refcount { + public: + using Expression = xo::ast::Expression; + //using ConstantInterface = xo::ast::ConstantInterface; + + public: + static ref::rp make() { return new Jit(); } + + llvm::Value * codegen_constant(ref::brw expr); + llvm::Function * codegen_primitive(ref::brw expr); + + llvm::Value * codegen(ref::brw expr); + + private: + Jit(); + + private: + /** 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 + **/ + std::unique_ptr llvm_cx_; + /** a module (aka library) being prepared by llvm. + * - function names are unique within a module. + **/ + std::unique_ptr llvm_module_; + }; + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end Jit.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt new file mode 100644 index 00000000..abd3c903 --- /dev/null +++ b/src/jit/CMakeLists.txt @@ -0,0 +1,35 @@ +# jit/CMakeLists.txt + +set(SELF_LIB xo_jit) +set(SELF_SRCS + Jit.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}]") + +# 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_libraries(${SELF_LIB} PUBLIC ${LLVM_LIBS}) + +# end CMakeLists.txt diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp new file mode 100644 index 00000000..b5d3241e --- /dev/null +++ b/src/jit/Jit.cpp @@ -0,0 +1,100 @@ +/* @file Jit.cpp */ + +#include "Jit.hpp" + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + using xo::ast::PrimitiveInterface; + using xo::reflect::TypeDescr; + + namespace jit { + Jit::Jit() + : llvm_cx_{std::make_unique()}, + llvm_module_{std::make_unique("xojit", *llvm_cx_)} + {} + + llvm::Value * + Jit::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (td->is_native()) { + return llvm::ConstantFP::get(*llvm_cx_, + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (td->is_native()) { + return llvm::ConstantFP::get(*llvm_cx_, + llvm::APFloat(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } + + llvm::Function * + Jit::codegen_primitive(ref::brw expr) + { + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygen/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + // PLACEHOLDER + // just make prototype for function :: double -> double + + std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + double_v, + false /*!varargs*/); + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + + // set names for arguments (for diagonostics?). Money-see-kaleidoscope-monkey-do here +#ifdef NOT_USING + for (auto & arg : fn->args()) + arg.setName(formalnameofthisarg); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + Jit::codegen(ref::brw expr) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + break; + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + return nullptr; + } /*codegen*/ + + } /*namespace jit*/ +} /*namespace xo*/ + +/* end Jit.cpp */ From 69dfaa931ab30cad66095107e6c1e70d6155f107 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 16:21:19 -0400 Subject: [PATCH 002/102] xo-jit: + compile Apply expressions [wip] --- example/ex1/ex1.cpp | 24 ++++++++++++++++ include/xo/jit/Jit.hpp | 4 +++ src/jit/Jit.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 571b288f..bad239be 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -3,6 +3,7 @@ #include "xo/jit/Jit.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/expression/Apply.hpp" #include int @@ -10,6 +11,7 @@ main() { using xo::jit::Jit; using xo::ast::make_constant; using xo::ast::make_primitive; + using xo::ast::make_apply; using xo::xtag; using std::cerr; using std::endl; @@ -51,6 +53,28 @@ main() { << endl; } } + + { + /* (sqrt 2) */ + + auto fn = make_primitive("sqrt", ::sqrt); + auto arg = make_constant(2.0); + + auto call = make_apply(fn, arg); + + auto llvm_ircode = jit->codegen(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; + } + } } /** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f7adf0cc..7e32fb26 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -11,6 +11,7 @@ #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" +#include "xo/expression/Apply.hpp" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -50,6 +51,7 @@ namespace xo { llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); + llvm::Value * codegen_apply(ref::brw expr); llvm::Value * codegen(ref::brw expr); @@ -64,6 +66,8 @@ namespace xo { * each with its own LLVMContext **/ std::unique_ptr llvm_cx_; + /** builder for intermediate-representation objects **/ + std::unique_ptr> llvm_ir_builder_; /** a module (aka library) being prepared by llvm. * - function names are unique within a module. **/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index b5d3241e..6bf39631 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -7,11 +7,15 @@ namespace xo { using xo::ast::Expression; using xo::ast::ConstantInterface; using xo::ast::PrimitiveInterface; + using xo::ast::Apply; using xo::reflect::TypeDescr; + using std::cerr; + using std::endl; namespace jit { Jit::Jit() : llvm_cx_{std::make_unique()}, + llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, llvm_module_{std::make_unique("xojit", *llvm_cx_)} {} @@ -75,6 +79,60 @@ namespace xo { return fn; } /*codegen_primitive*/ + llvm::Value * + Jit::codegen_apply(ref::brw apply) + { + using std::cerr; + using std::endl; + + /* editorial: + * + * to handle (computed functions) properly, + * we will need a runtime representation for a 'primitive function pointer' + * + * For now, finesse by only handling PrimitiveInterface in function-callee position + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + auto * fn = this->codegen_primitive(pm); + +#ifdef NOT_USING_DEBUG + cerr << "Jit::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + if (fn->arg_size() != apply->argv().size()) { + cerr << "Jit::codegen_apply: error: callee f expecting n1 args where n2 supplied" + << xtag("f", pm->name()) + << xtag("n1", pm->n_arg()) + << xtag("n2", apply->argv().size()) + << endl; + return nullptr; + } + + std::vector args; + args.reserve(apply->argv().size()); + + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr); + +#ifdef NOT_USING_DEBUG + cerr << "Jit::codegen_apply: arg:" << endl; + arg->print(llvm::errs()); + cerr << endl; +#endif + + args.push_back(arg); + } + + return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); + } else { + cerr << "Jit::codegen_apply: error: only allowing call to known primitives at present" << endl; + return nullptr; + } + } /*codegen_apply*/ + llvm::Value * Jit::codegen(ref::brw expr) { @@ -84,6 +142,7 @@ namespace xo { case exprtype::primitive: return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: + return this->codegen_apply(Apply::from(expr)); break; case exprtype::invalid: case exprtype::n_expr: @@ -91,6 +150,10 @@ namespace xo { break; } + cerr << "Jit::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + return nullptr; } /*codegen*/ From 731b91889c3acba36eedac2ebeaa7bdcead3d232 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 13 Jun 2024 18:00:16 -0400 Subject: [PATCH 003/102] xo-jit: handle variable refs + lambda defs --- example/ex1/ex1.cpp | 33 ++++++++++++++++ include/xo/jit/Jit.hpp | 11 ++++++ src/jit/Jit.cpp | 89 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index bad239be..d86b77f4 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -4,6 +4,8 @@ #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 int @@ -12,6 +14,8 @@ main() { using xo::ast::make_constant; using xo::ast::make_primitive; using xo::ast::make_apply; + using xo::ast::make_var; + using xo::ast::make_lambda; using xo::xtag; using std::cerr; using std::endl; @@ -75,6 +79,35 @@ main() { << endl; } } + + { + /* (lambda (x) (sin (cos x))) */ + + auto sin = make_primitive("sin", ::sin); + auto cos = make_primitive("cos", ::cos); + + auto x_var = make_var("x"); + 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"}, + call2); + + auto llvm_ircode = jit->codegen(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; + } + } } /** end ex1.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 7e32fb26..4c1f1534 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -12,6 +12,8 @@ #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 "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -52,6 +54,8 @@ namespace xo { llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); llvm::Value * codegen_apply(ref::brw expr); + llvm::Function * codegen_lambda(ref::brw expr); + llvm::Value * codegen_variable(ref::brw var); llvm::Value * codegen(ref::brw expr); @@ -72,6 +76,13 @@ namespace xo { * - function names are unique within a module. **/ std::unique_ptr llvm_module_; + + /** map global names to functions/variables **/ + std::map> global_env_; + /** map variable names (formal parameters) to + * corresponding llvm interactor + **/ + std::map nested_env_; }; } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 6bf39631..d9fc0f61 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -7,6 +7,8 @@ namespace xo { using xo::ast::Expression; using xo::ast::ConstantInterface; using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; using xo::ast::Apply; using xo::reflect::TypeDescr; using std::cerr; @@ -133,6 +135,88 @@ namespace xo { } } /*codegen_apply*/ + llvm::Function * + Jit::codegen_lambda(ref::brw lambda) + { + /* reminder! this is the *expression*, not the *closure* */ + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + /** function with this name already defined?? **/ + return nullptr; + } + + /* establish prototype for this function */ + + // PLACEHOLDER + // just make prototype for function :: double -> double + + std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + double_v, + false /*!varargs*/); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + int i = 0; + for (auto & arg : fn->args()) + arg.setName(lambda->argv().at(i)); + + /* generate function body */ + + auto block = llvm::BasicBlock::Create(*llvm_cx_, "entry", fn); + + llvm_ir_builder_->SetInsertPoint(block); + + /* formal parameters need to appear in named_value_map_ */ + nested_env_.clear(); + for (auto & arg : fn->args()) + nested_env_[std::string(arg.getName())] = &arg; + + llvm::Value * retval = this->codegen(lambda->body()); + + if (retval) { + /* completes the function.. */ + llvm_ir_builder_->CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*fn); + + /* optimize! */ + // thefpm->run(*fn, *thefam); + + return fn; + } + + /* oops, something went wrong */ + fn->eraseFromParent(); + + return nullptr; + } /*codegen_lambda*/ + + llvm::Value * + Jit::codegen_variable(ref::brw var) + { + auto ix = nested_env_.find(var->name()); + + if (ix == nested_env_.end()) { + cerr << "Jit::codegen_variable: no binding for variable x" + << xtag("x", var->name()) + << endl; + } + + return ix->second; + } /*codegen_variable*/ + llvm::Value * Jit::codegen(ref::brw expr) { @@ -143,7 +227,10 @@ namespace xo { return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: return this->codegen_apply(Apply::from(expr)); - break; + case exprtype::lambda: + return this->codegen_lambda(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr)); case exprtype::invalid: case exprtype::n_expr: return nullptr; From 451490145924d229233b2fe2c16edc00de773f55 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 15:07:08 -0400 Subject: [PATCH 004/102] xo-jit: + display + display_string --- include/xo/jit/Jit.hpp | 11 ++++++++++- src/jit/Jit.cpp | 9 +++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 4c1f1534..044343b8 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -59,6 +59,9 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + virtual void display(std::ostream & os) const; + virtual std::string display_string() const; + private: Jit(); @@ -83,7 +86,13 @@ namespace xo { * corresponding llvm interactor **/ std::map nested_env_; - }; + }; /*Jit*/ + + inline std::ostream & + operator<<(std::ostream & os, const Jit & x) { + x.display(os); + return os; + } } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index d9fc0f61..32bf55d4 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -244,6 +244,15 @@ namespace xo { return nullptr; } /*codegen*/ + void + Jit::display(std::ostream & os) const { + os << ""; + } + + std::string + Jit::display_string() const { + return tostr(*this); + } } /*namespace jit*/ } /*namespace xo*/ From 49eaf6bc452e3a01c67aee461edcd637867fceb6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:58:35 -0400 Subject: [PATCH 005/102] xo-jit: + README --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..e2c4cbb3 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# xo-jit library + +A library for representing abstract syntax trees for EGAD (a small jit-based language). + +## 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 +``` From 0c7b1e03d7d326bb6369c2dd30cc0356f642e462 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 14 Jun 2024 16:58:50 -0400 Subject: [PATCH 006/102] xo-jit: handle multiple-argument primitives --- src/jit/Jit.cpp | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 32bf55d4..872b949e 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -61,10 +61,46 @@ namespace xo { // PLACEHOLDER // just make prototype for function :: double -> double - std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + TypeDescr fn_td = expr->value_td(); + int n_fn_arg = fn_td->n_fn_arg(); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), - double_v, + std::vector llvm_argtype_v(n_fn_arg); + + /** check function args are all doubles **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + if (arg_td->is_native()) { + llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); + + // TODO: extend with other native types here... + } else { + cerr << "Jit::codegen_primitive: error: primitive f with arg i of type T where double expected" + << xtag("f", expr->name()) + << xtag("i", i) + << xtag("T", arg_td->short_name()) + << endl; + return nullptr; + } + } + + //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = nullptr; + + if (retval_td->is_native()) { + llvm_retval = llvm::Type::getDoubleTy(*llvm_cx_); + } else { + cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" + << xtag("f", expr->name()) + << xtag("T", retval_td->short_name()) + << endl; + return nullptr; + } + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, false /*!varargs*/); fn = llvm::Function::Create(llvm_fn_type, From fa0104422fc7330f3a71aaa691be5bb5716eaf5b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:17:14 -0400 Subject: [PATCH 007/102] xo-jit: incorporate kaleidoscope jit for codegen --- example/ex_kaleidoscope4/CMakeLists.txt | 12 ++ example/ex_kaleidoscope4/ex_kaleidoscope4.cpp | 16 +++ include/xo/jit/Jit.hpp | 50 ++++++- include/xo/jit/KaleidoscopeJit.hpp | 126 ++++++++++++++++++ src/jit/Jit.cpp | 126 ++++++++++++++++-- 5 files changed, 319 insertions(+), 11 deletions(-) create mode 100644 example/ex_kaleidoscope4/CMakeLists.txt create mode 100644 example/ex_kaleidoscope4/ex_kaleidoscope4.cpp create mode 100644 include/xo/jit/KaleidoscopeJit.hpp diff --git a/example/ex_kaleidoscope4/CMakeLists.txt b/example/ex_kaleidoscope4/CMakeLists.txt new file mode 100644 index 00000000..4e9476c3 --- /dev/null +++ b/example/ex_kaleidoscope4/CMakeLists.txt @@ -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 diff --git a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp new file mode 100644 index 00000000..63926aad --- /dev/null +++ b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp @@ -0,0 +1,16 @@ +/** ex_kaleidoscop4.cpp **/ + +#include "xo/jit/KaleidoscopeJit.hpp" +#include + +int +main() { + using std::cerr; + using std::endl; + + auto jit = xo::jit::KaleidoscopeJIT::Create(); + + cerr << "created kaleidoscope jit successfully" << endl; +} + +/** end ex_kaleidoscope4.cpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 044343b8..d66f86fe 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -14,6 +14,27 @@ #include "xo/expression/Apply.hpp" #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" + +#include "KaleidoscopeJit.hpp" +#ifdef NOT_USING +/* stuff from KaleidoscopeJIT.hpp */ +#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" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include +#endif + +/* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" #include "llvm/ADT/STLExtras.h" #include "llvm/IR/BasicBlock.h" @@ -36,6 +57,7 @@ #include "llvm/Transforms/Scalar/Reassociate.h" #include "llvm/Transforms/Scalar/SimplifyCFG.h" + namespace xo { namespace jit { /** @class Jit @@ -49,7 +71,8 @@ namespace xo { //using ConstantInterface = xo::ast::ConstantInterface; public: - static ref::rp make() { return new Jit(); } + /* tracking KaleidoscopeJIT::Create() here.. */ + static llvm::Expected> make_aux(); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); @@ -63,9 +86,32 @@ namespace xo { virtual std::string display_string() const; private: - Jit(); + Jit( + std::unique_ptr kal_jit +#ifdef NOT_USING + std::unique_ptr es, + llvm::orc::JITTargetMachineBuilder jtmb, + llvm::DataLayout dl +#endif + ); private: + // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- + + std::unique_ptr kal_jit_; + +#ifdef NOT_USING + std::unique_ptr jit_es_; + llvm::DataLayout jit_data_layout_; + llvm::orc::MangleAndInterner jit_mangle_; + llvm::orc::RTDyldObjectLinkingLayer jit_object_layer_; + llvm::orc::IRCompileLayer jit_compile_layer_; + /** reference here. looks like storage owned by .jit_es **/ + llvm::orc::JITDylib & jit_our_dynamic_lib_; +#endif + + // ----- this part adapted from kaleidoscope.cpp ----- + /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp new file mode 100644 index 00000000..3094c076 --- /dev/null +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -0,0 +1,126 @@ +//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Contains a simple JIT definition for use in the kaleidoscope tutorials. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H +#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H + +#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" +#include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/LLVMContext.h" +#include + +namespace xo { + namespace jit { + + class KaleidoscopeJIT { + 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: + std::unique_ptr ES; + + DataLayout DL; + MangleAndInterner Mangle; + + RTDyldObjectLinkingLayer ObjectLayer; + IRCompileLayer CompileLayer; + + JITDylib &MainJD; + + public: + KaleidoscopeJIT(std::unique_ptr ES, + JITTargetMachineBuilder JTMB, + DataLayout DL) + : ES(std::move(ES)), + DL(std::move(DL)), + Mangle(*this->ES, this->DL), + ObjectLayer(*this->ES, + []() { return std::make_unique(); }), + CompileLayer(*this->ES, ObjectLayer, + std::make_unique(std::move(JTMB))), + MainJD(this->ES->createBareJITDylib("
")) + { + MainJD.addGenerator( + cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( + DL.getGlobalPrefix()))); + if (JTMB.getTargetTriple().isOSBinFormatCOFF()) { + ObjectLayer.setOverrideObjectFlagsWithResponsibilityFlags(true); + ObjectLayer.setAutoClaimResponsibilityForObjectSymbols(true); + } + } + + ~KaleidoscopeJIT() { + if (auto Err = ES->endSession()) + ES->reportError(std::move(Err)); + } + + static llvm::Expected> Create() { + auto EPC = SelfExecutorProcessControl::Create(); + if (!EPC) + return EPC.takeError(); + + auto ES = std::make_unique(std::move(*EPC)); + + JITTargetMachineBuilder JTMB( + ES->getExecutorProcessControl().getTargetTriple()); + + auto DL = JTMB.getDefaultDataLayoutForTarget(); + if (!DL) + return DL.takeError(); + + return std::make_unique(std::move(ES), std::move(JTMB), + std::move(*DL)); + } + + const DataLayout &getDataLayout() const { return DL; } + + JITDylib &getMainJITDylib() { return MainJD; } + + llvm::Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) { + if (!RT) + RT = MainJD.getDefaultResourceTracker(); + return CompileLayer.add(RT, std::move(TSM)); + } + + llvm::Expected lookup(StringRef Name) { + return ES->lookup({&MainJD}, Mangle(Name.str())); + } + }; + + } // end namespace jit +} // end namespace xo + +#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 872b949e..d5aa2901 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -15,11 +15,96 @@ namespace xo { using std::endl; namespace jit { - Jit::Jit() - : llvm_cx_{std::make_unique()}, + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_copmile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + Jit::make_aux() + { +#ifdef NOT_USING + /* 'executor process control' */ + auto epc = llvm::orc::SelfExecutorProcessControl::Create(); + if (!epc) { + return epc.takeError(); + //throw std::runtime_error("Jit::make: failed to establish executor process control"); + } + + /* 'execution session' */ + auto es = std::make_unique(std::move(*epc)); + + /* 'jit target machine builder' */ + llvm::orc::JITTargetMachineBuilder jtmb(es + ->getExecutorProcessControl() + .getTargetTriple()); + + auto dl = jtmb.getDefaultDataLayoutForTarget(); + if (!dl) { + return dl.takeError(); + //throw std::runtime_error("Jit::make: failed to establish data layout object" + // " for target machine"); + } +#endif + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); + + return std::unique_ptr(new Jit(std::move(kal_jit) +#ifdef NOT_USING + std::move(es), + std::move(jtmb), + std::move(*dl) +#endif + )); + } /*make*/ + + Jit::Jit( + std::unique_ptr kal_jit +#ifdef NOT_USING + std::unique_ptr jit_es, + llvm::orc::JITTargetMachineBuilder jtmb, + llvm::DataLayout dl +#endif + ) + : kal_jit_{std::move(kal_jit)}, +#ifdef NOT_USING + jit_es_(std::move(jit_es)), + jit_data_layout_(std::move(dl)), + jit_mangle_(*this->jit_es_, this->jit_data_layout_), + jit_object_layer_(*this->jit_es_, + []() { return std::make_unique(); }), + jit_compile_layer_(*this->jit_es_, + jit_object_layer_, + std::make_unique(std::move(jtmb))), + /* note: string passed to createBareJITDyLib must be unique + * (within running process address space?) + */ + jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ +#endif + + llvm_cx_{std::make_unique()}, llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, llvm_module_{std::make_unique("xojit", *llvm_cx_)} - {} + { +#ifdef NOT_USING + jit_our_dynamic_lib_.addGenerator + (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess + (jit_data_layout_.getGlobalPrefix()))); + + if(jtmb.getTargetTriple().isOSBinFormatCOFF()) { + jit_object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); + jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); + } +#endif + } llvm::Value * Jit::codegen_constant(ref::brw expr) @@ -40,9 +125,12 @@ namespace xo { llvm::Function * Jit::codegen_primitive(ref::brw expr) { + constexpr bool c_debug_flag = true; + using xo::scope; + /** note: documentation (such as it is) for llvm::Function here: * - * https://llvm.org/doxygen/classllvm_1_1Function.html + * https://llvm.org/doxygenL/classllvm_1_1Function.html **/ auto * fn = llvm_module_->getFunction(expr->name()); @@ -64,12 +152,19 @@ namespace xo { TypeDescr fn_td = expr->value_td(); int n_fn_arg = fn_td->n_fn_arg(); - std::vector llvm_argtype_v(n_fn_arg); + scope log(XO_DEBUG(c_debug_flag), + xtag("fn_td", fn_td->short_name()), + xtag("n_fn_arg", n_fn_arg)); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); /** check function args are all doubles **/ for (int i = 0; i < n_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); + log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); + if (arg_td->is_native()) { llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); @@ -87,6 +182,9 @@ namespace xo { //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); TypeDescr retval_td = fn_td->fn_retval(); + + log && log(xtag("retval_td", retval_td->short_name())); + llvm::Type * llvm_retval = nullptr; if (retval_td->is_native()) { @@ -108,12 +206,22 @@ namespace xo { expr->name(), llvm_module_.get()); - // set names for arguments (for diagonostics?). Money-see-kaleidoscope-monkey-do here #ifdef NOT_USING - for (auto & arg : fn->args()) - arg.setName(formalnameofthisarg); + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } #endif + log && log("returning llvm function"); + return fn; } /*codegen_primitive*/ @@ -189,7 +297,7 @@ namespace xo { /* establish prototype for this function */ // PLACEHOLDER - // just make prototype for function :: double -> double + // just handle double arguments + return type for now std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); From 425323f917173bca5a75810d41638811fd5fe0c9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 13:17:31 -0400 Subject: [PATCH 008/102] xo-jit: ++ extend example --- example/CMakeLists.txt | 1 + example/ex1/ex1.cpp | 49 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 4151ec21..0cb5ee51 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(ex1) +add_subdirectory(ex_kaleidoscope4) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index d86b77f4..01b914d8 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -8,8 +8,33 @@ #include "xo/expression/Variable.hpp" #include +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#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::Jit; using xo::ast::make_constant; using xo::ast::make_primitive; @@ -22,11 +47,23 @@ main() { //using xo::ast::make_constant; - auto jit = Jit::make(); + static llvm::ExitOnError llvm_exit_on_err; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + + auto jit = llvm_exit_on_err(Jit::make_aux()); + + //static_assert(std::is_function_v); + + scope log(XO_DEBUG(true)); { auto expr = make_constant(7.0); + log && log(xtag("expr", expr)); + auto llvm_ircode = jit->codegen(expr); if (llvm_ircode) { @@ -42,7 +79,9 @@ main() { } { - auto expr = make_primitive("sqrt", ::sqrt); + auto expr = make_primitive("sqrt", &sqrt); + + log && log(xtag("expr", expr)); auto llvm_ircode = jit->codegen(expr); @@ -61,11 +100,13 @@ main() { { /* (sqrt 2) */ - auto fn = make_primitive("sqrt", ::sqrt); + auto fn = make_primitive("sqrt", &sqrt); auto arg = make_constant(2.0); auto call = make_apply(fn, arg); + log && log(xtag("expr", call)); + auto llvm_ircode = jit->codegen(call); if (llvm_ircode) { @@ -95,6 +136,8 @@ main() { {"x"}, call2); + log && log(xtag("expr", lambda)); + auto llvm_ircode = jit->codegen(lambda); if (llvm_ircode) { From 066c1356292d46d5bf5b8e8b6c37304bf98ec9ba Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:11:10 -0400 Subject: [PATCH 009/102] xo-jit: bugfix: need release on unique_ptr -> xfer to refcounted ptr --- example/ex1/ex1.cpp | 3 ++- include/xo/jit/Jit.hpp | 2 ++ src/jit/Jit.cpp | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 01b914d8..04dc2f12 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -53,7 +53,8 @@ main() { llvm::InitializeNativeTargetAsmPrinter(); llvm::InitializeNativeTargetAsmParser(); - auto jit = llvm_exit_on_err(Jit::make_aux()); + //auto jit = llvm_exit_on_err(Jit::make_aux()); + auto jit = Jit::make(); //static_assert(std::is_function_v); diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index d66f86fe..26c81e05 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -73,6 +73,8 @@ namespace xo { public: /* tracking KaleidoscopeJIT::Create() here.. */ static llvm::Expected> make_aux(); + static xo::ref::rp make(); + llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index d5aa2901..66412b67 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -66,6 +66,15 @@ namespace xo { )); } /*make*/ + xo::ref::rp + Jit::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + Jit::Jit( std::unique_ptr kal_jit #ifdef NOT_USING From e8a2297ac0c705375a95db9ca50e404e25c336de Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:11:59 -0400 Subject: [PATCH 010/102] xo-jit: need global initialization as per kaleidoscope4 --- include/xo/jit/Jit.hpp | 3 +++ src/jit/Jit.cpp | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 26c81e05..57e8cf9e 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -97,6 +97,9 @@ namespace xo { #endif ); + /* iniitialize native builder (i.e. for platform we're running on) */ + static void init_once(); + private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 66412b67..6a2d43a5 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -15,6 +15,18 @@ namespace xo { using std::endl; namespace jit { + void + Jit::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ /* tracking KaleidoscopeJIT::Create() here.. * @@ -29,6 +41,8 @@ namespace xo { llvm::Expected> Jit::make_aux() { + Jit::init_once(); + #ifdef NOT_USING /* 'executor process control' */ auto epc = llvm::orc::SelfExecutorProcessControl::Create(); From 3b5193c28d97a14528d1e407211ccc9c452ddf8b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:12:52 -0400 Subject: [PATCH 011/102] xo-jit: + llvm module instrumentation methods --- include/xo/jit/Jit.hpp | 11 +++++++++++ include/xo/jit/KaleidoscopeJit.hpp | 6 ++++++ src/jit/Jit.cpp | 17 +++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 57e8cf9e..43a289d4 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -75,6 +75,17 @@ namespace xo { static llvm::Expected> make_aux(); static xo::ref::rp make(); + // ----- module access ----- + + /** target triple = string describing target host for codegen **/ + const std::string & target_triple() const; + /** append function names defined in attached module to *p_v **/ + std::vector get_function_name_v(); + + /** write state of execution session (all the associated dynamic libraries) **/ + void dump_execution_session(); + + // ----- jit code generation ----- llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 3094c076..89f1a724 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -50,6 +50,7 @@ namespace xo { using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; private: + /* execution session - represents a currenlty-running jit program */ std::unique_ptr ES; DataLayout DL; @@ -118,6 +119,11 @@ namespace xo { llvm::Expected lookup(StringRef Name) { return ES->lookup({&MainJD}, Mangle(Name.str())); } + + /* dump */ + void dump_execution_session() { + ES->dump(llvm::errs()); + } }; } // end namespace jit diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 6a2d43a5..4591fadf 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,23 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + const std::string & + Jit::target_triple() const { + return llvm_module_->getTargetTriple(); + } + + std::vector + Jit::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + Jit::dump_execution_session() { + kal_jit_->dump_execution_session(); } llvm::Value * From 2b1827f5ccc23a9939cada1e9978e06a3e778c29 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:13:25 -0400 Subject: [PATCH 012/102] xo-jit: bugfix: missed } --- src/jit/Jit.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 4591fadf..ec583c7c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,8 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + } + const std::string & Jit::target_triple() const { return llvm_module_->getTargetTriple(); From 38d9609147aec3747bc6fc9c888fbb0d371aadd4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:13:44 -0400 Subject: [PATCH 013/102] xo-jit: set data layout on llvm module as per kaleidoscope4 --- src/jit/Jit.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index ec583c7c..640407f5 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -127,6 +127,9 @@ namespace xo { jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif + + llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + } const std::string & From cf7bc43c5e41c6a9af63f0edd420f82126e71f9b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:14:00 -0400 Subject: [PATCH 014/102] xo-jit: check non-null support pointers --- src/jit/Jit.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 640407f5..20d3a7cd 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -130,6 +130,15 @@ namespace xo { llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + if (!llvm_cx_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); + } + if (!llvm_ir_builder_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); + } } const std::string & From f7dfd67770bc8dda32997241fa0e18063db28407 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 15:14:22 -0400 Subject: [PATCH 015/102] xo-jit: + lookup_symbol method on dynamic library --- include/xo/jit/Jit.hpp | 2 ++ src/jit/Jit.cpp | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 43a289d4..cf3f3a0f 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -95,6 +95,8 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); + virtual void display(std::ostream & os) const; virtual std::string display_string() const; diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 20d3a7cd..eef5c197 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -442,6 +442,20 @@ namespace xo { return nullptr; } /*codegen*/ + llvm::orc::ExecutorAddr + Jit::lookup_symbol(const std::string & sym) + { + static llvm::ExitOnError llvm_exit_on_err; + + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym = llvm_exit_on_err(kal_jit_->lookup(sym)); + + /* llvm_addr: ExecutorAddr */ + auto llvm_addr = llvm_sym.getAddress(); + + return llvm_addr; + } /*lookup_symbol*/ + void Jit::display(std::ostream & os) const { os << ""; From f7db84972f458bcd7f7caa41d297aa60dae7d304 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:02:56 -0400 Subject: [PATCH 016/102] xo-jit: setup analsysis pipeline (most of kaleidoscope4) --- include/xo/jit/Jit.hpp | 24 +++++++++++++++++++++++- src/jit/Jit.cpp | 27 +++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index cf3f3a0f..3a8e4c14 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -79,7 +79,10 @@ namespace xo { /** target triple = string describing target host for codegen **/ const std::string & target_triple() const; - /** append function names defined in attached module to *p_v **/ + /** append function names defined in attached module to *p_v + * + * (RC 15jun2024 - this part is working) + **/ std::vector get_function_name_v(); /** write state of execution session (all the associated dynamic libraries) **/ @@ -148,8 +151,27 @@ namespace xo { std::map> global_env_; /** map variable names (formal parameters) to * corresponding llvm interactor + * + * only supports one level atm (i.e. only top-level functions) **/ std::map nested_env_; + + // ----- transforms (also adapted from kaleidescope.cpp) ------ + + /** manages all the passes+analaysis (?) **/ + std::unique_ptr llvm_fpmgr_; + /** loop analysis (?) **/ + std::unique_ptr llvm_lamgr_; + /** function-level analysis (?) **/ + std::unique_ptr llvm_famgr_; + /** cgscc (?) analysis **/ + std::unique_ptr llvm_cgamgr_; + /** module analsyis (?) **/ + std::unique_ptr llvm_mamgr_; + /** pass instrumentation **/ + std::unique_ptr llvm_pic_; + /** standard instrumentation **/ + std::unique_ptr llvm_si_; }; /*Jit*/ inline std::ostream & diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index eef5c197..7d53a313 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -67,6 +67,7 @@ namespace xo { } #endif + static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); @@ -139,6 +140,28 @@ namespace xo { if (!llvm_module_.get()) { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } + + this->llvm_fpmgr_ = std::make_unique(); + this->llvm_lamgr_ = std::make_unique(); + this->llvm_famgr_ = std::make_unique(); + this->llvm_cgamgr_ = std::make_unique(); + this->llvm_mamgr_ = std::make_unique(); + this->llvm_pic_ = std::make_unique(); + this->llvm_si_ = std::make_unique(*llvm_cx_, + /*DebugLogging*/ true); + + this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); + + // TODO: llvm_fpmgr_->addPass(InstCombinePass()) etc. + // TODO: llvm_fpmgr_->addPass(ReassociatePass()) etc. + // TODO: llvm_fpmgr_->addPass(GVNPasss()) etc. + // TODO: llvm_fpmgr_->addPass(SimplifyCFGPass()) etc. + + /** 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_); } const std::string & @@ -389,8 +412,8 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); - /* optimize! */ - // thefpm->run(*fn, *thefam); + /* optimize! does this generate code? */ + llvm_fpmgr_->run(*fn, *llvm_famgr_); return fn; } From 7f4d0ee8e29022e2ae46d0ebbbeba5820257a20b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:03:19 -0400 Subject: [PATCH 017/102] xo-jit: README: ++ links --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index e2c4cbb3..a8a7457d 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,11 @@ 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 From 73bf0663188117d291ac36eb581690e8a60f3cc1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 16:03:28 -0400 Subject: [PATCH 018/102] xo-jit: ex1: dump execution session --- example/ex1/ex1.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 04dc2f12..77ee265c 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -151,6 +151,10 @@ main() { << xtag("expr", lambda) << endl; } + + /* is this in module? */ + cerr << "ex1: jit execution session" << endl; + jit->dump_execution_session(); } } From 1b3718bd12a48775724446dd1475d0d1d1d33d65 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:13:59 -0400 Subject: [PATCH 019/102] xo-jit: + machgen_current_module(): generate machine code! --- include/xo/jit/Jit.hpp | 22 +++++++++++++++++++++- src/jit/Jit.cpp | 32 +++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 3a8e4c14..14b368d3 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -98,6 +98,13 @@ namespace xo { llvm::Value * codegen(ref::brw expr); + // ----- jit online execution ----- + + /** add IR code in current module to JIT, + * so that its available for execution + **/ + void machgen_current_module(); + llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; @@ -116,6 +123,9 @@ namespace xo { /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); + /** (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] ----- @@ -133,6 +143,14 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- + /** everything bleow represents a pipeline + * that takes expressions, and turns them into llvm IR. + * + * llvm IR can be added to running JIT by calling + * kal_jit_.addModule() + * Note that this makes the module itself unavailable to us + **/ + /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -142,7 +160,9 @@ namespace xo { std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; - /** a module (aka library) being prepared by llvm. + /** a module (1:1 with library) being prepared by llvm. + * IR-level -- does not contain machine code + * * - function names are unique within a module. **/ std::unique_ptr llvm_module_; diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 7d53a313..df517969 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -98,7 +98,7 @@ namespace xo { llvm::DataLayout dl #endif ) - : kal_jit_{std::move(kal_jit)}, + : kal_jit_{std::move(kal_jit)} #ifdef NOT_USING jit_es_(std::move(jit_es)), jit_data_layout_(std::move(dl)), @@ -114,9 +114,6 @@ namespace xo { jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ #endif - llvm_cx_{std::make_unique()}, - llvm_ir_builder_{std::make_unique>(*llvm_cx_)}, - llvm_module_{std::make_unique("xojit", *llvm_cx_)} { #ifdef NOT_USING jit_our_dynamic_lib_.addGenerator @@ -129,6 +126,16 @@ namespace xo { } #endif + this->recreate_llvm_ir_pipeline(); + } + + void + Jit::recreate_llvm_ir_pipeline() + { + llvm_cx_ = std::make_unique(); + llvm_ir_builder_ = std::make_unique>(*llvm_cx_); + llvm_module_ = std::make_unique("xojit", *llvm_cx_); + llvm_module_->setDataLayout(kal_jit_->getDataLayout()); if (!llvm_cx_.get()) { @@ -162,7 +169,7 @@ namespace xo { llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); - } + } /*recreate_llvm_ir_pipeline*/ const std::string & Jit::target_triple() const { @@ -465,6 +472,21 @@ namespace xo { return nullptr; } /*codegen*/ + void + Jit::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_)); + + llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } + llvm::orc::ExecutorAddr Jit::lookup_symbol(const std::string & sym) { From 537e178e09e5ff5f8a43a751942d85aaaf693e6a Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 15 Jun 2024 17:22:33 -0400 Subject: [PATCH 020/102] xo-jit: add transform passes (from kal 4) --- src/jit/Jit.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index df517969..1f660348 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -159,10 +159,11 @@ namespace xo { this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); - // TODO: llvm_fpmgr_->addPass(InstCombinePass()) etc. - // TODO: llvm_fpmgr_->addPass(ReassociatePass()) etc. - // TODO: llvm_fpmgr_->addPass(GVNPasss()) etc. - // TODO: llvm_fpmgr_->addPass(SimplifyCFGPass()) etc. + /** transform passes **/ + this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); + 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; From f3af5d27bf51c3617b7a79d066cb5749643edbcb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 01:01:49 -0400 Subject: [PATCH 021/102] xo-jit: move IR improvement pipeline to dedicated class --- include/xo/jit/IrPipeline.hpp | 75 +++++++++++++++++++++++++++++++++++ include/xo/jit/Jit.hpp | 18 +-------- src/jit/CMakeLists.txt | 1 + src/jit/IrPipeline.cpp | 45 +++++++++++++++++++++ src/jit/Jit.cpp | 27 ++----------- 5 files changed, 126 insertions(+), 40 deletions(-) create mode 100644 include/xo/jit/IrPipeline.hpp create mode 100644 src/jit/IrPipeline.cpp diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp new file mode 100644 index 00000000..8719fbe2 --- /dev/null +++ b/include/xo/jit/IrPipeline.hpp @@ -0,0 +1,75 @@ +/** @file IrPipeline.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" + +/* stuff from kaleidoscope.cpp */ +#include "llvm/ADT/APFloat.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/DerivedTypes.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/PassManager.h" +#include "llvm/IR/Type.h" +#include "llvm/IR/Verifier.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/StandardInstrumentations.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Transforms/InstCombine/InstCombine.h" +#include "llvm/Transforms/Scalar.h" +#include "llvm/Transforms/Scalar/GVN.h" +#include "llvm/Transforms/Scalar/Reassociate.h" +#include "llvm/Transforms/Scalar/SimplifyCFG.h" + +//#include + +namespace xo { + namespace jit { + /** @class 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(llvm::LLVMContext & llvm_cx); + + void run_pipeline(llvm::Function & fn); + + private: + // ----- transforms (also adapted from kaleidescope.cpp) ------ + + /** manages all the passes+analaysis (?) **/ + std::unique_ptr llvm_fpmgr_; + /** loop analysis (?) **/ + std::unique_ptr llvm_lamgr_; + /** function-level analysis (?) **/ + std::unique_ptr llvm_famgr_; + /** cgscc (?) analysis **/ + std::unique_ptr llvm_cgamgr_; + /** module analsyis (?) **/ + std::unique_ptr llvm_mamgr_; + /** pass instrumentation **/ + std::unique_ptr llvm_pic_; + /** standard instrumentation **/ + std::unique_ptr llvm_si_; + }; /*IrPipeline*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end IrPipeline.hpp **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 14b368d3..f5eb65a4 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -8,6 +8,7 @@ //#include #include "xo/refcnt/Refcounted.hpp" +#include "IrPipeline.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -150,6 +151,7 @@ namespace xo { * kal_jit_.addModule() * Note that this makes the module itself unavailable to us **/ + xo::ref::rp ir_pipeline_; /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. @@ -176,22 +178,6 @@ namespace xo { **/ std::map nested_env_; - // ----- transforms (also adapted from kaleidescope.cpp) ------ - - /** manages all the passes+analaysis (?) **/ - std::unique_ptr llvm_fpmgr_; - /** loop analysis (?) **/ - std::unique_ptr llvm_lamgr_; - /** function-level analysis (?) **/ - std::unique_ptr llvm_famgr_; - /** cgscc (?) analysis **/ - std::unique_ptr llvm_cgamgr_; - /** module analsyis (?) **/ - std::unique_ptr llvm_mamgr_; - /** pass instrumentation **/ - std::unique_ptr llvm_pic_; - /** standard instrumentation **/ - std::unique_ptr llvm_si_; }; /*Jit*/ inline std::ostream & diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index abd3c903..bbf5aa99 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -2,6 +2,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS + IrPipeline.cpp Jit.cpp ) diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp new file mode 100644 index 00000000..200d3a52 --- /dev/null +++ b/src/jit/IrPipeline.cpp @@ -0,0 +1,45 @@ +/* @file IrPipeline.cpp */ + +#include "IrPipeline.hpp" + +namespace xo { + namespace jit { + IrPipeline::IrPipeline(llvm::LLVMContext & llvm_cx) + { + using std::make_unique; + + this->llvm_fpmgr_ = make_unique(); + this->llvm_lamgr_ = std::make_unique(); + this->llvm_famgr_ = std::make_unique(); + this->llvm_cgamgr_ = std::make_unique(); + this->llvm_mamgr_ = std::make_unique(); + this->llvm_pic_ = std::make_unique(); + this->llvm_si_ = std::make_unique(llvm_cx, + /*DebugLogging*/ true); + + this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); + + /** transform passes **/ + this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); + this->llvm_fpmgr_->addPass(llvm::ReassociatePass()); + this->llvm_fpmgr_->addPass(llvm::GVNPass()); + this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass()); + + /** tracking for analysis passes that share info? **/ + llvm::PassBuilder llvm_pass_builder; + llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_); + llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_); + llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_); + } /*ctor*/ + + void + IrPipeline::run_pipeline(llvm::Function & fn) + { + llvm_fpmgr_->run(fn, *llvm_famgr_); + } /*run_pipeline*/ + } /*namespace jit*/ +} /*namespace xo*/ + + + +/* end IrPipeline.cpp */ diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 1f660348..67752b7b 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -148,28 +148,7 @@ namespace xo { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } - this->llvm_fpmgr_ = std::make_unique(); - this->llvm_lamgr_ = std::make_unique(); - this->llvm_famgr_ = std::make_unique(); - this->llvm_cgamgr_ = std::make_unique(); - this->llvm_mamgr_ = std::make_unique(); - this->llvm_pic_ = std::make_unique(); - this->llvm_si_ = std::make_unique(*llvm_cx_, - /*DebugLogging*/ true); - - this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); - - /** transform passes **/ - this->llvm_fpmgr_->addPass(llvm::InstCombinePass()); - 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_); + ir_pipeline_ = new IrPipeline(*llvm_cx_); } /*recreate_llvm_ir_pipeline*/ const std::string & @@ -420,8 +399,8 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); - /* optimize! does this generate code? */ - llvm_fpmgr_->run(*fn, *llvm_famgr_); + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); return fn; } From a927d44e0e757190ea1baa903c7ab91cd72300f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:00:37 -0400 Subject: [PATCH 022/102] xo-jit: + LlvmContext to keepalive native LLVMContext --- include/xo/jit/IrPipeline.hpp | 6 ++++- include/xo/jit/Jit.hpp | 5 +++-- include/xo/jit/LlvmContext.hpp | 40 ++++++++++++++++++++++++++++++++++ src/jit/CMakeLists.txt | 1 + src/jit/IrPipeline.cpp | 7 ++++-- src/jit/Jit.cpp | 32 +++++++++++++++++---------- src/jit/LlvmContext.cpp | 20 +++++++++++++++++ 7 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 include/xo/jit/LlvmContext.hpp create mode 100644 src/jit/LlvmContext.cpp diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 8719fbe2..7efd5b4f 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -6,6 +6,7 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" +#include "LlvmContext.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -46,13 +47,16 @@ namespace xo { **/ class IrPipeline : public ref::Refcount { public: - explicit IrPipeline(llvm::LLVMContext & llvm_cx); + explicit IrPipeline(ref::rp llvm_cx); void run_pipeline(llvm::Function & fn); private: // ----- transforms (also adapted from kaleidescope.cpp) ------ + /** keepalive for contained llvm::LLVMContext **/ + ref::rp llvm_cx_; + /** manages all the passes+analaysis (?) **/ std::unique_ptr llvm_fpmgr_; /** loop analysis (?) **/ diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f5eb65a4..0cfb423d 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -9,6 +9,7 @@ #include "xo/refcnt/Refcounted.hpp" #include "IrPipeline.hpp" +#include "LlvmContext.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -31,7 +32,6 @@ #include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/IR/DataLayout.h" -#include "llvm/IR/LLVMContext.h" #include #endif @@ -159,7 +159,8 @@ namespace xo { * Not threadsafe, but ok to have multiple threads, * each with its own LLVMContext **/ - std::unique_ptr llvm_cx_; + ref::rp llvm_cx_; + //std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; /** a module (1:1 with library) being prepared by llvm. diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp new file mode 100644 index 00000000..38f1492a --- /dev/null +++ b/include/xo/jit/LlvmContext.hpp @@ -0,0 +1,40 @@ +/** @file LlvmContext.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "llvm/IR/LLVMContext.h" +//#include + +namespace xo { + namespace jit { + /** @class LlvmContext + * @brief Keepalive for a llvm::LLVMContext instance. + * + * For example IrPipeline holds an rp + * to help ensure validity of embedded llvm::LLVMContext reference + **/ + class LlvmContext : public ref::Refcount { + public: + static xo::ref::rp make(); + + llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; } + std::unique_ptr & llvm_cx() { return llvm_cx_; } + + private: + LlvmContext(); + + private: + /** Llvm context. Ties together fragments of code generation + * for AST subtrees that go into the same module. + **/ + std::unique_ptr llvm_cx_; + }; /*LlvmContext*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end LlvmContext.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index bbf5aa99..018ecb0e 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -2,6 +2,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS + LlvmContext.cpp IrPipeline.cpp Jit.cpp ) diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index 200d3a52..d08d9639 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -4,17 +4,20 @@ namespace xo { namespace jit { - IrPipeline::IrPipeline(llvm::LLVMContext & llvm_cx) + IrPipeline::IrPipeline(ref::rp llvm_cx) { using std::make_unique; + this->llvm_cx_ = std::move(llvm_cx); + this->llvm_fpmgr_ = make_unique(); this->llvm_lamgr_ = std::make_unique(); this->llvm_famgr_ = std::make_unique(); this->llvm_cgamgr_ = std::make_unique(); this->llvm_mamgr_ = std::make_unique(); this->llvm_pic_ = std::make_unique(); - this->llvm_si_ = std::make_unique(llvm_cx, + /* reference kept alive by @ref llvm_cx_ */ + this->llvm_si_ = std::make_unique(llvm_cx_->llvm_cx_ref(), /*DebugLogging*/ true); this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get()); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 67752b7b..0accdd65 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -132,9 +132,10 @@ namespace xo { void Jit::recreate_llvm_ir_pipeline() { - llvm_cx_ = std::make_unique(); - llvm_ir_builder_ = std::make_unique>(*llvm_cx_); - llvm_module_ = std::make_unique("xojit", *llvm_cx_); + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); llvm_module_->setDataLayout(kal_jit_->getDataLayout()); @@ -148,7 +149,7 @@ namespace xo { throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); } - ir_pipeline_ = new IrPipeline(*llvm_cx_); + ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ const std::string & @@ -176,10 +177,10 @@ namespace xo { TypeDescr td = expr->value_td(); if (td->is_native()) { - return llvm::ConstantFP::get(*llvm_cx_, + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } else if (td->is_native()) { - return llvm::ConstantFP::get(*llvm_cx_, + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); } @@ -230,7 +231,7 @@ namespace xo { log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); if (arg_td->is_native()) { - llvm_argtype_v.push_back(llvm::Type::getDoubleTy(*llvm_cx_)); + llvm_argtype_v.push_back(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); // TODO: extend with other native types here... } else { @@ -252,7 +253,7 @@ namespace xo { llvm::Type * llvm_retval = nullptr; if (retval_td->is_native()) { - llvm_retval = llvm::Type::getDoubleTy(*llvm_cx_); + llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); } else { cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" << xtag("f", expr->name()) @@ -363,9 +364,9 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(1, llvm::Type::getDoubleTy(*llvm_cx_)); + std::vector double_v(1, llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(*llvm_cx_), + auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), double_v, false /*!varargs*/); @@ -381,7 +382,7 @@ namespace xo { /* generate function body */ - auto block = llvm::BasicBlock::Create(*llvm_cx_, "entry", fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", fn); llvm_ir_builder_->SetInsertPoint(block); @@ -459,8 +460,15 @@ namespace xo { auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), - std::move(llvm_cx_)); + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); diff --git a/src/jit/LlvmContext.cpp b/src/jit/LlvmContext.cpp new file mode 100644 index 00000000..19349aca --- /dev/null +++ b/src/jit/LlvmContext.cpp @@ -0,0 +1,20 @@ +/* @file LlvmContext.cpp */ + +#include "LlvmContext.hpp" + +namespace xo { + namespace jit { + xo::ref::rp + LlvmContext::make() { + return new LlvmContext(); + } + + LlvmContext::LlvmContext() + : llvm_cx_{std::make_unique()} + {} + + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end LlvmContext.cpp */ From 7c3226ee64e1f850687700b714a227ca0db74117 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:21:42 -0400 Subject: [PATCH 023/102] xo-jit: refactoring -- var names in KaleidoscopeJit --- include/xo/jit/Jit.hpp | 5 -- include/xo/jit/KaleidoscopeJit.hpp | 89 ++++++++++++++++-------------- src/jit/Jit.cpp | 15 ----- 3 files changed, 48 insertions(+), 61 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 0cfb423d..879818c8 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -115,7 +115,6 @@ namespace xo { Jit( std::unique_ptr kal_jit #ifdef NOT_USING - std::unique_ptr es, llvm::orc::JITTargetMachineBuilder jtmb, llvm::DataLayout dl #endif @@ -133,10 +132,6 @@ namespace xo { std::unique_ptr kal_jit_; #ifdef NOT_USING - std::unique_ptr jit_es_; - llvm::DataLayout jit_data_layout_; - llvm::orc::MangleAndInterner jit_mangle_; - llvm::orc::RTDyldObjectLinkingLayer jit_object_layer_; llvm::orc::IRCompileLayer jit_compile_layer_; /** reference here. looks like storage owned by .jit_es **/ llvm::orc::JITDylib & jit_our_dynamic_lib_; diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 89f1a724..5359bbb0 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -10,8 +10,7 @@ // //===----------------------------------------------------------------------===// -#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H -#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H +#pragma once #include "llvm/ADT/StringRef.h" #include "llvm/ExecutionEngine/JITSymbol.h" @@ -50,42 +49,47 @@ namespace xo { using SelfExecutorProcessControl = llvm::orc::SelfExecutorProcessControl; private: - /* execution session - represents a currenlty-running jit program */ - std::unique_ptr ES; + /** execution session - represents a currently-running jit program **/ + std::unique_ptr xsession_; - DataLayout DL; - MangleAndInterner Mangle; + /** (?) needed for name mangling (?) **/ + DataLayout data_layout_; + /** symbol mangling and unique-ifying */ + MangleAndInterner mangler_; - RTDyldObjectLinkingLayer ObjectLayer; - IRCompileLayer CompileLayer; + /** in-process linking layer + * (? specialized for jit in running process ?) + **/ + RTDyldObjectLinkingLayer object_layer_; + IRCompileLayer compile_layer_; JITDylib &MainJD; public: - KaleidoscopeJIT(std::unique_ptr ES, - JITTargetMachineBuilder JTMB, - DataLayout DL) - : ES(std::move(ES)), - DL(std::move(DL)), - Mangle(*this->ES, this->DL), - ObjectLayer(*this->ES, - []() { return std::make_unique(); }), - CompileLayer(*this->ES, ObjectLayer, - std::make_unique(std::move(JTMB))), - MainJD(this->ES->createBareJITDylib("
")) + KaleidoscopeJIT(std::unique_ptr xsession, + JITTargetMachineBuilder jtmb, + DataLayout data_layout_) + : xsession_{std::move(xsession)}, + data_layout_(std::move(data_layout_)), + mangler_(*this->xsession_, this->data_layout_), + object_layer_(*this->xsession_, + []() { return std::make_unique(); }), + compile_layer_(*this->xsession_, object_layer_, + std::make_unique(std::move(jtmb))), + MainJD(this->xsession_->createBareJITDylib("
")) { MainJD.addGenerator( cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( - DL.getGlobalPrefix()))); - if (JTMB.getTargetTriple().isOSBinFormatCOFF()) { - ObjectLayer.setOverrideObjectFlagsWithResponsibilityFlags(true); - ObjectLayer.setAutoClaimResponsibilityForObjectSymbols(true); + data_layout_.getGlobalPrefix()))); + if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { + object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); + object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } } ~KaleidoscopeJIT() { - if (auto Err = ES->endSession()) - ES->reportError(std::move(Err)); + if (auto Err = this->xsession_->endSession()) + this->xsession_->reportError(std::move(Err)); } static llvm::Expected> Create() { @@ -93,40 +97,43 @@ namespace xo { if (!EPC) return EPC.takeError(); - auto ES = std::make_unique(std::move(*EPC)); + auto xsession = std::make_unique(std::move(*EPC)); - JITTargetMachineBuilder JTMB( - ES->getExecutorProcessControl().getTargetTriple()); + JITTargetMachineBuilder jtmb + (xsession->getExecutorProcessControl().getTargetTriple()); - auto DL = JTMB.getDefaultDataLayoutForTarget(); - if (!DL) - return DL.takeError(); + auto data_layout = jtmb.getDefaultDataLayoutForTarget(); + if (!data_layout) + return data_layout.takeError(); - return std::make_unique(std::move(ES), std::move(JTMB), - std::move(*DL)); + return std::make_unique(std::move(xsession), + std::move(jtmb), + std::move(*data_layout)); } - const DataLayout &getDataLayout() const { return DL; } + const DataLayout & getDataLayout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } - llvm::Error addModule(ThreadSafeModule TSM, ResourceTrackerSP RT = nullptr) { + llvm::Error + addModule(ThreadSafeModule ts_module, + ResourceTrackerSP RT = nullptr) { if (!RT) RT = MainJD.getDefaultResourceTracker(); - return CompileLayer.add(RT, std::move(TSM)); + + return compile_layer_.add(RT, std::move(ts_module)); } - llvm::Expected lookup(StringRef Name) { - return ES->lookup({&MainJD}, Mangle(Name.str())); + llvm::Expected lookup(StringRef name) { + return this->xsession_->lookup({&MainJD}, + this->mangler_(name.str())); } /* dump */ void dump_execution_session() { - ES->dump(llvm::errs()); + this->xsession_->dump(llvm::errs()); } }; } // end namespace jit } // end namespace xo - -#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 0accdd65..43cb7e2c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -73,11 +73,6 @@ namespace xo { std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); return std::unique_ptr(new Jit(std::move(kal_jit) -#ifdef NOT_USING - std::move(es), - std::move(jtmb), - std::move(*dl) -#endif )); } /*make*/ @@ -93,18 +88,12 @@ namespace xo { Jit::Jit( std::unique_ptr kal_jit #ifdef NOT_USING - std::unique_ptr jit_es, llvm::orc::JITTargetMachineBuilder jtmb, llvm::DataLayout dl #endif ) : kal_jit_{std::move(kal_jit)} #ifdef NOT_USING - jit_es_(std::move(jit_es)), - jit_data_layout_(std::move(dl)), - jit_mangle_(*this->jit_es_, this->jit_data_layout_), - jit_object_layer_(*this->jit_es_, - []() { return std::make_unique(); }), jit_compile_layer_(*this->jit_es_, jit_object_layer_, std::make_unique(std::move(jtmb))), @@ -119,10 +108,6 @@ namespace xo { jit_our_dynamic_lib_.addGenerator (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess (jit_data_layout_.getGlobalPrefix()))); - - if(jtmb.getTargetTriple().isOSBinFormatCOFF()) { - jit_object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); - jit_object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); } #endif From c16686fd4cce950458d5f69131094542076f1f73 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:27:03 -0400 Subject: [PATCH 024/102] xo-jit: tidy: drop never-compiled obsolete code --- include/xo/jit/Jit.hpp | 30 +--------------- include/xo/jit/KaleidoscopeJit.hpp | 2 +- src/jit/Jit.cpp | 55 +++--------------------------- 3 files changed, 6 insertions(+), 81 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 879818c8..9ef11939 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -18,22 +18,6 @@ #include "xo/expression/Variable.hpp" #include "KaleidoscopeJit.hpp" -#ifdef NOT_USING -/* stuff from KaleidoscopeJIT.hpp */ -#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" -#include "llvm/ExecutionEngine/SectionMemoryManager.h" -#include "llvm/IR/DataLayout.h" -#include -#endif /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -112,13 +96,7 @@ namespace xo { virtual std::string display_string() const; private: - Jit( - std::unique_ptr kal_jit -#ifdef NOT_USING - llvm::orc::JITTargetMachineBuilder jtmb, - llvm::DataLayout dl -#endif - ); + Jit(std::unique_ptr kal_jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -131,12 +109,6 @@ namespace xo { std::unique_ptr kal_jit_; -#ifdef NOT_USING - llvm::orc::IRCompileLayer jit_compile_layer_; - /** reference here. looks like storage owned by .jit_es **/ - llvm::orc::JITDylib & jit_our_dynamic_lib_; -#endif - // ----- this part adapted from kaleidoscope.cpp ----- /** everything bleow represents a pipeline diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 5359bbb0..97f65358 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -111,7 +111,7 @@ namespace xo { std::move(*data_layout)); } - const DataLayout & getDataLayout() const { return data_layout_; } + const DataLayout & data_layout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index 43cb7e2c..dafbd07e 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -43,31 +43,6 @@ namespace xo { { Jit::init_once(); -#ifdef NOT_USING - /* 'executor process control' */ - auto epc = llvm::orc::SelfExecutorProcessControl::Create(); - if (!epc) { - return epc.takeError(); - //throw std::runtime_error("Jit::make: failed to establish executor process control"); - } - - /* 'execution session' */ - auto es = std::make_unique(std::move(*epc)); - - /* 'jit target machine builder' */ - llvm::orc::JITTargetMachineBuilder jtmb(es - ->getExecutorProcessControl() - .getTargetTriple()); - - auto dl = jtmb.getDefaultDataLayoutForTarget(); - if (!dl) { - return dl.takeError(); - //throw std::runtime_error("Jit::make: failed to establish data layout object" - // " for target machine"); - } -#endif - - static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); @@ -85,32 +60,9 @@ namespace xo { return jit.release(); } /*make*/ - Jit::Jit( - std::unique_ptr kal_jit -#ifdef NOT_USING - llvm::orc::JITTargetMachineBuilder jtmb, - llvm::DataLayout dl -#endif - ) + Jit::Jit(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} -#ifdef NOT_USING - jit_compile_layer_(*this->jit_es_, - jit_object_layer_, - std::make_unique(std::move(jtmb))), - /* note: string passed to createBareJITDyLib must be unique - * (within running process address space?) - */ - jit_our_dynamic_lib_(this->jit_es_->createBareJITDylib("")), /*was MainJD*/ -#endif - { -#ifdef NOT_USING - jit_our_dynamic_lib_.addGenerator - (cantFail(llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess - (jit_data_layout_.getGlobalPrefix()))); - } -#endif - this->recreate_llvm_ir_pipeline(); } @@ -120,9 +72,9 @@ namespace xo { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); - llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(kal_jit_->getDataLayout()); + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(kal_jit_->data_layout()); if (!llvm_cx_.get()) { throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); @@ -139,6 +91,7 @@ namespace xo { const std::string & Jit::target_triple() const { + // although this getter is defined, seems to be empty in practice return llvm_module_->getTargetTriple(); } From a23e0f56c6e3be57487d6ee633b0bc97e4d902a6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:34:27 -0400 Subject: [PATCH 025/102] xo-jit: fix Jit::target_triple() --- include/xo/jit/KaleidoscopeJit.hpp | 4 ++++ src/jit/Jit.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/KaleidoscopeJit.hpp index 97f65358..f3d2ebcd 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/KaleidoscopeJit.hpp @@ -111,6 +111,10 @@ namespace xo { std::move(*data_layout)); } + const std::string & target_triple() const { + return xsession_->getTargetTriple().getTriple(); + } + const DataLayout & data_layout() const { return data_layout_; } JITDylib &getMainJITDylib() { return MainJD; } diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp index dafbd07e..f1e00828 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/Jit.cpp @@ -89,10 +89,15 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ const std::string & Jit::target_triple() const { // although this getter is defined, seems to be empty in practice - return llvm_module_->getTargetTriple(); + //return llvm_module_->getTargetTriple(); + + return kal_jit_->target_triple(); } std::vector From 932e7cd966cc60a7aee91bd6d4c2015f55d28dc8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 11:49:23 -0400 Subject: [PATCH 026/102] xo-jit: refactor: Jit -> MachPipeline --- example/ex1/ex1.cpp | 6 +- include/xo/jit/{Jit.hpp => MachPipeline.hpp} | 18 ++--- src/jit/CMakeLists.txt | 2 +- src/jit/{Jit.cpp => MachPipeline.cpp} | 76 ++++++++++---------- 4 files changed, 51 insertions(+), 51 deletions(-) rename include/xo/jit/{Jit.hpp => MachPipeline.hpp} (92%) rename src/jit/{Jit.cpp => MachPipeline.cpp} (84%) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 77ee265c..238ba4a3 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -1,6 +1,6 @@ /** @file ex1.cpp **/ -#include "xo/jit/Jit.hpp" +#include "xo/jit/MachPipeline.hpp" #include "xo/expression/Constant.hpp" #include "xo/expression/Primitive.hpp" #include "xo/expression/Apply.hpp" @@ -35,7 +35,7 @@ int main() { using xo::scope; - using xo::jit::Jit; + using xo::jit::MachPipeline; using xo::ast::make_constant; using xo::ast::make_primitive; using xo::ast::make_apply; @@ -54,7 +54,7 @@ main() { llvm::InitializeNativeTargetAsmParser(); //auto jit = llvm_exit_on_err(Jit::make_aux()); - auto jit = Jit::make(); + auto jit = MachPipeline::make(); //static_assert(std::is_function_v); diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/MachPipeline.hpp similarity index 92% rename from include/xo/jit/Jit.hpp rename to include/xo/jit/MachPipeline.hpp index 9ef11939..71df826f 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -1,4 +1,4 @@ -/** @file Jit.hpp +/** @file MachPipeline.hpp * * Author: Roland Conybeare **/ @@ -45,20 +45,20 @@ namespace xo { namespace jit { - /** @class Jit + /** @class MachPipeline * @brief just-in-time compiler for EGAD * * TODO: make module name a parameter? **/ - class Jit : public ref::Refcount { + class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; //using ConstantInterface = xo::ast::ConstantInterface; public: /* tracking KaleidoscopeJIT::Create() here.. */ - static llvm::Expected> make_aux(); - static xo::ref::rp make(); + static llvm::Expected> make_aux(); + static xo::ref::rp make(); // ----- module access ----- @@ -96,7 +96,7 @@ namespace xo { virtual std::string display_string() const; private: - Jit(std::unique_ptr kal_jit); + MachPipeline(std::unique_ptr kal_jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -146,10 +146,10 @@ namespace xo { **/ std::map nested_env_; - }; /*Jit*/ + }; /*MachPipeline*/ inline std::ostream & - operator<<(std::ostream & os, const Jit & x) { + operator<<(std::ostream & os, const MachPipeline & x) { x.display(os); return os; } @@ -157,4 +157,4 @@ namespace xo { } /*namespace xo*/ -/** end Jit.hpp **/ +/** end MachPipeline.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 018ecb0e..88b9d2da 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -4,7 +4,7 @@ set(SELF_LIB xo_jit) set(SELF_SRCS LlvmContext.cpp IrPipeline.cpp - Jit.cpp + MachPipeline.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/Jit.cpp b/src/jit/MachPipeline.cpp similarity index 84% rename from src/jit/Jit.cpp rename to src/jit/MachPipeline.cpp index f1e00828..4acbbf3c 100644 --- a/src/jit/Jit.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,6 @@ -/* @file Jit.cpp */ +/* @file MachPipeline.cpp */ -#include "Jit.hpp" +#include "MachPipeline.hpp" namespace xo { using xo::ast::exprtype; @@ -16,7 +16,7 @@ namespace xo { namespace jit { void - Jit::init_once() { + MachPipeline::init_once() { static bool s_init_once = false; if (!s_init_once) { @@ -38,36 +38,36 @@ namespace xo { * + 'jit_copmile_layer' * + 'jit_our_dynamic_lib' */ - llvm::Expected> - Jit::make_aux() + llvm::Expected> + MachPipeline::make_aux() { - Jit::init_once(); + MachPipeline::init_once(); static llvm::ExitOnError llvm_exit_on_err; std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); - return std::unique_ptr(new Jit(std::move(kal_jit) + return std::unique_ptr(new MachPipeline(std::move(kal_jit) )); } /*make*/ - xo::ref::rp - Jit::make() { + xo::ref::rp + MachPipeline::make() { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr jit = llvm_exit_on_err(make_aux()); + std::unique_ptr jit = llvm_exit_on_err(make_aux()); return jit.release(); } /*make*/ - Jit::Jit(std::unique_ptr kal_jit) + MachPipeline::MachPipeline(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} { this->recreate_llvm_ir_pipeline(); } void - Jit::recreate_llvm_ir_pipeline() + MachPipeline::recreate_llvm_ir_pipeline() { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); @@ -77,13 +77,13 @@ namespace xo { llvm_module_->setDataLayout(kal_jit_->data_layout()); if (!llvm_cx_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm context"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); } if (!llvm_ir_builder_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm IR builder"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); } if (!llvm_module_.get()) { - throw std::runtime_error("Jit::ctor: expected non-empty llvm module"); + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); } ir_pipeline_ = new IrPipeline(llvm_cx_); @@ -93,7 +93,7 @@ namespace xo { * e.g. "x86_64-unknown-linux-gnu" **/ const std::string & - Jit::target_triple() const { + MachPipeline::target_triple() const { // although this getter is defined, seems to be empty in practice //return llvm_module_->getTargetTriple(); @@ -101,7 +101,7 @@ namespace xo { } std::vector - Jit::get_function_name_v() { + MachPipeline::get_function_name_v() { std::vector retval; for (const auto & fn_name : *llvm_module_) retval.push_back(fn_name.getName().str()); @@ -110,12 +110,12 @@ namespace xo { } /*get_function_names*/ void - Jit::dump_execution_session() { + MachPipeline::dump_execution_session() { kal_jit_->dump_execution_session(); } llvm::Value * - Jit::codegen_constant(ref::brw expr) + MachPipeline::codegen_constant(ref::brw expr) { TypeDescr td = expr->value_td(); @@ -131,7 +131,7 @@ namespace xo { } llvm::Function * - Jit::codegen_primitive(ref::brw expr) + MachPipeline::codegen_primitive(ref::brw expr) { constexpr bool c_debug_flag = true; using xo::scope; @@ -178,7 +178,7 @@ namespace xo { // TODO: extend with other native types here... } else { - cerr << "Jit::codegen_primitive: error: primitive f with arg i of type T where double expected" + cerr << "MachPipeline::codegen_primitive: error: primitive f with arg i of type T where double expected" << xtag("f", expr->name()) << xtag("i", i) << xtag("T", arg_td->short_name()) @@ -198,7 +198,7 @@ namespace xo { if (retval_td->is_native()) { llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); } else { - cerr << "Jit::codegen_primitive: error: primitive f returning T where double expected" + cerr << "MachPipeline::codegen_primitive: error: primitive f returning T where double expected" << xtag("f", expr->name()) << xtag("T", retval_td->short_name()) << endl; @@ -234,7 +234,7 @@ namespace xo { } /*codegen_primitive*/ llvm::Value * - Jit::codegen_apply(ref::brw apply) + MachPipeline::codegen_apply(ref::brw apply) { using std::cerr; using std::endl; @@ -251,13 +251,13 @@ namespace xo { auto * fn = this->codegen_primitive(pm); #ifdef NOT_USING_DEBUG - cerr << "Jit::codegen_apply: fn:" << endl; + cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); cerr << endl; #endif if (fn->arg_size() != apply->argv().size()) { - cerr << "Jit::codegen_apply: error: callee f expecting n1 args where n2 supplied" + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" << xtag("f", pm->name()) << xtag("n1", pm->n_arg()) << xtag("n2", apply->argv().size()) @@ -272,7 +272,7 @@ namespace xo { auto * arg = this->codegen(arg_expr); #ifdef NOT_USING_DEBUG - cerr << "Jit::codegen_apply: arg:" << endl; + cerr << "MachPipeline::codegen_apply: arg:" << endl; arg->print(llvm::errs()); cerr << endl; #endif @@ -282,13 +282,13 @@ namespace xo { return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); } else { - cerr << "Jit::codegen_apply: error: only allowing call to known primitives at present" << endl; + cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; return nullptr; } } /*codegen_apply*/ llvm::Function * - Jit::codegen_lambda(ref::brw lambda) + MachPipeline::codegen_lambda(ref::brw lambda) { /* reminder! this is the *expression*, not the *closure* */ @@ -356,12 +356,12 @@ namespace xo { } /*codegen_lambda*/ llvm::Value * - Jit::codegen_variable(ref::brw var) + MachPipeline::codegen_variable(ref::brw var) { auto ix = nested_env_.find(var->name()); if (ix == nested_env_.end()) { - cerr << "Jit::codegen_variable: no binding for variable x" + cerr << "MachPipeline::codegen_variable: no binding for variable x" << xtag("x", var->name()) << endl; } @@ -370,7 +370,7 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - Jit::codegen(ref::brw expr) + MachPipeline::codegen(ref::brw expr) { switch(expr->extype()) { case exprtype::constant: @@ -389,7 +389,7 @@ namespace xo { break; } - cerr << "Jit::codegen: error: no handler for expression of type T" + cerr << "MachPipeline::codegen: error: no handler for expression of type T" << xtag("T", expr->extype()) << endl; @@ -397,7 +397,7 @@ namespace xo { } /*codegen*/ void - Jit::machgen_current_module() + MachPipeline::machgen_current_module() { static llvm::ExitOnError llvm_exit_on_err; @@ -419,7 +419,7 @@ namespace xo { } llvm::orc::ExecutorAddr - Jit::lookup_symbol(const std::string & sym) + MachPipeline::lookup_symbol(const std::string & sym) { static llvm::ExitOnError llvm_exit_on_err; @@ -433,15 +433,15 @@ namespace xo { } /*lookup_symbol*/ void - Jit::display(std::ostream & os) const { - os << ""; + MachPipeline::display(std::ostream & os) const { + os << ""; } std::string - Jit::display_string() const { + MachPipeline::display_string() const { return tostr(*this); } } /*namespace jit*/ } /*namespace xo*/ -/* end Jit.cpp */ +/* end MachPipeline.cpp */ From 6d7de854da4a6e1a2afa23e28b489e36440dbcef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:00:57 -0400 Subject: [PATCH 027/102] xo-jit: refactor: KaleidoscopeJIT -> Jit + ancillary renames --- .../xo/jit/{KaleidoscopeJit.hpp => Jit.hpp} | 54 +++++++++---------- include/xo/jit/MachPipeline.hpp | 7 +-- src/jit/MachPipeline.cpp | 4 +- 3 files changed, 31 insertions(+), 34 deletions(-) rename include/xo/jit/{KaleidoscopeJit.hpp => Jit.hpp} (76%) diff --git a/include/xo/jit/KaleidoscopeJit.hpp b/include/xo/jit/Jit.hpp similarity index 76% rename from include/xo/jit/KaleidoscopeJit.hpp rename to include/xo/jit/Jit.hpp index f3d2ebcd..fa188245 100644 --- a/include/xo/jit/KaleidoscopeJit.hpp +++ b/include/xo/jit/Jit.hpp @@ -1,14 +1,6 @@ -//===- KaleidoscopeJIT.h - A simple JIT for Kaleidoscope --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Contains a simple JIT definition for use in the kaleidoscope tutorials. -// -//===----------------------------------------------------------------------===// +/** @file Jit.hpp **/ + +/** Adapted from LLVM KaleidoscopeJIT.h **/ #pragma once @@ -30,7 +22,7 @@ namespace xo { namespace jit { - class KaleidoscopeJIT { + class Jit { private: using StringRef = llvm::StringRef; using SectionMemoryManager = llvm::SectionMemoryManager; @@ -61,14 +53,16 @@ namespace xo { * (? specialized for jit in running process ?) **/ RTDyldObjectLinkingLayer object_layer_; + /** compilation layer (sits above linking layer) **/ IRCompileLayer compile_layer_; - JITDylib &MainJD; + /** destination library **/ + JITDylib & dest_dynamic_lib_; //MainJD; public: - KaleidoscopeJIT(std::unique_ptr xsession, - JITTargetMachineBuilder jtmb, - DataLayout data_layout_) + Jit(std::unique_ptr xsession, + JITTargetMachineBuilder jtmb, + DataLayout data_layout_) : xsession_{std::move(xsession)}, data_layout_(std::move(data_layout_)), mangler_(*this->xsession_, this->data_layout_), @@ -76,9 +70,9 @@ namespace xo { []() { return std::make_unique(); }), compile_layer_(*this->xsession_, object_layer_, std::make_unique(std::move(jtmb))), - MainJD(this->xsession_->createBareJITDylib("
")) + dest_dynamic_lib_(this->xsession_->createBareJITDylib("
")) { - MainJD.addGenerator( + dest_dynamic_lib_.addGenerator( cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( data_layout_.getGlobalPrefix()))); if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { @@ -87,12 +81,12 @@ namespace xo { } } - ~KaleidoscopeJIT() { + ~Jit() { if (auto Err = this->xsession_->endSession()) this->xsession_->reportError(std::move(Err)); } - static llvm::Expected> Create() { + static llvm::Expected> Create() { auto EPC = SelfExecutorProcessControl::Create(); if (!EPC) return EPC.takeError(); @@ -106,9 +100,9 @@ namespace xo { if (!data_layout) return data_layout.takeError(); - return std::make_unique(std::move(xsession), - std::move(jtmb), - std::move(*data_layout)); + return std::make_unique(std::move(xsession), + std::move(jtmb), + std::move(*data_layout)); } const std::string & target_triple() const { @@ -117,19 +111,19 @@ namespace xo { const DataLayout & data_layout() const { return data_layout_; } - JITDylib &getMainJITDylib() { return MainJD; } + JITDylib &getMainJITDylib() { return dest_dynamic_lib_; } llvm::Error addModule(ThreadSafeModule ts_module, ResourceTrackerSP RT = nullptr) { if (!RT) - RT = MainJD.getDefaultResourceTracker(); + RT = dest_dynamic_lib_.getDefaultResourceTracker(); return compile_layer_.add(RT, std::move(ts_module)); } llvm::Expected lookup(StringRef name) { - return this->xsession_->lookup({&MainJD}, + return this->xsession_->lookup({&dest_dynamic_lib_}, this->mangler_(name.str())); } @@ -137,7 +131,9 @@ namespace xo { void dump_execution_session() { this->xsession_->dump(llvm::errs()); } - }; + }; /*Jit*/ - } // end namespace jit -} // end namespace xo + } /*namespace jit&*/ +} /*namespace xo*/ + +/** end Jit.hpp **/ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 71df826f..834390c3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -10,6 +10,8 @@ #include "xo/refcnt/Refcounted.hpp" #include "IrPipeline.hpp" #include "LlvmContext.hpp" +#include "Jit.hpp" + #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" #include "xo/expression/PrimitiveInterface.hpp" @@ -17,7 +19,6 @@ #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" -#include "KaleidoscopeJit.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -96,7 +97,7 @@ namespace xo { virtual std::string display_string() const; private: - MachPipeline(std::unique_ptr kal_jit); + MachPipeline(std::unique_ptr jit); /* iniitialize native builder (i.e. for platform we're running on) */ static void init_once(); @@ -107,7 +108,7 @@ namespace xo { private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- - std::unique_ptr kal_jit_; + std::unique_ptr kal_jit_; // ----- this part adapted from kaleidoscope.cpp ----- diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 4acbbf3c..54ab95f8 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -45,7 +45,7 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr kal_jit = llvm_exit_on_err(KaleidoscopeJIT::Create()); + std::unique_ptr kal_jit = llvm_exit_on_err(Jit::Create()); return std::unique_ptr(new MachPipeline(std::move(kal_jit) )); @@ -60,7 +60,7 @@ namespace xo { return jit.release(); } /*make*/ - MachPipeline::MachPipeline(std::unique_ptr kal_jit) + MachPipeline::MachPipeline(std::unique_ptr kal_jit) : kal_jit_{std::move(kal_jit)} { this->recreate_llvm_ir_pipeline(); From fb3ccff6170c56b5efbe47fe33b7e022e47258bc Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:02:26 -0400 Subject: [PATCH 028/102] xo-jit: refactor: Jit.getMainJITDyLib -> dest_dynamic_lib_ref() --- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index fa188245..83b62c29 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -111,7 +111,7 @@ namespace xo { const DataLayout & data_layout() const { return data_layout_; } - JITDylib &getMainJITDylib() { return dest_dynamic_lib_; } + JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } llvm::Error addModule(ThreadSafeModule ts_module, diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 54ab95f8..235a8afb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -401,7 +401,7 @@ namespace xo { { static llvm::ExitOnError llvm_exit_on_err; - auto tracker = kal_jit_->getMainJITDylib().createResourceTracker(); + auto tracker = kal_jit_->dest_dynamic_lib_ref().createResourceTracker(); /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create * From b6ece858568a941cab411f6d9d609528378fb94f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:06:28 -0400 Subject: [PATCH 029/102] xo-jit: refactor: MachPipeline.kal_jit -> jit --- include/xo/jit/MachPipeline.hpp | 7 +++++-- src/jit/MachPipeline.cpp | 16 ++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 834390c3..4ffc029c 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -108,7 +108,10 @@ namespace xo { private: // ----- this part adapted from LLVM 19.0 KaleidoscopeJIT.hpp [wip] ----- - std::unique_ptr kal_jit_; + /** just-in-time compiler -- construct machine code that can + * be invoked from this running process + **/ + std::unique_ptr jit_; // ----- this part adapted from kaleidoscope.cpp ----- @@ -116,7 +119,7 @@ namespace xo { * that takes expressions, and turns them into llvm IR. * * llvm IR can be added to running JIT by calling - * kal_jit_.addModule() + * jit_->addModule() * Note that this makes the module itself unavailable to us **/ xo::ref::rp ir_pipeline_; diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 235a8afb..f912a394 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -60,8 +60,8 @@ namespace xo { return jit.release(); } /*make*/ - MachPipeline::MachPipeline(std::unique_ptr kal_jit) - : kal_jit_{std::move(kal_jit)} + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} { this->recreate_llvm_ir_pipeline(); } @@ -74,7 +74,7 @@ namespace xo { llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); - llvm_module_->setDataLayout(kal_jit_->data_layout()); + llvm_module_->setDataLayout(this->jit_->data_layout()); if (!llvm_cx_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); @@ -97,7 +97,7 @@ namespace xo { // although this getter is defined, seems to be empty in practice //return llvm_module_->getTargetTriple(); - return kal_jit_->target_triple(); + return this->jit_->target_triple(); } std::vector @@ -111,7 +111,7 @@ namespace xo { void MachPipeline::dump_execution_session() { - kal_jit_->dump_execution_session(); + this->jit_->dump_execution_session(); } llvm::Value * @@ -401,7 +401,7 @@ namespace xo { { static llvm::ExitOnError llvm_exit_on_err; - auto tracker = kal_jit_->dest_dynamic_lib_ref().createResourceTracker(); + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create * @@ -413,7 +413,7 @@ namespace xo { /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ this->llvm_cx_ = nullptr; - llvm_exit_on_err(kal_jit_->addModule(std::move(ts_module), tracker)); + llvm_exit_on_err(this->jit_->addModule(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); } @@ -424,7 +424,7 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym = llvm_exit_on_err(kal_jit_->lookup(sym)); + auto llvm_sym = llvm_exit_on_err(this->jit_->lookup(sym)); /* llvm_addr: ExecutorAddr */ auto llvm_addr = llvm_sym.getAddress(); From 9bcb86e8bcd6d1a63df96ac31fa0c312518f973f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:10:17 -0400 Subject: [PATCH 030/102] xo-jit: refactor: Jit.addModule() -> add_llvm_module() --- include/xo/jit/Jit.hpp | 14 +++++++++----- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 83b62c29..feb78652 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -113,13 +113,17 @@ namespace xo { JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } + /** compile module to machine code that's runnable from this process; + * incorporate into @ref dest_dynamic_lib_ + **/ llvm::Error - addModule(ThreadSafeModule ts_module, - ResourceTrackerSP RT = nullptr) { - if (!RT) - RT = dest_dynamic_lib_.getDefaultResourceTracker(); + add_llvm_module(ThreadSafeModule ts_module, + ResourceTrackerSP rtracker = nullptr) { + if (!rtracker) + rtracker = dest_dynamic_lib_.getDefaultResourceTracker(); - return compile_layer_.add(RT, std::move(ts_module)); + return compile_layer_.add(rtracker, + std::move(ts_module)); } llvm::Expected lookup(StringRef name) { diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index f912a394..e02e7316 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -413,7 +413,7 @@ namespace xo { /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ this->llvm_cx_ = nullptr; - llvm_exit_on_err(this->jit_->addModule(std::move(ts_module), tracker)); + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); } From 596ecbdf66c80b4bace694c35e36d9558a85c5a7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:14:51 -0400 Subject: [PATCH 031/102] xo-jit: refactor: cosmetic --- include/xo/jit/MachPipeline.hpp | 5 ++--- src/jit/MachPipeline.cpp | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 4ffc029c..8deede36 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -131,10 +131,9 @@ namespace xo { * each with its own LLVMContext **/ ref::rp llvm_cx_; - //std::unique_ptr llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_ir_builder_; - /** a module (1:1 with library) being prepared by llvm. + /** a module (1:1 with library ?) being prepared by llvm. * IR-level -- does not contain machine code * * - function names are unique within a module. @@ -144,7 +143,7 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; /** map variable names (formal parameters) to - * corresponding llvm interactor + * corresponding llvm IR. * * only supports one level atm (i.e. only top-level functions) **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e02e7316..02b27b00 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -45,9 +45,9 @@ namespace xo { static llvm::ExitOnError llvm_exit_on_err; - std::unique_ptr kal_jit = llvm_exit_on_err(Jit::Create()); + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); - return std::unique_ptr(new MachPipeline(std::move(kal_jit) + return std::unique_ptr(new MachPipeline(std::move(jit) )); } /*make*/ From bfbd097db578119a9aec504bfce3bd18646f185d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 16 Jun 2024 12:16:55 -0400 Subject: [PATCH 032/102] xo-jit: minor tidy (comments) --- include/xo/jit/MachPipeline.hpp | 6 ++++-- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 8deede36..07fd6050 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -91,15 +91,17 @@ namespace xo { **/ void machgen_current_module(); + /** lookup symbol in jit-associated output library **/ llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; virtual std::string display_string() const; private: - MachPipeline(std::unique_ptr jit); + /** construct instance, adopting jit for compilation+execution **/ + explicit MachPipeline(std::unique_ptr jit); - /* iniitialize native builder (i.e. for platform we're running on) */ + /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); /** (re)create pipeline to turn expressions into llvm IR code **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 02b27b00..68d8a127 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -35,7 +35,7 @@ namespace xo { * can instantiate from python * + 'jit object layer' * (realtime dynamic library object linking layer) - * + 'jit_copmile_layer' + * + 'jit_compile_layer' * + 'jit_our_dynamic_lib' */ llvm::Expected> From f7e4433a1dacbab63a459bc936d42a1e7267d7cb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 11:47:17 -0400 Subject: [PATCH 033/102] xo-jit: fix Lambda generation to handle multiple arguments --- src/jit/MachPipeline.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 68d8a127..1619d121 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -155,7 +155,7 @@ namespace xo { /** establish prototype for this function **/ // PLACEHOLDER - // just make prototype for function :: double -> double + // just make prototype for function :: double^n -> double TypeDescr fn_td = expr->value_td(); int n_fn_arg = fn_td->n_fn_arg(); @@ -307,7 +307,8 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(1, llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + std::vector double_v(lambda->n_arg(), + llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), double_v, From 32c74511914a77a29729b6bcacfe3cd2a0810cfd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 12:29:15 -0400 Subject: [PATCH 034/102] xo-jit: + codegen for if-expressions --- include/xo/jit/MachPipeline.hpp | 5 +- src/jit/MachPipeline.cpp | 82 +++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 07fd6050..ca59dfb3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -18,7 +18,7 @@ #include "xo/expression/Apply.hpp" #include "xo/expression/Lambda.hpp" #include "xo/expression/Variable.hpp" - +#include "xo/expression/IfExpr.hpp" /* stuff from kaleidoscope.cpp */ #include "llvm/ADT/APFloat.h" @@ -81,6 +81,7 @@ namespace xo { llvm::Value * codegen_apply(ref::brw expr); llvm::Function * codegen_lambda(ref::brw expr); llvm::Value * codegen_variable(ref::brw var); + llvm::Value * codegen_ifexpr(ref::brw ifexpr); llvm::Value * codegen(ref::brw expr); @@ -117,7 +118,7 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- - /** everything bleow represents a pipeline + /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * * llvm IR can be added to running JIT by calling diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1619d121..18c7245e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -10,6 +10,7 @@ namespace xo { using xo::ast::Lambda; using xo::ast::Variable; using xo::ast::Apply; + using xo::ast::IfExpr; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -370,6 +371,85 @@ namespace xo { return ix->second; } /*codegen_variable*/ + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr) + { + llvm::Value * test_ir = this->codegen(expr->test()); + + /** need test result in a variable + **/ + llvm::Value * test_with_cmp_ir + = llvm_ir_builder_->CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = llvm_ir_builder_->GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + llvm_ir_builder_->CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + llvm_ir_builder_->SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true()); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + llvm_ir_builder_->CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = llvm_ir_builder_->GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + llvm_ir_builder_->SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false()); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + llvm_ir_builder_->CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = llvm_ir_builder_->GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + llvm_ir_builder_->SetInsertPoint(merge_bb); + + llvm::PHINode * phi_node + = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } + llvm::Value * MachPipeline::codegen(ref::brw expr) { @@ -384,6 +464,8 @@ namespace xo { return this->codegen_lambda(Lambda::from(expr)); case exprtype::variable: return this->codegen_variable(Variable::from(expr)); + case exprtype::if_expr: + return this->codegen_ifexpr(IfExpr::from(expr)); case exprtype::invalid: case exprtype::n_expr: return nullptr; From 8d122425a07ce368f835611204162dbd52049bf6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:46:37 -0400 Subject: [PATCH 035/102] xo-jit: track new make_apply interface --- example/ex1/ex1.cpp | 6 +++--- src/jit/MachPipeline.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 238ba4a3..01d6073b 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -104,7 +104,7 @@ main() { auto fn = make_primitive("sqrt", &sqrt); auto arg = make_constant(2.0); - auto call = make_apply(fn, arg); + auto call = make_apply(fn, {arg}); log && log(xtag("expr", call)); @@ -129,8 +129,8 @@ main() { auto cos = make_primitive("cos", ::cos); auto x_var = make_var("x"); - auto call1 = make_apply(cos, x_var); /* (cos x) */ - auto call2 = make_apply(sin, call1); /* (sin (cos x)) */ + 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", diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 18c7245e..7081e181 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -464,7 +464,7 @@ namespace xo { return this->codegen_lambda(Lambda::from(expr)); case exprtype::variable: return this->codegen_variable(Variable::from(expr)); - case exprtype::if_expr: + case exprtype::ifexpr: return this->codegen_ifexpr(IfExpr::from(expr)); case exprtype::invalid: case exprtype::n_expr: From 0759cc0c518219c6c853aba7ec383bb363af7503 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:47:04 -0400 Subject: [PATCH 036/102] xo-jit: bugfix: shortcircuit on unknown variable --- src/jit/MachPipeline.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 7081e181..c41a920e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -366,6 +366,7 @@ namespace xo { cerr << "MachPipeline::codegen_variable: no binding for variable x" << xtag("x", var->name()) << endl; + return nullptr; } return ix->second; From 3298fdf277ca2050597289bf77481ebed1aecacb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 13:48:03 -0400 Subject: [PATCH 037/102] xo-jit: bugfix + debug for codegen_lambda() --- src/jit/MachPipeline.cpp | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index c41a920e..969cfd04 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -291,6 +291,12 @@ namespace xo { llvm::Function * MachPipeline::codegen_lambda(ref::brw lambda) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + /* reminder! this is the *expression*, not the *closure* */ global_env_[lambda->name()] = lambda.get(); @@ -300,6 +306,10 @@ namespace xo { if (fn) { /** function with this name already defined?? **/ + cerr << "MachPipeline::codegen_lambda: function f already defined" + << xtag("f", lambda->name()) + << endl; + return nullptr; } @@ -321,9 +331,15 @@ namespace xo { lambda->name(), llvm_module_.get()); /* also capture argument names */ - int i = 0; - for (auto & arg : fn->args()) - arg.setName(lambda->argv().at(i)); + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)); + ++i; + } + } /* generate function body */ @@ -333,8 +349,15 @@ namespace xo { /* formal parameters need to appear in named_value_map_ */ nested_env_.clear(); - for (auto & arg : fn->args()) - nested_env_[std::string(arg.getName())] = &arg; + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + + nested_env_[std::string(arg.getName())] = &arg; + ++i; + } + } llvm::Value * retval = this->codegen(lambda->body()); From c739e3bd4c755d796e5667004dda458fa3555096 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 17 Jun 2024 17:14:44 -0400 Subject: [PATCH 038/102] xo-jit: + MachPipeline::dump_current_module --- include/xo/jit/MachPipeline.hpp | 3 +++ src/jit/MachPipeline.cpp | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ca59dfb3..84b01a9d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -92,6 +92,9 @@ namespace xo { **/ void machgen_current_module(); + /** dump text description of module contents to console **/ + void dump_current_module(); + /** lookup symbol in jit-associated output library **/ llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 969cfd04..3192e531 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -503,6 +503,14 @@ namespace xo { return nullptr; } /*codegen*/ + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + void MachPipeline::machgen_current_module() { From d43ac46eab14303fe0234abd36dc9ce75ef218b7 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:54:06 -0400 Subject: [PATCH 039/102] xo-jit: example: + reqd type arg to Variable in ex1.cpp --- example/ex1/ex1.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 01d6073b..3929bcfa 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -41,6 +41,7 @@ main() { 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; @@ -128,13 +129,13 @@ main() { auto sin = make_primitive("sin", ::sin); auto cos = make_primitive("cos", ::cos); - auto x_var = make_var("x"); + auto x_var = make_var("x", Reflect::require()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ auto call2 = make_apply(sin, {call1}); /* (sin (cos x)) */ /* (define (lm_1 x) (sin (cos x))) */ auto lambda = make_lambda("lm_1", - {"x"}, + {x_var}, call2); log && log(xtag("expr", lambda)); From 2a4b9a4360a1806eee1bc85653ae9807fb8effc1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 10:54:42 -0400 Subject: [PATCH 040/102] xo-jit: + td_to_llvm_type() + support native int+float values --- include/xo/jit/MachPipeline.hpp | 2 +- src/jit/MachPipeline.cpp | 146 +++++++++++++++++++++++--------- 2 files changed, 108 insertions(+), 40 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 84b01a9d..3f217db3 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -96,7 +96,7 @@ namespace xo { void dump_current_module(); /** lookup symbol in jit-associated output library **/ - llvm::orc::ExecutorAddr lookup_symbol(const std::string & x); + llvm::Expected lookup_symbol(const std::string & x); virtual void display(std::ostream & os) const; virtual std::string display_string() const; diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 3192e531..e1851474 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -6,11 +6,13 @@ namespace xo { using xo::ast::exprtype; using xo::ast::Expression; using xo::ast::ConstantInterface; + using xo::ast::FunctionInterface; using xo::ast::PrimitiveInterface; using xo::ast::Lambda; using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::reflect::Reflect; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -120,17 +122,51 @@ namespace xo { { TypeDescr td = expr->value_td(); - if (td->is_native()) { + if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); - } else if (td->is_native()) { + } else if (Reflect::is_native(td)) { return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); } return nullptr; } + namespace { + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { @@ -158,7 +194,7 @@ namespace xo { // PLACEHOLDER // just make prototype for function :: double^n -> double - TypeDescr fn_td = expr->value_td(); + TypeDescr fn_td = expr->valuetype(); int n_fn_arg = fn_td->n_fn_arg(); scope log(XO_DEBUG(c_debug_flag), @@ -174,18 +210,12 @@ namespace xo { log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); - if (arg_td->is_native()) { - llvm_argtype_v.push_back(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); - // TODO: extend with other native types here... - } else { - cerr << "MachPipeline::codegen_primitive: error: primitive f with arg i of type T where double expected" - << xtag("f", expr->name()) - << xtag("i", i) - << xtag("T", arg_td->short_name()) - << endl; + if (!llvm_argtype) return nullptr; - } + + llvm_argtype_v.push_back(llvm_argtype); } //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); @@ -194,17 +224,10 @@ namespace xo { log && log(xtag("retval_td", retval_td->short_name())); - llvm::Type * llvm_retval = nullptr; + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), retval_td); - if (retval_td->is_native()) { - llvm_retval = llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()); - } else { - cerr << "MachPipeline::codegen_primitive: error: primitive f returning T where double expected" - << xtag("f", expr->name()) - << xtag("T", retval_td->short_name()) - << endl; + if (!llvm_retval) return nullptr; - } auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, llvm_argtype_v, @@ -247,9 +270,30 @@ namespace xo { * * For now, finesse by only handling PrimitiveInterface in function-callee position */ - if (apply->fn()->extype() == exprtype::primitive) { - auto pm = PrimitiveInterface::from(apply->fn()); - auto * fn = this->codegen_primitive(pm); + if (apply->fn()->extype() == exprtype::primitive + || apply->fn()->extype() == exprtype::lambda) + { + llvm::Function * llvm_fn = nullptr; + FunctionInterface * fn = nullptr; + { + // TODO: codgen_function() + + auto pm = PrimitiveInterface::from(apply->fn()); + if (pm) { + fn = pm.get(); + llvm_fn = this->codegen_primitive(pm); + } + + auto lm = Lambda::from(apply->fn()); + if (lm) { + fn = lm.get(); + llvm_fn = this->codegen_lambda(lm); + } + } + + if (!llvm_fn) { + return nullptr; + } #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; @@ -257,15 +301,30 @@ namespace xo { cerr << endl; #endif - if (fn->arg_size() != apply->argv().size()) { + if (llvm_fn->arg_size() != apply->argv().size()) { cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - << xtag("f", pm->name()) - << xtag("n1", pm->n_arg()) + << xtag("f", fn->name()) + << xtag("n1", fn->n_arg()) << xtag("n2", apply->argv().size()) << endl; + return nullptr; } + /** also check argument types **/ + for (size_t i = 0, n = fn->n_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != fn->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee f for arg i seeeing U instead of expected T" + << xtag("f", fn->name()) + << xtag("i", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", fn->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } + std::vector args; args.reserve(apply->argv().size()); @@ -281,7 +340,7 @@ namespace xo { args.push_back(arg); } - return llvm_ir_builder_->CreateCall(fn, args, "calltmp"); + return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); } else { cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; return nullptr; @@ -318,11 +377,16 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - std::vector double_v(lambda->n_arg(), - llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref())); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); - auto * llvm_fn_type = llvm::FunctionType::get(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - double_v, + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); + } + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + arg_type_v, false /*!varargs*/); /* create (initially empty) function */ @@ -336,7 +400,7 @@ namespace xo { for (auto & arg : fn->args()) { log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); - arg.setName(lambda->argv().at(i)); + arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -464,6 +528,7 @@ namespace xo { parent_fn->insert(parent_fn->end(), merge_bb); llvm_ir_builder_->SetInsertPoint(merge_bb); + /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), 2 /*#of branches being merged (?)*/, @@ -533,18 +598,21 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } - llvm::orc::ExecutorAddr + llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) { static llvm::ExitOnError llvm_exit_on_err; /* llvm_sym: ExecutorSymbolDef */ - auto llvm_sym = llvm_exit_on_err(this->jit_->lookup(sym)); + auto llvm_sym_expected = this->jit_->lookup(sym); - /* llvm_addr: ExecutorAddr */ - auto llvm_addr = llvm_sym.getAddress(); + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); - return llvm_addr; + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } } /*lookup_symbol*/ void From 1f02ec02effd0ad69fee0b1fdc9d22e372122928 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:18:15 -0400 Subject: [PATCH 041/102] xo-pyexpression: experiment: try moving xo intrinsics into xo-jit --- include/xo/jit/intrinsics.hpp | 13 +++++++++++++ src/jit/intrinsics.cpp | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 include/xo/jit/intrinsics.hpp create mode 100644 src/jit/intrinsics.cpp diff --git a/include/xo/jit/intrinsics.hpp b/include/xo/jit/intrinsics.hpp new file mode 100644 index 00000000..a0528dc2 --- /dev/null +++ b/include/xo/jit/intrinsics.hpp @@ -0,0 +1,13 @@ +/** @file intrinsics.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include + +extern "C" int32_t mul_i32(int32_t x, int32_t y); +extern "C" double mul_f64(double x, double y); + +/** end intrinsics.hpp **/ diff --git a/src/jit/intrinsics.cpp b/src/jit/intrinsics.cpp new file mode 100644 index 00000000..2abdf7d8 --- /dev/null +++ b/src/jit/intrinsics.cpp @@ -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 */ From d59328ee1f99a6d699c94bc069e71463f24a1ca8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:02 -0400 Subject: [PATCH 042/102] xo-jit: build: + intrinsics --- src/jit/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 88b9d2da..aa0a3deb 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -5,6 +5,7 @@ set(SELF_SRCS LlvmContext.cpp IrPipeline.cpp MachPipeline.cpp + intrinsics.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) From c9a061abf0a283123046051e627afa72769d1b31 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:25 -0400 Subject: [PATCH 043/102] xo-jit: supply explicit_symbol_def to make_primitive() --- example/ex1/ex1.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 3929bcfa..6f5a7c35 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -81,7 +81,8 @@ main() { } { - auto expr = make_primitive("sqrt", &sqrt); + auto expr = make_primitive("sqrt", &sqrt, + false /*!explicit_symbol_def*/); log && log(xtag("expr", expr)); @@ -102,7 +103,8 @@ main() { { /* (sqrt 2) */ - auto fn = make_primitive("sqrt", &sqrt); + auto fn = make_primitive("sqrt", &sqrt, + false /*!explicit_symbol_def*/); auto arg = make_constant(2.0); auto call = make_apply(fn, {arg}); @@ -126,8 +128,10 @@ main() { { /* (lambda (x) (sin (cos x))) */ - auto sin = make_primitive("sin", ::sin); - auto cos = make_primitive("cos", ::cos); + auto sin = make_primitive("sin", ::sin, + false /*!explicit_symbol_def*/); + auto cos = make_primitive("cos", ::cos, + false /*!explicit_symbol_def*/); auto x_var = make_var("x", Reflect::require()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ From 2e99cc7c31025aa74456d9570b56731b0546b8c3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:19:59 -0400 Subject: [PATCH 044/102] xo-jit: minor: fix convention (stray _ prefix on format param) --- include/xo/jit/Jit.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index feb78652..1847084a 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -62,9 +62,9 @@ namespace xo { public: Jit(std::unique_ptr xsession, JITTargetMachineBuilder jtmb, - DataLayout data_layout_) + DataLayout data_layout) : xsession_{std::move(xsession)}, - data_layout_(std::move(data_layout_)), + data_layout_(std::move(data_layout)), mangler_(*this->xsession_, this->data_layout_), object_layer_(*this->xsession_, []() { return std::make_unique(); }), From 97aea9d513cecd801b8c620c66e65220dbaaef88 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:20:53 -0400 Subject: [PATCH 045/102] xo-jit: + Jit::mangle() --- include/xo/jit/Jit.hpp | 7 ++++++- include/xo/jit/MachPipeline.hpp | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 1847084a..6a160323 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -126,9 +126,14 @@ namespace xo { std::move(ts_module)); } + /** report mangled symbol name **/ + auto mangle(StringRef name) { + return this->mangler_(name.str()); + } + llvm::Expected lookup(StringRef name) { return this->xsession_->lookup({&dest_dynamic_lib_}, - this->mangler_(name.str())); + this->mangle(name)); } /* dump */ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 3f217db3..967e3127 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -95,6 +95,9 @@ namespace xo { /** dump text description of module contents to console **/ void dump_current_module(); + /** report mangle symbol **/ + std::string mangle(const std::string & x) const; + /** lookup symbol in jit-associated output library **/ llvm::Expected lookup_symbol(const std::string & x); From 3f441d8ba6fa4f5ded22b435d204912aa465390d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:21:18 -0400 Subject: [PATCH 046/102] xo-jit: cosmetic: code layout/comments --- src/jit/MachPipeline.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e1851474..b97f9129 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -137,7 +137,7 @@ namespace xo { } return nullptr; - } + } /*codegen_constant*/ namespace { llvm::Type * @@ -204,11 +204,12 @@ namespace xo { std::vector llvm_argtype_v; llvm_argtype_v.reserve(n_fn_arg); - /** check function args are all doubles **/ + /** check function args are all known **/ for (int i = 0; i < n_fn_arg; ++i) { TypeDescr arg_td = fn_td->fn_arg(i); - log && log(xtag("i_arg", i), xtag("arg_td", arg_td->short_name())); + log && log(xtag("i_arg", i), + xtag("arg_td", arg_td->short_name())); llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); From 787d0b69e2338c4b8468cba4c52781b1e9989e04 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:21:46 -0400 Subject: [PATCH 047/102] xo-jit: + MachPipeline::mangle() --- src/jit/MachPipeline.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index b97f9129..34578cf4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -598,6 +598,18 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } + std::string + MachPipeline::mangle(const std::string & sym) const + { + auto p = this->jit_->mangle(sym); + + if (p) + return (*p).str(); + + throw std::runtime_error(tostr("MachPipeline::mangle" + ": mangle(sym) returned empty pointer", + xtag("sym", sym))); + } /*mangle*/ llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) From 69ee09fa59222a960b3ee18c21198288e42a15f8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:08 -0400 Subject: [PATCH 048/102] xo-jit: tidy: drop unused temporary --- src/jit/MachPipeline.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 34578cf4..de881aca 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -614,8 +614,6 @@ namespace xo { llvm::Expected MachPipeline::lookup_symbol(const std::string & sym) { - static llvm::ExitOnError llvm_exit_on_err; - /* llvm_sym: ExecutorSymbolDef */ auto llvm_sym_expected = this->jit_->lookup(sym); From 39a8e8aad4078ea7d5f011e870d91b010de801ef Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:24 -0400 Subject: [PATCH 049/102] xo-jit: cosmetic: code layout / comments --- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 6a160323..8cf15af6 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -142,7 +142,7 @@ namespace xo { } }; /*Jit*/ - } /*namespace jit&*/ + } /*namespace jit*/ } /*namespace xo*/ /** end Jit.hpp **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index de881aca..d72de5a1 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -465,8 +465,7 @@ namespace xo { { llvm::Value * test_ir = this->codegen(expr->test()); - /** need test result in a variable - **/ + /** need test result in a variable **/ llvm::Value * test_with_cmp_ir = llvm_ir_builder_->CreateFCmpONE(test_ir, llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), @@ -538,7 +537,7 @@ namespace xo { phi_node->addIncoming(when_false_ir, when_false_bb); return phi_node; - } + } /*codegen_ifexpr*/ llvm::Value * MachPipeline::codegen(ref::brw expr) @@ -597,7 +596,8 @@ namespace xo { llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); this->recreate_llvm_ir_pipeline(); - } + } /*machgen_current_module*/ + std::string MachPipeline::mangle(const std::string & sym) const { From ba39b6366d2dc6e00adba98bf93ad6156c711c4f Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:22:53 -0400 Subject: [PATCH 050/102] xo-jit: + Jit::intern_symbol() --- include/xo/jit/Jit.hpp | 13 +++++++++++++ src/jit/MachPipeline.cpp | 1 + 2 files changed, 14 insertions(+) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 8cf15af6..435a4bc6 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -126,6 +126,19 @@ namespace xo { std::move(ts_module)); } + /** intern @p symbol, binding it to address @p dest **/ + template + llvm::Error intern_symbol(const std::string & symbol, T * dest) { + llvm::orc::SymbolMap symbol_map; + symbol_map[mangler_(symbol)] + = 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 **/ auto mangle(StringRef name) { return this->mangler_(name.str()); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index d72de5a1..162c113d 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,7 @@ /* @file MachPipeline.cpp */ #include "MachPipeline.hpp" +#include namespace xo { using xo::ast::exprtype; From c09f1f46df17346e0aed45240e20e94724cc47b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:23:23 -0400 Subject: [PATCH 051/102] xo-jit: in codegen_primitive() honor explicit_symbol_def flag --- src/jit/MachPipeline.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 162c113d..0baf2c3e 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -254,6 +254,25 @@ namespace xo { } #endif + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + llvm_exit_on_err(this->jit_->intern_symbol(expr->name(), + expr->function_address())); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } + log && log("returning llvm function"); return fn; From 0273a8b8dfe8795f4976055cd27f42d36dbba518 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 19 Jun 2024 18:24:06 -0400 Subject: [PATCH 052/102] xo-jit: cosmetic: code layout --- include/xo/jit/Jit.hpp | 7 ++++--- src/jit/Jit.cpp | 10 ++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 src/jit/Jit.cpp diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 435a4bc6..5af092d0 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -72,9 +72,10 @@ namespace xo { std::make_unique(std::move(jtmb))), dest_dynamic_lib_(this->xsession_->createBareJITDylib("
")) { - dest_dynamic_lib_.addGenerator( - cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess( - data_layout_.getGlobalPrefix()))); + dest_dynamic_lib_.addGenerator + (cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess + (data_layout_.getGlobalPrefix()))); + if (jtmb.getTargetTriple().isOSBinFormatCOFF()) { object_layer_.setOverrideObjectFlagsWithResponsibilityFlags(true); object_layer_.setAutoClaimResponsibilityForObjectSymbols(true); diff --git a/src/jit/Jit.cpp b/src/jit/Jit.cpp new file mode 100644 index 00000000..8f915406 --- /dev/null +++ b/src/jit/Jit.cpp @@ -0,0 +1,10 @@ +/* @file Jit.cpp */ + +#include "Jit.hpp" + +namespace xo { + namespace jit { + } /*namespace jit*/ +} /*namespace xo*/ + +/* end Jit.cpp */ From 52ec4f09a586bd110d1628b0a3e30159f3a249d1 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:07:43 -0400 Subject: [PATCH 053/102] xo-jit: supply llvmintrinsic to primitives --- example/ex1/ex1.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 6f5a7c35..0214a265 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -38,6 +38,7 @@ main() { 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; @@ -82,7 +83,8 @@ main() { { auto expr = make_primitive("sqrt", &sqrt, - false /*!explicit_symbol_def*/); + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); log && log(xtag("expr", expr)); @@ -104,7 +106,8 @@ main() { /* (sqrt 2) */ auto fn = make_primitive("sqrt", &sqrt, - false /*!explicit_symbol_def*/); + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); auto arg = make_constant(2.0); auto call = make_apply(fn, {arg}); @@ -128,10 +131,14 @@ main() { { /* (lambda (x) (sin (cos x))) */ - auto sin = make_primitive("sin", ::sin, - false /*!explicit_symbol_def*/); - auto cos = make_primitive("cos", ::cos, - false /*!explicit_symbol_def*/); + 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()); auto call1 = make_apply(cos, {x_var}); /* (cos x) */ From 88cc8885b64545566a57bb4dbb35259682acff42 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:08:07 -0400 Subject: [PATCH 054/102] xo-jit: in MachPipeline use intrinsic for faster code path --- src/jit/MachPipeline.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 0baf2c3e..d0abc7c6 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -13,6 +13,7 @@ namespace xo { using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; using xo::reflect::Reflect; using xo::reflect::TypeDescr; using std::cerr; @@ -295,6 +296,7 @@ namespace xo { || apply->fn()->extype() == exprtype::lambda) { llvm::Function * llvm_fn = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; FunctionInterface * fn = nullptr; { // TODO: codgen_function() @@ -303,6 +305,8 @@ namespace xo { if (pm) { fn = pm.get(); llvm_fn = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); } auto lm = Lambda::from(apply->fn()); @@ -361,6 +365,25 @@ namespace xo { args.push_back(arg); } + switch(intrinsic) { + case llvmintrinsic::i_add: + return llvm_ir_builder_->CreateAdd(args[0], args[1]); + case llvmintrinsic::i_mul: + return llvm_ir_builder_->CreateMul(args[0], args[1]); + case llvmintrinsic::fp_add: + return llvm_ir_builder_->CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return llvm_ir_builder_->CreateFMul(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); } else { cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; From ea681a65eaa1b8716686a1dc6a8bb0975b02a977 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 14:08:45 -0400 Subject: [PATCH 055/102] xo-jit: doc: + HOWTO --- HOWTO | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 HOWTO diff --git a/HOWTO b/HOWTO new file mode 100644 index 00000000..6965105f --- /dev/null +++ b/HOWTO @@ -0,0 +1,45 @@ +* 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 From e671686a3a0c65208ae01f31679d448a3a32b9e6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:00:06 -0400 Subject: [PATCH 056/102] xo-jit: refactor MachPipeline to use stack for lambda formals --- include/xo/jit/MachPipeline.hpp | 18 ++++++++++++- src/jit/MachPipeline.cpp | 46 +++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 967e3127..75244b93 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -54,6 +54,7 @@ namespace xo { class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; + using TypeDescr = xo::reflect::TypeDescr; //using ConstantInterface = xo::ast::ConstantInterface; public: @@ -111,6 +112,16 @@ namespace xo { /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); + /** 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); + /** (re)create pipeline to turn expressions into llvm IR code **/ void recreate_llvm_ir_pipeline(); @@ -155,8 +166,13 @@ namespace xo { * 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::map nested_env_; + std::map nested_env_; /* <-> kaleidoscope NamedValues */ }; /*MachPipeline*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index d0abc7c6..c1c65b37 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -391,6 +391,25 @@ namespace xo { } } /*codegen_apply*/ + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + if (!llvm_var_type) + return nullptr; + + return tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + } /*create_entry_block_alloca*/ + llvm::Function * MachPipeline::codegen_lambda(ref::brw lambda) { @@ -462,7 +481,25 @@ namespace xo { for (auto & arg : fn->args()) { log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); - nested_env_[std::string(arg.getName())] = &arg; + /* stack location for arg[i] */ + llvm::AllocaInst * alloca + = create_entry_block_alloca(fn, + std::string(arg.getName()), + lambda->fn_arg(i)); + + if (!alloca) + return nullptr; + + /* store on function entry + * see codegen_variable() for corresponding load + */ + this->llvm_ir_builder_->CreateStore(&arg, alloca); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + nested_env_[std::string(arg.getName())] = alloca; ++i; } } @@ -500,7 +537,12 @@ namespace xo { return nullptr; } - return ix->second; + llvm::AllocaInst * alloca = ix->second; + + /* code to load value from stack */ + return this->llvm_ir_builder_->CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); } /*codegen_variable*/ llvm::Value * From f1de52b96200abdf1c3a06a469e8ebcb25d43c64 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:00:59 -0400 Subject: [PATCH 057/102] xo-jit: cosmetic: code layout --- src/jit/MachPipeline.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index c1c65b37..1720b545 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -440,12 +440,14 @@ namespace xo { // PLACEHOLDER // just handle double arguments + return type for now - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); std::vector arg_type_v(lambda->n_arg()); for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); } auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, @@ -461,7 +463,9 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm format param names", xtag("i", i), xtag("param", lambda->argv().at(i))); + log && log("llvm format param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); arg.setName(lambda->argv().at(i)->name()); ++i; @@ -479,7 +483,9 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + log && log("nested environment", + xtag("i", i), + xtag("param", std::string(arg.getName()))); /* stack location for arg[i] */ llvm::AllocaInst * alloca From 6abede9c335c46fe1d9bce5b6d2a7fbee97b5a63 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:01:11 -0400 Subject: [PATCH 058/102] xo-jit: print IR before- and after- optimization --- src/jit/MachPipeline.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1720b545..293f8625 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -519,9 +519,25 @@ namespace xo { /* validate! always validate! */ llvm::verifyFunction(*fn); + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + /* optimize! improves IR */ ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + return fn; } From 2235bba87286d55ebcef4030f4747c43236f7ef4 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 21 Jun 2024 17:01:30 -0400 Subject: [PATCH 059/102] xo-jit: add mem-to-register pass to IrPipeline --- include/xo/jit/IrPipeline.hpp | 1 + src/jit/IrPipeline.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 7efd5b4f..60473bc5 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -28,6 +28,7 @@ #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" diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index d08d9639..285edbe3 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -24,6 +24,13 @@ namespace xo { /** 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()); From e246f12d705935fdba1abf34d7fad96f77166ba6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:02:47 -0400 Subject: [PATCH 060/102] xo-jit: refactor to support function pointer arguments. --- example/ex1/ex1.cpp | 8 +- include/xo/jit/MachPipeline.hpp | 40 +- include/xo/jit/activation_record.hpp | 38 ++ src/jit/CMakeLists.txt | 1 + src/jit/MachPipeline.cpp | 614 ++++++++++++++++++--------- src/jit/activation_record.cpp | 45 ++ 6 files changed, 522 insertions(+), 224 deletions(-) create mode 100644 include/xo/jit/activation_record.hpp create mode 100644 src/jit/activation_record.cpp diff --git a/example/ex1/ex1.cpp b/example/ex1/ex1.cpp index 0214a265..22e1b0b7 100644 --- a/example/ex1/ex1.cpp +++ b/example/ex1/ex1.cpp @@ -67,7 +67,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -88,7 +88,7 @@ main() { log && log(xtag("expr", expr)); - auto llvm_ircode = jit->codegen(expr); + auto llvm_ircode = jit->codegen_toplevel(expr); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -114,7 +114,7 @@ main() { log && log(xtag("expr", call)); - auto llvm_ircode = jit->codegen(call); + auto llvm_ircode = jit->codegen_toplevel(call); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ @@ -151,7 +151,7 @@ main() { log && log(xtag("expr", lambda)); - auto llvm_ircode = jit->codegen(lambda); + auto llvm_ircode = jit->codegen_toplevel(lambda); if (llvm_ircode) { /* note: llvm:errs() is 'raw stderr stream' */ diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 75244b93..2497fb5f 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -11,6 +11,7 @@ #include "IrPipeline.hpp" #include "LlvmContext.hpp" #include "Jit.hpp" +#include "activation_record.hpp" #include "xo/expression/Expression.hpp" #include "xo/expression/ConstantInterface.hpp" @@ -54,6 +55,7 @@ namespace xo { class MachPipeline : public ref::Refcount { public: using Expression = xo::ast::Expression; + using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; //using ConstantInterface = xo::ast::ConstantInterface; @@ -64,6 +66,10 @@ namespace xo { // ----- module access ----- + llvm::Module * current_module() { return llvm_module_.get(); } + ref::brw 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; /** append function names defined in attached module to *p_v @@ -75,16 +81,22 @@ namespace xo { /** write state of execution session (all the associated dynamic libraries) **/ void dump_execution_session(); - // ----- jit code generation ----- + // ----- code generation ----- llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); - llvm::Value * codegen_apply(ref::brw expr); - llvm::Function * codegen_lambda(ref::brw expr); - llvm::Value * codegen_variable(ref::brw var); - llvm::Value * codegen_ifexpr(ref::brw ifexpr); + llvm::Value * codegen_apply(ref::brw expr, 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 expr); + llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr); + llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_toplevel(ref::brw expr); // ----- jit online execution ----- @@ -112,6 +124,10 @@ namespace xo { /** iniitialize native builder (i.e. for platform we're running on) **/ static void init_once(); + /** helper function. find all lambda expressions in AST @p expr **/ + std::vector> find_lambdas(ref::brw expr) const; + + public: /** 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 @@ -122,6 +138,7 @@ namespace xo { const std::string & var_name, TypeDescr var_type); + private: /** (re)create pipeline to turn expressions into llvm IR code **/ void recreate_llvm_ir_pipeline(); @@ -135,6 +152,7 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- + public: /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * @@ -144,6 +162,7 @@ namespace xo { **/ xo::ref::rp ir_pipeline_; + private: /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -151,8 +170,10 @@ namespace xo { * each with its own LLVMContext **/ ref::rp llvm_cx_; + /** builder for intermediate-representation objects **/ - std::unique_ptr> llvm_ir_builder_; + std::unique_ptr> llvm_toplevel_ir_builder_; + /** a module (1:1 with library ?) being prepared by llvm. * IR-level -- does not contain machine code * @@ -162,6 +183,8 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; + + public: /** map variable names (formal parameters) to * corresponding llvm IR. * @@ -172,8 +195,7 @@ namespace xo { * * rhs identifies logical stack location of a variable **/ - std::map nested_env_; /* <-> kaleidoscope NamedValues */ - + std::stack env_stack_; /* <-> kaleidoscope NamedValues */ }; /*MachPipeline*/ inline std::ostream & diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp new file mode 100644 index 00000000..eb98975e --- /dev/null +++ b/include/xo/jit/activation_record.hpp @@ -0,0 +1,38 @@ +/** @file activation_record.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include +#include +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record() = default; + + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); + + private: + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index aa0a3deb..fe0b9e3d 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -6,6 +6,7 @@ set(SELF_SRCS IrPipeline.cpp MachPipeline.cpp intrinsics.cpp + activation_record.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 293f8625..f19d92b1 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -76,7 +76,7 @@ namespace xo { { //llvm_cx_ = std::make_unique(); llvm_cx_ = LlvmContext::make(); - llvm_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); llvm_module_->setDataLayout(this->jit_->data_layout()); @@ -84,7 +84,7 @@ namespace xo { if (!llvm_cx_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); } - if (!llvm_ir_builder_.get()) { + if (!llvm_toplevel_ir_builder_.get()) { throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); } if (!llvm_module_.get()) { @@ -142,11 +142,71 @@ namespace xo { } /*codegen_constant*/ namespace { + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - if (Reflect::is_native(td)) { + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { return llvm::Type::getInt8Ty(llvm_cx_ref); @@ -172,7 +232,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { - constexpr bool c_debug_flag = true; + //constexpr bool c_debug_flag = true; using xo::scope; /** note: documentation (such as it is) for llvm::Function here: @@ -193,49 +253,14 @@ namespace xo { /** establish prototype for this function **/ - // PLACEHOLDER - // just make prototype for function :: double^n -> double - TypeDescr fn_td = expr->valuetype(); - int n_fn_arg = fn_td->n_fn_arg(); - scope log(XO_DEBUG(c_debug_flag), - xtag("fn_td", fn_td->short_name()), - xtag("n_fn_arg", n_fn_arg)); + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - log && log(xtag("i_arg", i), - xtag("arg_td", arg_td->short_name())); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx_.borrow(), arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - //std::vector double_v(n_fn_arg, llvm::Type::getDoubleTy(*llvm_cx_)); - - TypeDescr retval_td = fn_td->fn_retval(); - - log && log(xtag("retval_td", retval_td->short_name())); - - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), retval_td); - - if (!llvm_retval) + if (!llvm_fn_type) return nullptr; - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, expr->name(), @@ -274,121 +299,158 @@ namespace xo { #endif } +#ifdef OBSOLETE log && log("returning llvm function"); +#endif return fn; } /*codegen_primitive*/ llvm::Value * - MachPipeline::codegen_apply(ref::brw apply) + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + using std::cerr; using std::endl; - /* editorial: - * - * to handle (computed functions) properly, - * we will need a runtime representation for a 'primitive function pointer' - * - * For now, finesse by only handling PrimitiveInterface in function-callee position + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. */ - if (apply->fn()->extype() == exprtype::primitive - || apply->fn()->extype() == exprtype::lambda) + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); { - llvm::Function * llvm_fn = nullptr; - llvmintrinsic intrinsic = llvmintrinsic::invalid; - FunctionInterface * fn = nullptr; - { - // TODO: codgen_function() - + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { auto pm = PrimitiveInterface::from(apply->fn()); + if (pm) { - fn = pm.get(); - llvm_fn = this->codegen_primitive(pm); + llvm_fnval = this->codegen_primitive(pm); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); - auto lm = Lambda::from(apply->fn()); - if (lm) { - fn = lm.get(); - llvm_fn = this->codegen_lambda(lm); - } + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ } + } - if (!llvm_fn) { - return nullptr; - } - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif - - if (llvm_fn->arg_size() != apply->argv().size()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - << xtag("f", fn->name()) - << xtag("n1", fn->n_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = fn->n_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != fn->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee f for arg i seeeing U instead of expected T" - << xtag("f", fn->name()) - << xtag("i", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", fn->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } - - std::vector args; - args.reserve(apply->argv().size()); - - for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr); - -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: arg:" << endl; - arg->print(llvm::errs()); - cerr << endl; -#endif - - args.push_back(arg); - } - - switch(intrinsic) { - case llvmintrinsic::i_add: - return llvm_ir_builder_->CreateAdd(args[0], args[1]); - case llvmintrinsic::i_mul: - return llvm_ir_builder_->CreateMul(args[0], args[1]); - case llvmintrinsic::fp_add: - return llvm_ir_builder_->CreateFAdd(args[0], args[1]); - case llvmintrinsic::fp_mul: - return llvm_ir_builder_->CreateFMul(args[0], args[1]); - case llvmintrinsic::invalid: - case llvmintrinsic::fp_sqrt: - case llvmintrinsic::fp_pow: - case llvmintrinsic::fp_sin: - case llvmintrinsic::fp_cos: - case llvmintrinsic::fp_tan: - case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ - break; - } - - return llvm_ir_builder_->CreateCall(llvm_fn, args, "calltmp"); - } else { - cerr << "MachPipeline::codegen_apply: error: only allowing call to known primitives at present" << endl; + if (!llvm_fnval) { return nullptr; } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + switch(intrinsic) { + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + } /*codegen_apply*/ /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ @@ -397,21 +459,61 @@ namespace xo { const std::string & var_name, TypeDescr var_type) { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), llvm_fn->getEntryBlock().begin()); llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + if (!llvm_var_type) return nullptr; - return tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; } /*create_entry_block_alloca*/ + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + llvm::Function * - MachPipeline::codegen_lambda(ref::brw lambda) + MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; using xo::scope; @@ -419,27 +521,18 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - /* reminder! this is the *expression*, not the *closure* */ - global_env_[lambda->name()] = lambda.get(); /* do we already know a function with this name? */ auto * fn = llvm_module_->getFunction(lambda->name()); if (fn) { - /** function with this name already defined?? **/ - cerr << "MachPipeline::codegen_lambda: function f already defined" - << xtag("f", lambda->name()) - << endl; - - return nullptr; + return fn; } /* establish prototype for this function */ - // PLACEHOLDER - // just handle double arguments + return type for now - +#ifdef OBSOLETE llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_retval()); @@ -449,10 +542,11 @@ namespace xo { arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), lambda->fn_arg(i)); } +#endif - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - arg_type_v, - false /*!varargs*/); + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, @@ -463,7 +557,7 @@ namespace xo { { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm format param names", + log && log("llvm formal param names", xtag("i", i), xtag("param", lambda->argv().at(i))); @@ -472,114 +566,158 @@ namespace xo { } } + return fn; + } /*codegen_lambda_decl*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + /* generate function body */ - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - llvm_ir_builder_->SetInsertPoint(block); + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); - /* formal parameters need to appear in named_value_map_ */ - nested_env_.clear(); { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + int i = 0; - for (auto & arg : fn->args()) { + for (auto & arg : llvm_fn->args()) { log && log("nested environment", xtag("i", i), xtag("param", std::string(arg.getName()))); + std::string arg_name = std::string(arg.getName()); + /* stack location for arg[i] */ llvm::AllocaInst * alloca - = create_entry_block_alloca(fn, - std::string(arg.getName()), + = create_entry_block_alloca(llvm_fn, + arg_name, lambda->fn_arg(i)); - if (!alloca) + if (!alloca) { + this->env_stack_.pop(); return nullptr; + } /* store on function entry * see codegen_variable() for corresponding load */ - this->llvm_ir_builder_->CreateStore(&arg, alloca); + ir_builder.CreateStore(&arg, alloca); /* remember stack location for reference + assignment * in lambda body. * */ - nested_env_[std::string(arg.getName())] = alloca; + env_stack_.top().alloc_var(arg_name, alloca); ++i; } } - llvm::Value * retval = this->codegen(lambda->body()); + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); if (retval) { /* completes the function.. */ - llvm_ir_builder_->CreateRet(retval); + ir_builder.CreateRet(retval); /* validate! always validate! */ - llvm::verifyFunction(*fn); + llvm::verifyFunction(*llvm_fn); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-before-opt", buf)); } /* optimize! improves IR */ - ir_pipeline_->run_pipeline(*fn); // llvm_fpmgr_->run(*fn, *llvm_famgr_); + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); if (log) { std::string buf; llvm::raw_string_ostream ss(buf); - fn->print(ss); + llvm_fn->print(ss); log(xtag("IR-after-opt", buf)); } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); - return fn; + llvm_fn = nullptr; } - /* oops, something went wrong */ - fn->eraseFromParent(); + this->env_stack_.pop(); - return nullptr; - } /*codegen_lambda*/ + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ llvm::Value * - MachPipeline::codegen_variable(ref::brw var) + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) { - auto ix = nested_env_.find(var->name()); - - if (ix == nested_env_.end()) { - cerr << "MachPipeline::codegen_variable: no binding for variable x" + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) << endl; + return nullptr; } - llvm::AllocaInst * alloca = ix->second; + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; /* code to load value from stack */ - return this->llvm_ir_builder_->CreateLoad(alloca->getAllocatedType(), - alloca, - var->name().c_str()); + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr) + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test()); + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir - = llvm_ir_builder_->CreateFCmpONE(test_ir, - llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), - llvm::APFloat(0.0)), - "iftest"); + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); - llvm::Function * parent_fn = llvm_ir_builder_->GetInsertBlock()->getParent(); + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); /* when_true_bb, when_false_bb, merge_bb: * initially-empty basic-blocks for {when_true, when_false, merged} codegen @@ -601,45 +739,46 @@ namespace xo { /* IR to direct control flow to one of {when_true_bb, when_false_bb}, * depending on result of test_with_cmp_ir */ - llvm_ir_builder_->CreateCondBr(test_with_cmp_ir, - when_true_bb, - when_false_bb); + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); /* populate when_true_bb */ - llvm_ir_builder_->SetInsertPoint(when_true_bb); + ir_builder.SetInsertPoint(when_true_bb); - llvm::Value * when_true_ir = this->codegen(expr->when_true()); + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); if (!when_true_ir) return nullptr; /* at end of when-true sequence, jump to merge suffix */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = llvm_ir_builder_->GetInsertBlock(); + when_true_bb = ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - llvm_ir_builder_->SetInsertPoint(when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false()); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); if (!when_false_ir) return nullptr; /* at end of when-false sequence, jump to merge suffix */ - llvm_ir_builder_->CreateBr(merge_bb); + ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = llvm_ir_builder_->GetInsertBlock(); + when_false_bb = ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - llvm_ir_builder_->SetInsertPoint(merge_bb); + ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = llvm_ir_builder_->CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); phi_node->addIncoming(when_true_ir, when_true_bb); phi_node->addIncoming(when_false_ir, when_false_bb); @@ -647,7 +786,7 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr) + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: @@ -655,13 +794,13 @@ namespace xo { case exprtype::primitive: return this->codegen_primitive(PrimitiveInterface::from(expr)); case exprtype::apply: - return this->codegen_apply(Apply::from(expr)); + return this->codegen_apply(Apply::from(expr), ir_builder); case exprtype::lambda: - return this->codegen_lambda(Lambda::from(expr)); + return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr)); + return this->codegen_variable(Variable::from(expr), ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr)); + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -675,6 +814,59 @@ namespace xo { return nullptr; } /*codegen*/ + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + void MachPipeline::dump_current_module() { diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.cpp @@ -0,0 +1,45 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + llvm::AllocaInst * + activation_record::lookup_var(const std::string & x) const { + + auto ix = frame_.find(x); + + if (ix == frame_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return nullptr; + } + + return ix->second; + } /*lookup_var*/ + + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ From 2f593d15d55cd00dc6c6ecee44374ecc38fa59d9 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:03:19 -0400 Subject: [PATCH 061/102] xo-jit: + 2 examples --- example/CMakeLists.txt | 2 + example/ex2_jit/CMakeLists.txt | 12 +++ example/ex2_jit/ex2_jit.cpp | 170 ++++++++++++++++++++++++++++++++ example/ex3_fptr/CMakeLists.txt | 12 +++ example/ex3_fptr/ex3_fptr.cpp | 45 +++++++++ 5 files changed, 241 insertions(+) create mode 100644 example/ex2_jit/CMakeLists.txt create mode 100644 example/ex2_jit/ex2_jit.cpp create mode 100644 example/ex3_fptr/CMakeLists.txt create mode 100644 example/ex3_fptr/ex3_fptr.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 0cb5ee51..168ffa7c 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,2 +1,4 @@ add_subdirectory(ex1) +add_subdirectory(ex2_jit) +add_subdirectory(ex3_fptr) add_subdirectory(ex_kaleidoscope4) diff --git a/example/ex2_jit/CMakeLists.txt b/example/ex2_jit/CMakeLists.txt new file mode 100644 index 00000000..1a6d1e88 --- /dev/null +++ b/example/ex2_jit/CMakeLists.txt @@ -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 diff --git a/example/ex2_jit/ex2_jit.cpp b/example/ex2_jit/ex2_jit.cpp new file mode 100644 index 00000000..3131f823 --- /dev/null +++ b/example/ex2_jit/ex2_jit.cpp @@ -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 + +#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); + + 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()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + //auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + auto lambda = make_lambda("callit", + {f_var, x_var}, + call1); +#elif CHOICE == 1 +#define FUNCTION_SYMBOL "root4" + /* def root4(x : double) { sqrt(sqrt(x)) } */ + + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(sqrt, {x_var}); + auto call2 = make_apply(sqrt, {call1}); + + auto lambda = make_lambda("root4", + {x_var}, + call2); +#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()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + /* (define (twice f ::int->int x ::int) (f (f x))) */ + auto lambda = make_lambda("twice", + {f_var, x_var}, + call2); +#endif + + log && log(xtag("lambda", lambda)); + + auto llvm_ircode = jit->codegen_toplevel(lambda); + + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "ex1 llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "ex1: code generation failed" + << xtag("expr", lambda) + << endl; + } + + jit->machgen_current_module(); + + /* is this in module? */ + cerr << "ex2: jit execution session" << endl; + jit->dump_execution_session(); + + auto fn = jit->lookup_symbol(FUNCTION_SYMBOL); + + if (!fn) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", FUNCTION_SYMBOL) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("fn", fn.get().getValue()) + << xtag("symbol", FUNCTION_SYMBOL) + << endl; + } + } +} + +/** end ex2_jit.cpp **/ diff --git a/example/ex3_fptr/CMakeLists.txt b/example/ex3_fptr/CMakeLists.txt new file mode 100644 index 00000000..b66e7f86 --- /dev/null +++ b/example/ex3_fptr/CMakeLists.txt @@ -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 diff --git a/example/ex3_fptr/ex3_fptr.cpp b/example/ex3_fptr/ex3_fptr.cpp new file mode 100644 index 00000000..ef6de58c --- /dev/null +++ b/example/ex3_fptr/ex3_fptr.cpp @@ -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 putArgs; + putArgs.push_back(builder.getInt8Ty()->getPointerTo()); + llvm::ArrayRef argsRef(putArgs); + llvm::FunctionType *putsType = llvm::FunctionType::get(builder.getInt32Ty(), argsRef, false); + /* = FunctionType + Callee-pointer */ + llvm::FunctionCallee putFunction_callee = module->getOrInsertFunction("puts", putsType); + +#ifdef NOT_YET + llvm::Constant * putFunction = llvm::Constant + + // Allocate memory for the function pointer + llvm::Value *p = builder.CreateAlloca(putFunction->getType(), nullptr, "p"); + builder.CreateStore(putFunction, p, false); + + // Load the function pointer and call it + llvm::Value *temp = builder.CreateLoad(p); + builder.CreateCall(temp, helloWorld); + + // Return 0 to complete the main function + builder.CreateRet(llvm::ConstantInt::get(builder.getInt32Ty(), 0)); + + // Print the module (IR code) + module->print(llvm::errs(), nullptr); +#endif +} From 5c7af2151b3f410528bd537c04ec2fe11f7960dd Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 14:03:30 -0400 Subject: [PATCH 062/102] xo-jit: + utest --- CMakeLists.txt | 2 +- utest/CMakeLists.txt | 15 +++ utest/MachPipeline.test.cpp | 188 ++++++++++++++++++++++++++++++++++++ utest/jit_utest_main.cpp | 6 ++ 4 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 utest/CMakeLists.txt create mode 100644 utest/MachPipeline.test.cpp create mode 100644 utest/jit_utest_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 094529c0..3e1f3fb4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets # ---------------------------------------------------------------- add_subdirectory(example) -#add_subdirectory(utest) +add_subdirectory(utest) # ---------------------------------------------------------------- # docs targets depend on all the other library/utest targets diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 00000000..dbb64642 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,15 @@ +# 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_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) +endif() + +# end CMakeLists.txt diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp new file mode 100644 index 00000000..072cde2a --- /dev/null +++ b/utest/MachPipeline.test.cpp @@ -0,0 +1,188 @@ +/* @file MachPipeline.test.cpp */ + +#include "xo/jit/MachPipeline.hpp" +#include "xo/expression/Primitive.hpp" +#include "xo/indentlog/scope.hpp" +#include + +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::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 + root4_ast() { + auto sqrt = make_primitive("sqrt", + ::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + auto x_var = make_var("x", Reflect::require()); + 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 + 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()); + auto x_var = make_var("x", Reflect::require()); + auto call1 = make_apply(f_var, {x_var}); /* (f x) */ + auto call2 = make_apply(f_var, {call1}); /* (f (f x)) */ + + /* def twice(f :: double->double, x :: double) { f(f(x)); } */ + auto twice = make_lambda("twice", + {f_var, x_var}, + call2); + + auto x2_var = make_var("x2", Reflect::require()); + auto call3 = make_apply(twice, {root, x2_var}); + + /* def root4(x2 :: double) { + * def twice(f :: double->double, x :: double) { f(f(x)); }}; + * twice(sqrt, x2) + * } + */ + auto fn_ast = make_lambda("root_2x", + {x2_var}, + call3); + + return fn_ast; + } + + struct TestCase { + rp (*make_ast_)(); + /* each pair is (input, output) for function double->double */ + std::vector> call_v_; + }; + + static std::vector + s_testcase_v = { + {&root4_ast, + {std::make_pair(1.0, 1.0), + std::make_pair(16.0, 2.0), + std::make_pair(81.0, 3.0)}}, + {&root_2x_ast, + {std::make_pair(1.0, 1.0), + std::make_pair(16.0, 2.0), + std::make_pair(81.0, 3.0)}} + }; + + /** testcase root_ast tests: + * - nested function call + * + * testcase root_2x_ast relies on: + * - lambda in function position + * - argument with function type + **/ + TEST_CASE("machpipeline.fptr", "[llvm][llvm_fnptr]") { + constexpr bool c_debug_flag = true; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline")); + //log && log("(A)", xtag("foo", foo)); + + auto jit = MachPipeline::make(); + + for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) { + TestCase const & testcase = s_testcase_v[i_tc]; + + INFO(tostr(xtag("i_tc", i_tc))); + + auto ast = (*testcase.make_ast_)(); + + REQUIRE(ast.get()); + + log && log(xtag("ast", ast)); + + REQUIRE(ast->extype() == exprtype::lambda); + + brw fn_ast = Lambda::from(ast); + + llvm::Value * llvm_ircode = jit->codegen_toplevel(fn_ast); + + /* TODO: printer for llvm::Value* */ + if (llvm_ircode) { + /* note: llvm:errs() is 'raw stderr stream' */ + cerr << "llvm_ircode:" << endl; + llvm_ircode->print(llvm::errs()); + cerr << endl; + } else { + cerr << "code generation failed" + << xtag("fn_ast", fn_ast) + << endl; + } + + REQUIRE(llvm_ircode); + + jit->machgen_current_module(); + + /** lookup compiled function pointer in jit **/ + auto llvm_addr = jit->lookup_symbol(fn_ast->name()); + + if (!llvm_addr) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", fn_ast->name()) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", fn_ast->name()) + << endl; + } + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + for (std::size_t j_call = 0, n_call = testcase.call_v_.size(); j_call < n_call; ++j_call) { + double input = testcase.call_v_[j_call].first; + double expected = testcase.call_v_[j_call].second; + + INFO(tostr(xtag("j_call", j_call), xtag("input", input), xtag("expected", expected))); + + auto actual = (*fn_ptr)(input); + + REQUIRE(actual == expected); + } + } + + } /*TEST_CASE(machpipeline)*/ + } /*namespace ut*/ +} /*namespace xo*/ + + +/* end MachPipeline.test.cpp */ diff --git a/utest/jit_utest_main.cpp b/utest/jit_utest_main.cpp new file mode 100644 index 00000000..1bea47b1 --- /dev/null +++ b/utest/jit_utest_main.cpp @@ -0,0 +1,6 @@ +/* @file jit_utest_main.cpp */ + +#define CATCH_CONFIG_MAIN +#include + +/* end jit_utest_main.cpp */ From d06f176c98e569e2d7c836517bf9d86e78d4fe4b Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 15:08:20 -0400 Subject: [PATCH 063/102] xo-jit: add sub/div intrinsics --- src/jit/MachPipeline.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index f19d92b1..1352387a 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -419,15 +419,29 @@ namespace xo { ++i; } + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); case llvmintrinsic::i_add: return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); case llvmintrinsic::i_mul: return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); case llvmintrinsic::fp_add: return ir_builder.CreateFAdd(args[0], args[1]); case llvmintrinsic::fp_mul: return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: From be6d7c2aab322800650e5a27a360583453f1be71 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 24 Jun 2024 22:18:59 -0400 Subject: [PATCH 064/102] xo-jit: TypeDescr->llvm::Type conv for structs --- include/xo/jit/MachPipeline.hpp | 3 -- src/jit/MachPipeline.cpp | 76 +++++++++++++++++++++++++++++++++ utest/CMakeLists.txt | 2 + utest/MachPipeline.test.cpp | 25 ++++++++++- 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 2497fb5f..308397fc 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -152,7 +152,6 @@ namespace xo { // ----- this part adapted from kaleidoscope.cpp ----- - public: /** everything below represents a pipeline * that takes expressions, and turns them into llvm IR. * @@ -162,7 +161,6 @@ namespace xo { **/ xo::ref::rp ir_pipeline_; - private: /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. * @@ -184,7 +182,6 @@ namespace xo { /** map global names to functions/variables **/ std::map> global_env_; - public: /** map variable names (formal parameters) to * corresponding llvm IR. * diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1352387a..9a1a50bb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -15,6 +15,7 @@ namespace xo { using xo::ast::IfExpr; using xo::ast::llvmintrinsic; using xo::reflect::Reflect; + using xo::reflect::StructMember; using xo::reflect::TypeDescr; using std::cerr; using std::endl; @@ -142,6 +143,12 @@ namespace xo { } /*codegen_constant*/ namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); @@ -197,6 +204,73 @@ namespace xo { return llvm_ptr_type; } + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * If we don't set isPacked, then padding will be chosen based on DataLayout, + * which might C++ compiler's padding, but no guarantees. + * + * 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), + true /*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::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); @@ -206,6 +280,8 @@ namespace xo { * i.e. something that can be stored in a variable */ return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt index dbb64642..00614ddd 100644 --- a/utest/CMakeLists.txt +++ b/utest/CMakeLists.txt @@ -9,6 +9,8 @@ set(SELF_SRCS 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() diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 072cde2a..c2c7f5bc 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -2,6 +2,8 @@ #include "xo/jit/MachPipeline.hpp" #include "xo/expression/Primitive.hpp" +#include "xo/ratio/ratio.hpp" +#include "xo/reflect/reflect_struct.hpp" #include "xo/indentlog/scope.hpp" #include @@ -15,6 +17,7 @@ namespace xo { 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; @@ -179,8 +182,28 @@ namespace xo { REQUIRE(actual == expected); } } - } /*TEST_CASE(machpipeline)*/ + + TEST_CASE("machpipeline.struct", "[llvm][llvm_struct]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.struct")); + //log && log("(A)", xtag("foo", foo)); + + auto jit = MachPipeline::make(); + + /* let's reflect xo::ratio::ratio */ + + auto struct_td = reflect_struct>(); + + REQUIRE(struct_td); + } /*TEST_CASE(machpipeline.struct)*/ } /*namespace ut*/ } /*namespace xo*/ From e1d8d7619b577f3e7335b93136973d7ff2314e77 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:27:36 -0400 Subject: [PATCH 065/102] xo-jit: ignore unused-parameter in llvm .h files --- include/xo/jit/IrPipeline.hpp | 47 +++++++++++++++------------- include/xo/jit/Jit.hpp | 29 +++++++++-------- include/xo/jit/LlvmContext.hpp | 7 ++++- include/xo/jit/activation_record.hpp | 7 +++-- 4 files changed, 52 insertions(+), 38 deletions(-) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index 60473bc5..d8234e6b 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -9,28 +9,31 @@ #include "LlvmContext.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/Utils/Mem2Reg.h" -#include "llvm/Transforms/Scalar/Reassociate.h" -#include "llvm/Transforms/Scalar/SimplifyCFG.h" +#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 diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 5af092d0..98fbc614 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -4,19 +4,22 @@ #pragma once -#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" -#include "llvm/ExecutionEngine/SectionMemoryManager.h" -#include "llvm/IR/DataLayout.h" -#include "llvm/IR/LLVMContext.h" +#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" +# include "llvm/ExecutionEngine/SectionMemoryManager.h" +# include "llvm/IR/DataLayout.h" +# include "llvm/IR/LLVMContext.h" +#pragma GCC diagnostic pop #include namespace xo { diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp index 38f1492a..cf594c82 100644 --- a/include/xo/jit/LlvmContext.hpp +++ b/include/xo/jit/LlvmContext.hpp @@ -6,7 +6,12 @@ #pragma once #include "xo/refcnt/Refcounted.hpp" -#include "llvm/IR/LLVMContext.h" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include "llvm/IR/LLVMContext.h" +#pragma GCC diagnostic pop + //#include namespace xo { diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index eb98975e..c2aba2dd 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -6,8 +6,11 @@ #pragma once #include "LlvmContext.hpp" -#include -#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +# include +# include +#pragma GCC diagnostic pop #include //#include From ce9d93240a98cc387dcfb2f955ffaa1b1484d7b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 00:27:58 -0400 Subject: [PATCH 066/102] xo-jit: + LLVM_LIBRARY_DIR, need this on darwin --- src/jit/CMakeLists.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index fe0b9e3d..4168eebe 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -23,6 +23,15 @@ 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 @@ -34,6 +43,7 @@ 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 From 72d0305cdbddd07ae937b7a88c668abafa89025c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:31:07 -0400 Subject: [PATCH 067/102] xo-jit: refactor: Jit::mangle() -> std::string_view --- include/xo/jit/Jit.hpp | 10 +++++++--- include/xo/jit/MachPipeline.hpp | 4 ++-- src/jit/MachPipeline.cpp | 26 ++++++++++++++------------ 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 98fbc614..9888e1ca 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -133,8 +133,10 @@ namespace xo { /** intern @p symbol, binding it to address @p dest **/ template llvm::Error intern_symbol(const std::string & symbol, T * dest) { + auto mangled_sym = mangler_(symbol); + llvm::orc::SymbolMap symbol_map; - symbol_map[mangler_(symbol)] + symbol_map[mangled_sym] = llvm::orc::ExecutorSymbolDef(llvm::orc::ExecutorAddr::fromPtr(dest), llvm::JITSymbolFlags()); @@ -144,8 +146,10 @@ namespace xo { } /*intern_symbol*/ /** report mangled symbol name **/ - auto mangle(StringRef name) { - return this->mangler_(name.str()); + std::string_view mangle(StringRef name) { + auto tmp = *(this->mangler_(name.str())); + + return std::string_view(tmp.data(), tmp.size()); } llvm::Expected lookup(StringRef name) { diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 308397fc..ec7e0b4d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -108,8 +108,8 @@ namespace xo { /** dump text description of module contents to console **/ void dump_current_module(); - /** report mangle symbol **/ - std::string mangle(const std::string & x) const; + /** report mangled symbol for @p x **/ + std::string_view mangle(const std::string & x) const; /** lookup symbol in jit-associated output library **/ llvm::Expected lookup_symbol(const std::string & x); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9a1a50bb..e647c63f 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -308,9 +308,11 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { - //constexpr bool c_debug_flag = true; + constexpr bool c_debug_flag = true; using xo::scope; + scope log(XO_DEBUG(c_debug_flag)); + /** note: documentation (such as it is) for llvm::Function here: * * https://llvm.org/doxygenL/classllvm_1_1Function.html @@ -359,8 +361,13 @@ namespace xo { if (expr->explicit_symbol_def()) { static llvm::ExitOnError llvm_exit_on_err; - llvm_exit_on_err(this->jit_->intern_symbol(expr->name(), - expr->function_address())); + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); #ifdef NOT_USING if (!llvm_result) { @@ -373,6 +380,8 @@ namespace xo { return nullptr; } #endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); } #ifdef OBSOLETE @@ -987,17 +996,10 @@ namespace xo { this->recreate_llvm_ir_pipeline(); } /*machgen_current_module*/ - std::string + std::string_view MachPipeline::mangle(const std::string & sym) const { - auto p = this->jit_->mangle(sym); - - if (p) - return (*p).str(); - - throw std::runtime_error(tostr("MachPipeline::mangle" - ": mangle(sym) returned empty pointer", - xtag("sym", sym))); + return this->jit_->mangle(sym); } /*mangle*/ llvm::Expected From fcd87b52c0ead72b6dff4332b16b5d4f18712928 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:35:17 -0400 Subject: [PATCH 068/102] xo-jit: + MachPipeline::data_layout() --- include/xo/jit/MachPipeline.hpp | 7 +++++-- src/jit/MachPipeline.cpp | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ec7e0b4d..0dc5a6a8 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -57,6 +57,7 @@ namespace xo { using Expression = xo::ast::Expression; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; + using DataLayout = llvm::DataLayout; //using ConstantInterface = xo::ast::ConstantInterface; public: @@ -64,7 +65,7 @@ namespace xo { static llvm::Expected> make_aux(); static xo::ref::rp make(); - // ----- module access ----- + // ----- access ----- llvm::Module * current_module() { return llvm_module_.get(); } ref::brw llvm_cx() { return llvm_cx_; } @@ -72,6 +73,8 @@ namespace xo { /** target triple = string describing target host for codegen **/ const std::string & target_triple() 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) @@ -173,7 +176,7 @@ namespace xo { std::unique_ptr> llvm_toplevel_ir_builder_; /** a module (1:1 with library ?) being prepared by llvm. - * IR-level -- does not contain machine code + * IR-level -- does not contain machine coode * * - function names are unique within a module. **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e647c63f..5b3e2cba 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using llvm::DataLayout; using std::cerr; using std::endl; @@ -106,6 +107,11 @@ namespace xo { return this->jit_->target_triple(); } + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + std::vector MachPipeline::get_function_name_v() { std::vector retval; From 56210442a2ea0d9f22e8262309318d157412d6b6 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:36:06 -0400 Subject: [PATCH 069/102] xo-jit: honor clang nit --- src/jit/MachPipeline.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5b3e2cba..bf2d76e2 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -7,7 +7,7 @@ namespace xo { using xo::ast::exprtype; using xo::ast::Expression; using xo::ast::ConstantInterface; - using xo::ast::FunctionInterface; + //using xo::ast::FunctionInterface; using xo::ast::PrimitiveInterface; using xo::ast::Lambda; using xo::ast::Variable; From 360d1da2f95f525ff97f412e55bb854c4c88f641 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 09:36:39 -0400 Subject: [PATCH 070/102] xo-jit: + struct unit test ! --- utest/MachPipeline.test.cpp | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index c2c7f5bc..37fb581c 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -3,6 +3,7 @@ #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 @@ -184,8 +185,32 @@ namespace xo { } } /*TEST_CASE(machpipeline)*/ + rp + make_ratio() { + auto make_ratio_impl = make_primitive("make_ratio_impl", + xo::ratio::make_ratio, + true /*explicit_symbol_def*/, + llvmintrinsic::invalid); + REQUIRE(make_ratio_impl.get()); + REQUIRE(make_ratio_impl->explicit_symbol_def()); + + /* jit-prepared library: + * 1. *uses* make_ratio_impl + * 2. *provides* make_ratio (can do jit->lookup_symbol("make_ratio")) + */ + auto n_var = make_var("n", Reflect::require()); + auto d_var = make_var("d", Reflect::require()); + auto call1 = make_apply(make_ratio_impl, {n_var, d_var}); /*make_ratio(n,d)*/ + + auto make_ratio = make_lambda("make_ratio", + {n_var, d_var}, + call1); + + return make_ratio; + } + TEST_CASE("machpipeline.struct", "[llvm][llvm_struct]") { - constexpr bool c_debug_flag = false; + constexpr bool c_debug_flag = true; // can get bits from /dev/random by uncommenting the 2nd line below //uint64_t seed = xxx; @@ -200,9 +225,61 @@ namespace xo { /* let's reflect xo::ratio::ratio */ - auto struct_td = reflect_struct>(); + using ratio_type = xo::ratio::ratio; + + auto struct_td = reflect_struct(); REQUIRE(struct_td); + + auto fn_ast = make_ratio(); + + 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(); + + cerr << "execution session after codegen:" << endl; + jit->dump_execution_session(); + + /** lookup compiled function pointer in jit **/ + auto llvm_addr = jit->lookup_symbol(fn_ast->name()); + + cerr << "execution session after lookup attempt:" << endl; + jit->dump_execution_session(); + + if (!llvm_addr) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", fn_ast->name()) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", fn_ast->name()) + << endl; + } + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + 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*/ From e8f8a43f7a5efe5853103626a5b92667a240b468 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 10:58:31 -0400 Subject: [PATCH 071/102] xo-jit: + docs/ directory + sphinx doc skeleton --- CMakeLists.txt | 3 ++ docs/CMakeLists.txt | 7 +++++ docs/README | 70 +++++++++++++++++++++++++++++++++++++++++++++ docs/conf.py | 39 +++++++++++++++++++++++++ docs/glossary.rst | 20 +++++++++++++ docs/index.rst | 21 ++++++++++++++ 6 files changed, 160 insertions(+) create mode 100644 docs/CMakeLists.txt create mode 100644 docs/README create mode 100644 docs/conf.py create mode 100644 docs/glossary.rst create mode 100644 docs/index.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e1f3fb4..2ee6351c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,9 @@ 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) + # ---------------------------------------------------------------- # docs targets depend on all the other library/utest targets # diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt new file mode 100644 index 00000000..bf9c8563 --- /dev/null +++ b/docs/CMakeLists.txt @@ -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 +) diff --git a/docs/README b/docs/README new file mode 100644 index 00000000..2fab6399 --- /dev/null +++ b/docs/README @@ -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} diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..31acd35e --- /dev/null +++ b/docs/conf.py @@ -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' diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 00000000..c5a22ec1 --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,20 @@ +.. _glossary: + +Glossary +-------- + +.. glossary:: + xsession + | Shorthand for `llvm::orc::ExecutionSession`. + | Manages running JIT-generated machine code in the host process + + td + | Short for `xo::reflect::TypeDescr`. + + XO + A set of integrated c++ libraries for complex event processing, with browser and python integration. + `xo documentation here`_ + +.. _xo documentation here: https://rconybea.github.io/web/sw/xo.html + +.. toctree:: diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..ea337854 --- /dev/null +++ b/docs/index.rst @@ -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 From 274370c28c0c51b41872c651ff30f60ef5961142 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:04:58 -0400 Subject: [PATCH 072/102] xo-jit: + Jit::xsession accessor --- include/xo/jit/Jit.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index 9888e1ca..f983fb2e 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -109,13 +109,15 @@ namespace xo { 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(); } - const DataLayout & data_layout() const { return data_layout_; } - - JITDylib & dest_dynamic_lib_ref() { return dest_dynamic_lib_; } /** compile module to machine code that's runnable from this process; * incorporate into @ref dest_dynamic_lib_ From 09d884737da7f0edd2cf0244765cd234954d25b8 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:13:25 -0400 Subject: [PATCH 073/102] xo-jit: + MachPipeline::codegen_type() --- include/xo/jit/MachPipeline.hpp | 17 +++++++++++++++++ src/jit/MachPipeline.cpp | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 0dc5a6a8..49be235d 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -86,6 +86,23 @@ namespace xo { // ----- code generation ----- + /** establish llvm IR corresponding to a c++ type. + * Handles + * T := bool|char|short|int|long|float|double + * | T1(*)(T2..Tn) + * | struct{T1,..,Tn} + * + * Not supported yet: + * - vector + * - string + * - map + * - unions + * - pointers (except function pointers) + * + * Idempotent: multiple calls with the same @p td produce the same @c llvm::Type pointer. + * @c llvm::Type instances are *immortal* (llvm interns them into opaque global lookup tables) + **/ + llvm::Type * codegen_type(TypeDescr td); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw expr); llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index bf2d76e2..88037393 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -311,6 +311,11 @@ namespace xo { } } + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + llvm::Function * MachPipeline::codegen_primitive(ref::brw expr) { From 585e4cc35c9d70a6f77c37e507792686edc48815 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:06 -0400 Subject: [PATCH 074/102] xo-jit: + MachPipeline::xsession() --- include/xo/jit/MachPipeline.hpp | 4 ++++ src/jit/MachPipeline.cpp | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 49be235d..3e475e92 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -43,6 +43,7 @@ #include "llvm/Transforms/Scalar/GVN.h" #include "llvm/Transforms/Scalar/Reassociate.h" #include "llvm/Transforms/Scalar/SimplifyCFG.h" +#include namespace xo { @@ -57,6 +58,7 @@ namespace xo { using Expression = xo::ast::Expression; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; + using ExecutionSession = llvm::orc::ExecutionSession; using DataLayout = llvm::DataLayout; //using ConstantInterface = xo::ast::ConstantInterface; @@ -73,6 +75,8 @@ namespace xo { /** 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 diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 88037393..5f13c371 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -17,6 +17,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; using llvm::DataLayout; using std::cerr; using std::endl; @@ -96,6 +97,11 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + /** identifies target host/architecture for machine code. * e.g. "x86_64-unknown-linux-gnu" **/ From 7e1f3c8cb502919aa36216139a3f54dccb86be12 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:22 -0400 Subject: [PATCH 075/102] xo-jit: cosmetic -- code layout --- src/jit/MachPipeline.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5f13c371..5228cc09 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -97,6 +97,11 @@ namespace xo { ir_pipeline_ = new IrPipeline(llvm_cx_); } /*recreate_llvm_ir_pipeline*/ + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + const ExecutionSession * MachPipeline::xsession() const { return this->jit_->xsession(); @@ -113,11 +118,6 @@ namespace xo { return this->jit_->target_triple(); } - const DataLayout & - MachPipeline::data_layout() const { - return this->jit_->data_layout(); - } - std::vector MachPipeline::get_function_name_v() { std::vector retval; @@ -242,7 +242,7 @@ namespace xo { llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, sm.get_member_td())); - } + } std::string struct_name = std::string(struct_td->short_name()); From cf95f64961f675eeb3a1282aa6997b71f12a4a0e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Tue, 25 Jun 2024 11:14:41 -0400 Subject: [PATCH 076/102] xo-jit: modest output change in utest --- utest/MachPipeline.test.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 37fb581c..7a309f74 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -251,13 +251,14 @@ namespace xo { jit->machgen_current_module(); - cerr << "execution session after codegen:" << endl; + log && log("execution session after codegen:"); + //log && log(jit->xsession()); // segfaults jit->dump_execution_session(); /** lookup compiled function pointer in jit **/ auto llvm_addr = jit->lookup_symbol(fn_ast->name()); - cerr << "execution session after lookup attempt:" << endl; + log && log("execution session after lookup attempt:"); jit->dump_execution_session(); if (!llvm_addr) { From f971f18ecde5cb81fa511164d9a8939e8c43b6d0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:10:00 -0400 Subject: [PATCH 077/102] xo-jit: inspect struct alignment in utest --- utest/MachPipeline.test.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 7a309f74..5e0fafc4 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -231,8 +231,12 @@ namespace xo { 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* */ @@ -249,12 +253,38 @@ namespace xo { REQUIRE(llvm_ircode); + // ----- inspect alignment ----- + + llvm::StructType * struct_llvm_type + = static_cast(jit->codegen_type(struct_td)); + + auto struct_layout = jit->data_layout().getStructLayout(struct_llvm_type); + + log && log(xtag("struct-size", struct_layout->getSizeInBytes()), + xtag("struct-alignment", struct_layout->getAlignment().value())); + for (int i = 0, n = struct_llvm_type->getNumElements(); i < n; ++i) { + llvm::TypeSize llvm_tz = struct_layout->getElementOffset(i); + auto offset = reinterpret_cast(struct_td->struct_member(i).get_member_tp(nullptr).address()); + + log && log(xtag("i", i), + xtag("name(c++)", struct_td->struct_member(i).member_name()), + xtag("type(c++)", struct_td->struct_member(i).get_member_td()->short_name()), + xtag("offset(c++)", offset), + xtag("offset(llvm)", llvm_tz.getKnownMinValue())); + + REQUIRE(offset == llvm_tz.getKnownMinValue()); + } + + // ----- generate JIT machine code ----- + jit->machgen_current_module(); log && log("execution session after codegen:"); //log && log(jit->xsession()); // segfaults jit->dump_execution_session(); + // ----- verify: lookup symbol + /** lookup compiled function pointer in jit **/ auto llvm_addr = jit->lookup_symbol(fn_ast->name()); @@ -276,6 +306,8 @@ namespace xo { REQUIRE(fn_ptr); + // ---- invoke compiled function ----- + auto value = (*fn_ptr)(2, 3); log && log(xtag("value.num", value.num()), From 465be8ddd2044ebf3b082ec93e4dea54b0b3e805 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:10:36 -0400 Subject: [PATCH 078/102] xo-jit: drop isPacked=true when creating struct types --- src/jit/MachPipeline.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 5228cc09..e2a69e7c 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -247,9 +247,6 @@ namespace xo { std::string struct_name = std::string(struct_td->short_name()); /* structs with names: within an llvmcontext, must be unique - * - * If we don't set isPacked, then padding will be chosen based on DataLayout, - * which might C++ compiler's padding, but no guarantees. * * We can however compare the offsets recorded in xo::reflect with * offsets chosen by llvm, *once we've created the llvm type* @@ -268,7 +265,7 @@ namespace xo { = llvm::StructType::create(llvm_cx_ref, llvm_membertype_v, llvm::StringRef(struct_name), - true /*isPacked*/); + false /*!isPacked*/); /* TODO: inspect (how) offsets that llvm is using * we need them to match what C++ chose From 27d8f05b528c7ff1992d5c71d81e079a61848b96 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:11:02 -0400 Subject: [PATCH 079/102] xo-jit: handle pointer types --- src/jit/MachPipeline.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index e2a69e7c..69cf0d02 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -280,6 +280,22 @@ namespace xo { return llvm_struct_type; } /*struct_td_to_llvm_type*/ + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + llvm::Type * td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); @@ -291,6 +307,8 @@ namespace xo { return function_td_to_llvm_fnptr_type(llvm_cx, td); } else if (td->is_struct()) { return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); } else if (Reflect::is_native(td)) { return llvm::Type::getInt1Ty(llvm_cx_ref); } else if (Reflect::is_native(td)) { From be3e62f75a1f73f2a0ef9750bd0a2446769bf405 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 13:11:22 -0400 Subject: [PATCH 080/102] xo-jit: helper functions -> explicit stack frames [wip, not used] --- src/jit/MachPipeline.cpp | 173 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 172 insertions(+), 1 deletion(-) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 69cf0d02..386f16e4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -700,6 +700,176 @@ namespace xo { return fn; } /*codegen_lambda_decl*/ + namespace { + /** A function type: + * + * _baseframe* (*) (_baseframe* + **/ + llvm::FunctionType * + require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, + llvm::PointerType * frameptr_llvm_type) + { + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is frame pointer */ + llvm_argtype_v.push_back(frameptr_llvm_type); + + /* 2nd arg is an i32. + * 0 -> unwind. + * 1 -> lift to heap (someday) + */ + llvm_argtype_v.push_back + (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is frame pointer */ + llvm::Type * retval_llvm_type = frameptr_llvm_type; + + auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + return unwind_llvm_type; + } /*require_baseframe_unwind_llvm_type*/ + + /** Each lambda gets its own stack frame definition. + * However all the various frame representations share the same 'baseframe' + * prefix. + * + * _baseframe: + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) + * +-------+ + * + * This helper function generates an llvm::Type* for a baseframe. + * It only needs to be invoked once (per LlvmContext, I guess ..) + **/ + llvm::StructType * + require_baseframe_llvm_type(xo::ref::brw llvm_cx) + { + /* _baseframe: base type for a stack frame */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_baseframe"); + + /* _baseframe*: pointer to a stack frame */ + llvm::PointerType * frameptr_llvm_type + = llvm::PointerType::getUnqual(frame_llvm_type); + + /* unwind function = frame[1] */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + frameptr_llvm_type); + + /* _baseframe members */ + std::vector llvm_membertype_v; + { + llvm_membertype_v.reserve(2); + + /* frame[0] = pointer to next frame */ + llvm_membertype_v.push_back(frameptr_llvm_type); + /* frame[1] = unwind function */ + llvm_membertype_v.push_back(unwind_llvm_type); + } + + frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, + unwind_llvm_type /*frame[1]*/); + + return frame_llvm_type; + } /*require_baseframe_llvm_type*/ + + /** need a supporting type for stack frame + * - so we can handle variables with non-trivial dtors + * (e.g. smart pointers) + * - so we can implement nested lexical scoping + * - so we can walk stack for exception handling + * - eventually: so we can eventually implement trampoline + * + * frame representation: + * + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) + * +-------| + * arg[i] [2+i] | ... | + * +-------+ + * + * invoke frame.unwind_fn to dispose of a frame + * - ctl=0 dtor. deal with smart pointers etc. + * - ctl=1 copy. lift frame into heap for lambda capture + * + * every frame is a subtype of _baseframe (ofc llvm doesn't know this). + * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above + * + * editor bait: activation_record_to_llvm_type + **/ + llvm::StructType * + frame_to_llvm_type(xo::ref::brw llvm_cx, + ref::brw lambda, + llvm::Function * lambda_llvm_fn) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* frame type doesn't need a name */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + /* _baseframe */ + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + /* _baseframe*: pointer to a generic stack frame */ + llvm::PointerType * baseframeptr_llvm_type + = llvm::PointerType::getUnqual(baseframe_llvm_type); + + /* _baseframe* (*)(_baseframe*, i32) */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + baseframeptr_llvm_type); + + /* llvm_argtype_v: + * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame + * - llvm_argtype_v[1] = llvm::Type* for unwind_fn + * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) + */ + std::vector llvm_argtype_v; + { + llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); + + /* frame pointer */ + llvm_argtype_v.push_back(baseframeptr_llvm_type); + /* unwind function */ + llvm_argtype_v.push_back(unwind_llvm_type); + + int i_arg = 0; + for (auto & arg : lambda_llvm_fn->args()) { + log && log(xtag("i_arg", i_arg), + xtag("param", std::string(arg.getName()))); + + llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, + lambda->fn_arg(i_arg))); + + ++i_arg; + } + } + + frame_llvm_type->setBody(llvm_argtype_v); + + return frame_llvm_type; + } /*frame_to_llvm_type*/ + } /*namespace*/ + + llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -724,7 +894,6 @@ namespace xo { return nullptr; } - /* generate function body */ auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); @@ -733,6 +902,8 @@ namespace xo { /** Actual parameters will need their own activation record. * Track its shape here. + * + * Local variables will be formal parameters to a nested lambda; **/ this->env_stack_.push(activation_record()); From 71df4f824da453c53cfcc649de423877d8432301 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 26 Jun 2024 14:38:48 -0400 Subject: [PATCH 081/102] xo-jit: progress towards explicit stack frames [wip, incomplete] --- include/xo/jit/MachPipeline.hpp | 7 +++ include/xo/jit/activation_record.hpp | 27 ++++++++- src/jit/MachPipeline.cpp | 82 +++++++++++++++++++++++++++- src/jit/activation_record.cpp | 10 ++-- 4 files changed, 118 insertions(+), 8 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 3e475e92..14003c44 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -152,6 +152,13 @@ namespace xo { std::vector> find_lambdas(ref::brw 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); + /** 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 diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index c2aba2dd..7c70ba3f 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -22,16 +22,41 @@ namespace xo { **/ class activation_record { public: - activation_record() = default; + activation_record(llvm::Function * llvm_fn, + llvm::AllocaInst * frame) : frame_{frame} { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + name2ix_map_[arg_name] = 2 + i_arg; + } + } + + std::int32_t lookup_var(const std::string & var_name) const; + +#ifdef OBSOLETE llvm::AllocaInst * lookup_var(const std::string & var_name) const; llvm::AllocaInst * alloc_var(const std::string & var_name, llvm::AllocaInst * alloca); +#endif private: + /** stack frame for a user-defined function (lambda) **/ + llvm::AllocaInst * frame_ = nullptr; + + /** for each formal parameter, + * reports its position in stack frame. + * This is the position to use with getelementptr, + * i.e. +2 to skip first two slots, that are reserved + * for nextframe pointer (slot 0) + unwind pointer (slot 1) + **/ + std::map name2ix_map_; + +#ifdef OBSOLETE /** maps named slots in a stack frame to logical addresses **/ std::map frame_; /* <-> kaleidoscope NamedValues */ +#endif }; /*activation_record*/ } /*namespace jit*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 386f16e4..76fa68ae 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -627,6 +627,42 @@ namespace xo { } /*create_entry_block_alloca*/ + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + if (!frame_llvm_type) + return nullptr; + + if (log) { + std::string llvm_frame_type_str; + llvm::raw_string_ostream ss(llvm_frame_type_str); + frame_llvm_type->print(ss); + + log(xtag("frame_llvm_type", llvm_frame_type_str)); + } + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, + nullptr, + llvm_fn->getName()); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_frame_alloca*/ + std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -869,7 +905,6 @@ namespace xo { } /*frame_to_llvm_type*/ } /*namespace*/ - llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -900,12 +935,24 @@ namespace xo { ir_builder.SetInsertPoint(block); + /* create stack frame */ + llvm::StructType * frame_llvm_type + = frame_to_llvm_type(llvm_cx_, + lambda, + llvm_fn); + + llvm::AllocaInst * frame_alloca = create_entry_frame_alloca(llvm_fn, + frame_llvm_type); + + if (!frame_alloca) + return nullptr; + /** Actual parameters will need their own activation record. * Track its shape here. * * Local variables will be formal parameters to a nested lambda; **/ - this->env_stack_.push(activation_record()); + this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -918,6 +965,7 @@ namespace xo { std::string arg_name = std::string(arg.getName()); +#ifdef OBSOLETE /* stack location for arg[i] */ llvm::AllocaInst * alloca = create_entry_block_alloca(llvm_fn, @@ -928,17 +976,45 @@ namespace xo { this->env_stack_.pop(); return nullptr; } +#endif + + /* need to store args to stack frame */ + + std::int32_t i_slot = env_stack_.top().lookup_var(arg_name); + + if (i_slot == -1) { + /* variable not found! */ + return nullptr; + } + + llvm::Value * i32_zero = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, 0)); + llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, i_slot)); + + std::array index_v = { + {i32_zero /*deref frame pointer*/, + i32_slot /*field# relative to frame pointer*/}}; + + /* location in stack frame of arg #i */ + auto arg_ptr + = ir_builder.CreateInBoundsGEP(frame_llvm_type, + frame_alloca, + index_v); /* store on function entry * see codegen_variable() for corresponding load */ - ir_builder.CreateStore(&arg, alloca); + ir_builder.CreateStore(&arg, + alloca /*destination*/); +#ifdef OBSOLETE /* remember stack location for reference + assignment * in lambda body. * */ env_stack_.top().alloc_var(arg_name, alloca); +#endif ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index c7f40362..cc1aaae3 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -9,21 +9,22 @@ namespace xo { using std::cerr; using std::endl; - llvm::AllocaInst * + int32_t activation_record::lookup_var(const std::string & x) const { - auto ix = frame_.find(x); + auto ix = name2ix_map_.find(x); - if (ix == frame_.end()) { + if (ix == name2ix_map_.end()) { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; - return nullptr; + return -1; } return ix->second; } /*lookup_var*/ +#ifdef OBSOLETE llvm::AllocaInst * activation_record::alloc_var(const std::string & x, llvm::AllocaInst * alloca) @@ -38,6 +39,7 @@ namespace xo { frame_[x] = alloca; return alloca; } /*alloc_var*/ +#endif } /*namespace jit*/ } /*namespace xo*/ From 1f0c0cb71de79e892fc3266c00203801b6871eca Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 30 Jun 2024 20:03:55 -0400 Subject: [PATCH 082/102] xo-jit: use Environment for toplevel lambdas --- include/xo/jit/MachPipeline.hpp | 4 +- include/xo/jit/activation_record.hpp | 27 +-- src/jit/MachPipeline.cpp | 261 +-------------------------- src/jit/activation_record.cpp | 10 +- 4 files changed, 16 insertions(+), 286 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 14003c44..11589e8c 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -20,6 +20,7 @@ #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" @@ -57,6 +58,7 @@ namespace xo { 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; @@ -211,7 +213,7 @@ namespace xo { std::unique_ptr llvm_module_; /** map global names to functions/variables **/ - std::map> global_env_; + ref::rp global_env_; /** map variable names (formal parameters) to * corresponding llvm IR. diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 7c70ba3f..c2aba2dd 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -22,41 +22,16 @@ namespace xo { **/ 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()); + activation_record() = default; - name2ix_map_[arg_name] = 2 + i_arg; - } - } - - std::int32_t lookup_var(const std::string & var_name) const; - -#ifdef OBSOLETE llvm::AllocaInst * lookup_var(const std::string & var_name) const; llvm::AllocaInst * alloc_var(const std::string & var_name, llvm::AllocaInst * alloca); -#endif private: - /** stack frame for a user-defined function (lambda) **/ - llvm::AllocaInst * frame_ = nullptr; - - /** for each formal parameter, - * reports its position in stack frame. - * This is the position to use with getelementptr, - * i.e. +2 to skip first two slots, that are reserved - * for nextframe pointer (slot 0) + unwind pointer (slot 1) - **/ - std::map name2ix_map_; - -#ifdef OBSOLETE /** maps named slots in a stack frame to logical addresses **/ std::map frame_; /* <-> kaleidoscope NamedValues */ -#endif }; /*activation_record*/ } /*namespace jit*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 76fa68ae..9c6bb1cd 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -13,6 +13,7 @@ namespace xo { using xo::ast::Variable; using xo::ast::Apply; using xo::ast::IfExpr; + using xo::ast::GlobalEnv; using xo::ast::llvmintrinsic; using xo::reflect::Reflect; using xo::reflect::StructMember; @@ -69,7 +70,8 @@ namespace xo { } /*make*/ MachPipeline::MachPipeline(std::unique_ptr jit) - : jit_{std::move(jit)} + : jit_{std::move(jit)}, + global_env_{GlobalEnv::make()} { this->recreate_llvm_ir_pipeline(); } @@ -627,42 +629,6 @@ namespace xo { } /*create_entry_block_alloca*/ - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, - llvm::StructType * frame_llvm_type) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn)); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - if (!frame_llvm_type) - return nullptr; - - if (log) { - std::string llvm_frame_type_str; - llvm::raw_string_ostream ss(llvm_frame_type_str); - frame_llvm_type->print(ss); - - log(xtag("frame_llvm_type", llvm_frame_type_str)); - } - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, - nullptr, - llvm_fn->getName()); - - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_frame_alloca*/ - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -688,7 +654,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - global_env_[lambda->name()] = lambda.get(); + this->global_env_->require_global(lambda->name(), lambda); /* do we already know a function with this name? */ auto * fn = llvm_module_->getFunction(lambda->name()); @@ -736,175 +702,6 @@ namespace xo { return fn; } /*codegen_lambda_decl*/ - namespace { - /** A function type: - * - * _baseframe* (*) (_baseframe* - **/ - llvm::FunctionType * - require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, - llvm::PointerType * frameptr_llvm_type) - { - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(2); - - /* 1st arg is frame pointer */ - llvm_argtype_v.push_back(frameptr_llvm_type); - - /* 2nd arg is an i32. - * 0 -> unwind. - * 1 -> lift to heap (someday) - */ - llvm_argtype_v.push_back - (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); - - /* return value is frame pointer */ - llvm::Type * retval_llvm_type = frameptr_llvm_type; - - auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, - llvm_argtype_v, - false /*!varargs*/); - - return unwind_llvm_type; - } /*require_baseframe_unwind_llvm_type*/ - - /** Each lambda gets its own stack frame definition. - * However all the various frame representations share the same 'baseframe' - * prefix. - * - * _baseframe: - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) - * +-------+ - * - * This helper function generates an llvm::Type* for a baseframe. - * It only needs to be invoked once (per LlvmContext, I guess ..) - **/ - llvm::StructType * - require_baseframe_llvm_type(xo::ref::brw llvm_cx) - { - /* _baseframe: base type for a stack frame */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_baseframe"); - - /* _baseframe*: pointer to a stack frame */ - llvm::PointerType * frameptr_llvm_type - = llvm::PointerType::getUnqual(frame_llvm_type); - - /* unwind function = frame[1] */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - frameptr_llvm_type); - - /* _baseframe members */ - std::vector llvm_membertype_v; - { - llvm_membertype_v.reserve(2); - - /* frame[0] = pointer to next frame */ - llvm_membertype_v.push_back(frameptr_llvm_type); - /* frame[1] = unwind function */ - llvm_membertype_v.push_back(unwind_llvm_type); - } - - frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, - unwind_llvm_type /*frame[1]*/); - - return frame_llvm_type; - } /*require_baseframe_llvm_type*/ - - /** need a supporting type for stack frame - * - so we can handle variables with non-trivial dtors - * (e.g. smart pointers) - * - so we can implement nested lexical scoping - * - so we can walk stack for exception handling - * - eventually: so we can eventually implement trampoline - * - * frame representation: - * - * ^ - * | - * +-------+ | - * next_frame [0] | o-------/ - * +-------| - * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) - * +-------| - * arg[i] [2+i] | ... | - * +-------+ - * - * invoke frame.unwind_fn to dispose of a frame - * - ctl=0 dtor. deal with smart pointers etc. - * - ctl=1 copy. lift frame into heap for lambda capture - * - * every frame is a subtype of _baseframe (ofc llvm doesn't know this). - * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above - * - * editor bait: activation_record_to_llvm_type - **/ - llvm::StructType * - frame_to_llvm_type(xo::ref::brw llvm_cx, - ref::brw lambda, - llvm::Function * lambda_llvm_fn) - { - constexpr bool c_debug_flag = true; - using xo::scope; - - scope log(XO_DEBUG(c_debug_flag)); - - /* frame type doesn't need a name */ - llvm::StructType * frame_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); - - /* _baseframe */ - llvm::StructType * baseframe_llvm_type - = require_baseframe_llvm_type(llvm_cx); - - /* _baseframe*: pointer to a generic stack frame */ - llvm::PointerType * baseframeptr_llvm_type - = llvm::PointerType::getUnqual(baseframe_llvm_type); - - /* _baseframe* (*)(_baseframe*, i32) */ - llvm::FunctionType * unwind_llvm_type - = require_baseframe_unwind_llvm_type(llvm_cx, - baseframeptr_llvm_type); - - /* llvm_argtype_v: - * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame - * - llvm_argtype_v[1] = llvm::Type* for unwind_fn - * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) - */ - std::vector llvm_argtype_v; - { - llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); - - /* frame pointer */ - llvm_argtype_v.push_back(baseframeptr_llvm_type); - /* unwind function */ - llvm_argtype_v.push_back(unwind_llvm_type); - - int i_arg = 0; - for (auto & arg : lambda_llvm_fn->args()) { - log && log(xtag("i_arg", i_arg), - xtag("param", std::string(arg.getName()))); - - llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, - lambda->fn_arg(i_arg))); - - ++i_arg; - } - } - - frame_llvm_type->setBody(llvm_argtype_v); - - return frame_llvm_type; - } /*frame_to_llvm_type*/ - } /*namespace*/ - llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, llvm::IRBuilder<> & ir_builder) @@ -915,7 +712,7 @@ namespace xo { scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); - global_env_[lambda->name()] = lambda.get(); + global_env_->require_global(lambda->name(), lambda.get()); /* do we already know a function with this name? */ auto * llvm_fn = llvm_module_->getFunction(lambda->name()); @@ -929,30 +726,17 @@ namespace xo { return nullptr; } + /* generate function body */ auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); ir_builder.SetInsertPoint(block); - /* create stack frame */ - llvm::StructType * frame_llvm_type - = frame_to_llvm_type(llvm_cx_, - lambda, - llvm_fn); - - llvm::AllocaInst * frame_alloca = create_entry_frame_alloca(llvm_fn, - frame_llvm_type); - - if (!frame_alloca) - return nullptr; - /** Actual parameters will need their own activation record. * Track its shape here. - * - * Local variables will be formal parameters to a nested lambda; **/ - this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); + this->env_stack_.push(activation_record()); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -965,7 +749,6 @@ namespace xo { std::string arg_name = std::string(arg.getName()); -#ifdef OBSOLETE /* stack location for arg[i] */ llvm::AllocaInst * alloca = create_entry_block_alloca(llvm_fn, @@ -976,45 +759,17 @@ namespace xo { this->env_stack_.pop(); return nullptr; } -#endif - - /* need to store args to stack frame */ - - std::int32_t i_slot = env_stack_.top().lookup_var(arg_name); - - if (i_slot == -1) { - /* variable not found! */ - return nullptr; - } - - llvm::Value * i32_zero = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, 0)); - llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), - llvm::APInt(32, i_slot)); - - std::array index_v = { - {i32_zero /*deref frame pointer*/, - i32_slot /*field# relative to frame pointer*/}}; - - /* location in stack frame of arg #i */ - auto arg_ptr - = ir_builder.CreateInBoundsGEP(frame_llvm_type, - frame_alloca, - index_v); /* store on function entry * see codegen_variable() for corresponding load */ - ir_builder.CreateStore(&arg, - alloca /*destination*/); + ir_builder.CreateStore(&arg, alloca); -#ifdef OBSOLETE /* remember stack location for reference + assignment * in lambda body. * */ env_stack_.top().alloc_var(arg_name, alloca); -#endif ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index cc1aaae3..c7f40362 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -9,22 +9,21 @@ namespace xo { using std::cerr; using std::endl; - int32_t + llvm::AllocaInst * activation_record::lookup_var(const std::string & x) const { - auto ix = name2ix_map_.find(x); + auto ix = frame_.find(x); - if (ix == name2ix_map_.end()) { + if (ix == frame_.end()) { cerr << "activation_record::lookup_var: no binding for variable x" << xtag("x", x) << endl; - return -1; + return nullptr; } return ix->second; } /*lookup_var*/ -#ifdef OBSOLETE llvm::AllocaInst * activation_record::alloc_var(const std::string & x, llvm::AllocaInst * alloca) @@ -39,7 +38,6 @@ namespace xo { frame_[x] = alloca; return alloca; } /*alloc_var*/ -#endif } /*namespace jit*/ } /*namespace xo*/ From 921c70dcd74ccc8a7469aab32687df0cee32f91e Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 18:51:23 -0400 Subject: [PATCH 083/102] xo-jit: basically drop ex_kaleidoscope4, jit .h removed --- example/ex_kaleidoscope4/ex_kaleidoscope4.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp index 63926aad..46f0c81d 100644 --- a/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp +++ b/example/ex_kaleidoscope4/ex_kaleidoscope4.cpp @@ -1,6 +1,6 @@ /** ex_kaleidoscop4.cpp **/ -#include "xo/jit/KaleidoscopeJit.hpp" +#include "xo/jit/Jit.hpp" #include int @@ -8,9 +8,9 @@ main() { using std::cerr; using std::endl; - auto jit = xo::jit::KaleidoscopeJIT::Create(); + //auto jit = xo::jit::KaleidoscopeJIT::Create(); - cerr << "created kaleidoscope jit successfully" << endl; + //cerr << "created kaleidoscope jit successfully" << endl; } /** end ex_kaleidoscope4.cpp **/ From fdc5d46fd7b340c716ee4a26aa89c96516fc0241 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 5 Jul 2024 20:26:07 -0400 Subject: [PATCH 084/102] xo-jit: + runtime_binding_path, ++ to activation_record --- include/xo/jit/activation_record.hpp | 82 ++++++++++++++++++++++++++-- src/jit/MachPipeline.cpp | 4 +- src/jit/activation_record.cpp | 8 ++- 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index c2aba2dd..15ee76f5 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -6,6 +6,7 @@ #pragma once #include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" # include @@ -16,21 +17,92 @@ namespace xo { namespace jit { - /** scope for a stack frame associated with a user-defined function + /** 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 local(int j_rt_slot) { + return runtime_binding_path(0, j_rt_slot); + } + + public: + /** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/ + int i_rt_link_ = -2; + /** slot# within runtime environment where this variable bound. + * (local vars only -- ignored for global vars) + **/ + int j_rt_slot_ = 0; + }; + + /** + * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * - * each function needs its own IR builder, to keep track of things like insert point + * 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. 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 + * b. 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: - activation_record() = default; + using Lambda = xo::ast::Lambda; + + public: + activation_record(const ref::rp & lm) + : lambda_{lm} {} llvm::AllocaInst * lookup_var(const std::string & var_name) const; - llvm::AllocaInst * alloc_var(const std::string & var_name, + /** + * @p j_slot index number (0-based) for var_name in formal parameter list for + * its originating lambda + **/ + llvm::AllocaInst * alloc_var(std::size_t j_slot, + const std::string & var_name, llvm::AllocaInst * alloca); private: - /** maps named slots in a stack frame to logical addresses **/ + /** 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. + * + **/ + ref::rp lambda_; + + /** @c binding_v_[i] specifies how/where to get location for formal parameter number *i* + * of @ref lambda_. + * + **/ + std::vector binding_v_; + + /** maps named slots in a stack frame to logical addresses. + * Only applies to not-captured vars with i_rt_link_=0 + **/ std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9c6bb1cd..041f52b4 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -736,7 +736,7 @@ namespace xo { /** Actual parameters will need their own activation record. * Track its shape here. **/ - this->env_stack_.push(activation_record()); + this->env_stack_.push(activation_record(lambda.get())); { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -769,7 +769,7 @@ namespace xo { * in lambda body. * */ - env_stack_.top().alloc_var(arg_name, alloca); + env_stack_.top().alloc_var(i, arg_name, alloca); ++i; } } diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index c7f40362..4b6a77a0 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -25,7 +25,8 @@ namespace xo { } /*lookup_var*/ llvm::AllocaInst * - activation_record::alloc_var(const std::string & x, + activation_record::alloc_var(std::size_t j_slot, + const std::string & x, llvm::AllocaInst * alloca) { if (frame_.find(x) != frame_.end()) { @@ -35,6 +36,11 @@ namespace xo { return nullptr; } + if (j_slot >= binding_v_.size()) + binding_v_.resize(j_slot + 1); + + binding_v_[j_slot] = runtime_binding_path::local(j_slot); + frame_[x] = alloca; return alloca; } /*alloc_var*/ From d7192c1d97405a46e93157d35a5fe0c9d0fb3a99 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 13:27:12 -0400 Subject: [PATCH 085/102] xo-jit: + explicit env for captured function args [wip, not tested] --- include/xo/jit/MachPipeline.hpp | 2 + include/xo/jit/activation_record.hpp | 135 ++++++++-- include/xo/jit/type2llvm.hpp | 181 +++++++++++++ src/jit/CMakeLists.txt | 1 + src/jit/MachPipeline.cpp | 217 ++-------------- src/jit/activation_record.cpp | 372 ++++++++++++++++++++++++++- src/jit/type2llvm.cpp | 305 ++++++++++++++++++++++ 7 files changed, 992 insertions(+), 221 deletions(-) create mode 100644 include/xo/jit/type2llvm.hpp create mode 100644 src/jit/type2llvm.cpp diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 11589e8c..88c9f544 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -161,6 +161,7 @@ namespace xo { 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 @@ -170,6 +171,7 @@ namespace xo { 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 **/ diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 15ee76f5..82763e6c 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -28,31 +28,61 @@ namespace xo { 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; - /** slot# within runtime environment where this variable bound. + /** >= 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; + }; + /** * 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 + * if lm->needs_closure_flag() is false, then: * - * a. all formal parameters of lm + * 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 - * b. conversely, the top layer of lm's body has no free variables. + * 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 @@ -70,20 +100,76 @@ namespace xo { class activation_record { public: using Lambda = xo::ast::Lambda; + using TypeDescr = xo::reflect::TypeDescr; public: - activation_record(const ref::rp & lm) - : lambda_{lm} {} + activation_record(const ref::rp & lm); - llvm::AllocaInst * lookup_var(const std::string & var_name) const; + const ref::rp lambda() const { return lambda_; } - /** - * @p j_slot index number (0-based) for var_name in formal parameter list for - * its originating lambda + /** retrieve @c llvm::Value* representing the primary stack location + * for formal parameter @p var_name **/ - llvm::AllocaInst * alloc_var(std::size_t j_slot, - const std::string & var_name, - llvm::AllocaInst * alloca); + 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 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 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 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 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_. @@ -94,16 +180,27 @@ namespace xo { **/ ref::rp lambda_; - /** @c binding_v_[i] specifies how/where to get location for formal parameter number *i* - * of @ref lambda_. - * + /** @c binding_v_[i] specifies how/where we mean to navigate to + * location for formal parameter number *i* of @ref lambda_. **/ std::vector binding_v_; - /** maps named slots in a stack frame to logical addresses. - * Only applies to not-captured vars with i_rt_link_=0 + /** if this function requires an explicit environment, + * gives stack location for that environment. **/ - std::map frame_; /* <-> kaleidoscope NamedValues */ + llvm::AllocaInst * localenv_alloca_ = nullptr; + + /** maps named slots in a stack frame to logical addresses. + * + * - For captured arguments: will refer to slot within stack-allocated local environment + * (an llvm::StructType, created by type2llvm::create_localenv_llvm_type()) + * + * - For non-captured arguments: will refer to stack-allocated argument copy + * + * In either case using copy-to-stack to evade directly confronting + * so we don't have to comply with llvm IR's SSA requirement. + **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ }; /*activation_record*/ } /*namespace jit*/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp new file mode 100644 index 00000000..2d532893 --- /dev/null +++ b/include/xo/jit/type2llvm.hpp @@ -0,0 +1,181 @@ +/** @file type2llvm.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "LlvmContext.hpp" +#include "xo/expression/Lambda.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include +//#include + +namespace xo { + namespace jit { + /** + **/ + struct type2llvm { + public: + 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 llvm_cx, + TypeDescr td); + + /** establish llvm representation for a function type + * described by @p fn_td + **/ + static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** 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 + **/ + static llvm::StructType * + create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw 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 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 llvm_cx, + llvm::PointerType * hint_envptr_llvm_type = nullptr); + + private: + /** establish llvm representation for a function-pointer type + * described by @p fn_td + **/ + static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + /** establish llvm representation for a struct type described by @p struct_td + **/ + static llvm::StructType * struct_td_to_llvm_type(xo::ref::brw 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 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 llvm_cx); + + /** 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_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td); + + }; /*type2llvm*/ + } /*namespace jit*/ +} /*namespace xo*/ + +/** end type2llvm.hpp **/ diff --git a/src/jit/CMakeLists.txt b/src/jit/CMakeLists.txt index 4168eebe..c52f7116 100644 --- a/src/jit/CMakeLists.txt +++ b/src/jit/CMakeLists.txt @@ -7,6 +7,7 @@ set(SELF_SRCS MachPipeline.cpp intrinsics.cpp activation_record.cpp + type2llvm.cpp ) xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 041f52b4..3bd895f0 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -1,6 +1,8 @@ /* @file MachPipeline.cpp */ #include "MachPipeline.hpp" +#include "activation_record.hpp" +#include "type2llvm.hpp" #include namespace xo { @@ -156,187 +158,9 @@ namespace xo { return nullptr; } /*codegen_constant*/ - namespace { - /** REMINDER: - * 1. creation of llvm types is idempotent - * (duplicate calls will receive the same llvm::Type* pointer) - * 2. llvm::Types are never deleted. - **/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); - - /** obtain llvm representation for a function type with the same signature as - * that represented by @p fn_td - **/ - llvm::FunctionType * - function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - int n_fn_arg = fn_td->n_fn_arg(); - - std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); - - /** check function args are all known **/ - for (int i = 0; i < n_fn_arg; ++i) { - TypeDescr arg_td = fn_td->fn_arg(i); - - llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); - - if (!llvm_argtype) - return nullptr; - - llvm_argtype_v.push_back(llvm_argtype); - } - - TypeDescr retval_td = fn_td->fn_retval(); - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); - - if (!llvm_retval) - return nullptr; - - auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, - llvm_argtype_v, - false /*!varargs*/); - return llvm_fn_type; - } - - llvm::PointerType * - function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) - { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); - - /** like C: llvm IR doesn't support function-valued variables; - * it does however support pointer-to-function-valued variables - **/ - auto * llvm_ptr_type - = llvm::PointerType::get(llvm_fn_type, - 0 /*numbered address space*/); - - return llvm_ptr_type; - } - - /** - * Generate llvm::Type correspoinding to a TypeDescr for a struct. - **/ - llvm::StructType * - struct_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr struct_td) - { - // see - // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] - - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - /* note: object pointer ignored for struct types, - * since number of members is known at compile time - */ - int n_member = struct_td->n_child(nullptr /*&object*/); - - /* one type for each struct member */ - std::vector llvm_membertype_v; - llvm_membertype_v.reserve(n_member); - - for (int i = 0; i < n_member; ++i) { - StructMember const & sm = struct_td->struct_member(i); - - llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, - sm.get_member_td())); - } - - std::string struct_name = std::string(struct_td->short_name()); - - /* structs with names: within an llvmcontext, must be unique - * - * We can however compare the offsets recorded in xo::reflect with - * offsets chosen by llvm, *once we've created the llvm type* - * - * Also, we can't guarantee that a c++ type was completely reflected -- - * it's possible one or more members were omitted, in which case - * it's unlikely at best that llvm chooses the same layout. - * - * Instead: tell llvm to make packed struct, - * and introduce dummy members for padding. - * - * A consequence is we have to maintain mapping between llvm's - * member numbering and xo::reflect's - */ - llvm::StructType * llvm_struct_type - = llvm::StructType::create(llvm_cx_ref, - llvm_membertype_v, - llvm::StringRef(struct_name), - false /*!isPacked*/); - - /* TODO: inspect (how) offsets that llvm is using - * we need them to match what C++ chose - * - * (because we want jitted llvm code to interoperate with - * C++ library code that has structs) - */ - - // GetElementPtrInst is interesting, - // but I think that's for generating code - - return llvm_struct_type; - } /*struct_td_to_llvm_type*/ - - llvm::PointerType * - pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) - { - assert(pointer_td->is_pointer()); - - TypeDescr dest_td = pointer_td->fixed_child_td(0); - - llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); - - llvm::PointerType * llvm_ptr_type - = llvm::PointerType::getUnqual(llvm_dest_type); - - return llvm_ptr_type; - } /*pointer_td_llvm_type*/ - - llvm::Type * - td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { - auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); - - if (td->is_function()) { - /* in this context, we're looking for a representation for a value, - * i.e. something that can be stored in a variable - */ - return function_td_to_llvm_fnptr_type(llvm_cx, td); - } else if (td->is_struct()) { - return struct_td_to_llvm_type(llvm_cx, td); - } else if (td->is_pointer()) { - return pointer_td_to_llvm_type(llvm_cx, td); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt1Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt8Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt16Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt32Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getInt64Ty(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getFloatTy(llvm_cx_ref); - } else if (Reflect::is_native(td)) { - return llvm::Type::getDoubleTy(llvm_cx_ref); - } else { - cerr << "td_to_llvm_type: no llvm type available for T" - << xtag("T", td->short_name()) - << endl; - return nullptr; - } - } - } - llvm::Type * MachPipeline::codegen_type(TypeDescr td) { - return td_to_llvm_type(llvm_cx_.borrow(), td); + return type2llvm::td_to_llvm_type(llvm_cx_.borrow(), td); } llvm::Function * @@ -368,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -577,7 +401,7 @@ namespace xo { */ llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); return ir_builder.CreateCall(llvm_fn_type, llvm_fnval, @@ -586,6 +410,7 @@ namespace xo { } /*codegen_apply*/ +#ifdef OBSOLETE /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ llvm::AllocaInst * MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, @@ -603,8 +428,8 @@ namespace xo { llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), llvm_fn->getEntryBlock().begin()); - llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), - var_type); + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx_.borrow(), + var_type); log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); if (log) { @@ -627,6 +452,7 @@ namespace xo { return retval; } /*create_entry_block_alloca*/ +#endif std::vector> @@ -678,8 +504,8 @@ namespace xo { #endif llvm::FunctionType * llvm_fn_type - = function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, @@ -734,10 +560,18 @@ namespace xo { ir_builder.SetInsertPoint(block); /** Actual parameters will need their own activation record. - * Track its shape here. + * Track its shape + setup/teardown here. **/ this->env_stack_.push(activation_record(lambda.get())); + bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, ir_builder); + + if (!ok_flag) { + this->env_stack_.pop(); + return nullptr; + } + +#ifdef OBSOLETE { log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); @@ -773,6 +607,7 @@ namespace xo { ++i; } } +#endif llvm::Value * retval = this->codegen(lambda->body(), ir_builder); @@ -827,14 +662,16 @@ namespace xo { return nullptr; } - llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + activation_record & ar = env_stack_.top(); - if (!alloca) + const runtime_binding_detail * binding = ar.lookup_var(var->name()); + + if (!binding) return nullptr; /* code to load value from stack */ - return ir_builder.CreateLoad(alloca->getAllocatedType(), - alloca, + return ir_builder.CreateLoad(binding->llvm_type_, + binding->llvm_addr_, var->name().c_str()); } /*codegen_variable*/ diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 4b6a77a0..413412af 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -1,6 +1,7 @@ /* @file activation_record.cpp */ #include "activation_record.hpp" +#include "type2llvm.hpp" #include "xo/indentlog/print/tag.hpp" #include @@ -9,7 +10,29 @@ namespace xo { using std::cerr; using std::endl; - llvm::AllocaInst * + activation_record::activation_record(const ref::rp & lm) + : lambda_{lm}, + binding_v_(lm->n_arg()) + { + /* populate binding_v_ */ + int n_arg = lm->n_arg(); + binding_v_.resize(n_arg); + + /* next slot# to use in explicit activation record */ + int rt_env_slot = 0; + + for (int i_arg = 0; i_arg < n_arg; ++i_arg) { + if (lm->is_captured(lm->i_argname(i_arg))) { + /* local param #i_arg needs a slot in explicit activation record */ + binding_v_[i_arg] = runtime_binding_path::local(rt_env_slot); + ++rt_env_slot; + } else { + binding_v_[i_arg] = runtime_binding_path::stackonly(); + } + } + } /*ctor*/ + + const runtime_binding_detail * activation_record::lookup_var(const std::string & x) const { auto ix = frame_.find(x); @@ -21,13 +44,12 @@ namespace xo { return nullptr; } - return ix->second; + return &(ix->second); } /*lookup_var*/ - llvm::AllocaInst * - activation_record::alloc_var(std::size_t j_slot, - const std::string & x, - llvm::AllocaInst * alloca) + const runtime_binding_detail * + activation_record::alloc_var(const std::string & x, + const runtime_binding_detail & binding) { if (frame_.find(x) != frame_.end()) { cerr << "activation_record::alloc_var: variable x already present in frame" @@ -36,14 +58,340 @@ namespace xo { return nullptr; } - if (j_slot >= binding_v_.size()) - binding_v_.resize(j_slot + 1); + frame_[x] = binding; - binding_v_[j_slot] = runtime_binding_path::local(j_slot); - - frame_[x] = alloca; - return alloca; + return &(frame_[x]); } /*alloc_var*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + runtime_binding_detail + activation_record::create_entry_block_alloca(ref::brw 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), + xtag("llvm_fn", (void*)llvm_fn), + 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 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 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 * i32_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, + i_slot /*value*/)); + std::array index_v = { + {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 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()) { + 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].is_stackonly())); + + if (binding_v_[i_arg].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)); + + 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()) { + 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].is_captured())); + + if (binding_v_[i_arg].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); + + 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*/ diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp new file mode 100644 index 00000000..23ee48c4 --- /dev/null +++ b/src/jit/type2llvm.cpp @@ -0,0 +1,305 @@ +/* @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 llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } /*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_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } /*function_td_to_llvm_type*/ + + llvm::PointerType * + type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + type2llvm::struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(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 llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + 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); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::PointerType * + type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::ref::brw llvm_cx, + llvm::PointerType * envptr_llvm_type) + { + if (!envptr_llvm_type) + envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is _env_api pointer */ + llvm_argtype_v.push_back(envptr_llvm_type); + + /* 2nd arg is i32 */ + llvm_argtype_v.push_back(llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is _env_api pointer */ + llvm::Type * retval_llvm_type = envptr_llvm_type; + + /* _env_api* (_env_api*, i32) */ + auto * unwind_llvm_type + = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + /* _env_api* (*)(_env_api*, i32) */ + auto * unwind_llvm_fnptr_type + = llvm::PointerType::getUnqual(unwind_llvm_type); + + return unwind_llvm_fnptr_type; + } /*require_localenv_unwind_llvm_fnptr_type*/ + + llvm::StructType * + type2llvm::env_api_llvm_type(xo::ref::brw 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"); + + /* _env_api[0]: pointer to a local environment */ + llvm::PointerType * envptr_llvm_type + = llvm::PointerType::getUnqual(env_llvm_type); + + /* _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 llvm_cx) + { + llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(env_llvm_type); + } /*env_api_llvm_ptr_type*/ + +#ifdef NOT_USING + llvm::StructType * + type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* closure type doesn't need a name. + * (We might find it convenient to give one anyway) + */ + llvm::StructType * closure_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + llvm::PointerType * parent_llvm_type + } /*function_td_to_llvm_fnptr_type*/ +#endif + + llvm::StructType * + type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); + llvm::PointerType * unwind_llvm_fnptr_type + = type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type); + + std::vector member_llvm_type_v; + member_llvm_type_v.push_back(parentenvptr_llvm_type); + member_llvm_type_v.push_back(unwind_llvm_fnptr_type); + + for (const auto & var : lambda->argv()) { + if (lambda->is_captured(var->name())) { + /* var needs a slot in localenv_llvm_type for lambda */ + + member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx, + var->valuetype())); + } + } + + /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ + llvm::StructType * localenv_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + localenv_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + + return localenv_llvm_type; + } /*create_localenv_llvm_type*/ + + } /*namespace jit*/ +} /*namespace xo*/ + +/* end type2llvm.cpp */ From 4c8289336d4a291432f2fa4fcb0f2c113e3bad91 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 16:57:05 -0400 Subject: [PATCH 086/102] xo-jit: + primitive wrapper (accept+ignore envptr as 1st argument) --- include/xo/jit/MachPipeline.hpp | 10 +++ include/xo/jit/type2llvm.hpp | 10 ++- src/jit/MachPipeline.cpp | 130 +++++++++++++++++++++++++++----- src/jit/type2llvm.cpp | 8 +- 4 files changed, 138 insertions(+), 20 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 88c9f544..ed81c6c8 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -111,6 +111,16 @@ namespace xo { llvm::Type * codegen_type(TypeDescr td); llvm::Value * codegen_constant(ref::brw expr); llvm::Function * codegen_primitive(ref::brw 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 expr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_apply(ref::brw expr, llvm::IRBuilder<> & ir_builder); /* NOTE: codegen_lambda() needs to be reentrant too. * for example can have a lambda in apply position. diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 2d532893..dffe155b 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -29,9 +29,17 @@ namespace xo { /** 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_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag = false); /** establish llvm concrete representation for a particular lambda's * runtime local environment: diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 3bd895f0..fc44ae88 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -20,6 +20,7 @@ namespace xo { using xo::reflect::Reflect; using xo::reflect::StructMember; using xo::reflect::TypeDescr; + using xo::scope; using llvm::orc::ExecutionSession; using llvm::DataLayout; using std::cerr; @@ -167,7 +168,6 @@ namespace xo { MachPipeline::codegen_primitive(ref::brw expr) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag)); @@ -249,12 +249,123 @@ namespace xo { return fn; } /*codegen_primitive*/ + llvm::Function * + MachPipeline::codegen_primitive_wrapper(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + + scope log(XO_DEBUG(c_debug_flag), + xtag("primitive-name", expr->name())); + + constexpr const char * c_prefix = "w."; + + /* unique name for wrapper. Note we don't allow period in schematica identifiers + * (though we could if we replace . with .. when lowering) + */ + std::string wrap_name = std::string(c_prefix) + expr->name(); + + /* original primitive */ + auto * native_lvfn = codegen_primitive(expr); + + /* wrapped primitive */ + auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); + + if (wrap_lvfn) { + /* wrapper already defined */ + return wrap_lvfn; + } + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * native_lvtype + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!native_lvtype) + return nullptr; + + llvm::FunctionType * wrapper_lvtype + = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + fn_td, + true /*wrapper_flag (for closure)*/); + + wrap_lvfn = llvm::Function::Create(wrapper_lvtype, + llvm::Function::ExternalLinkage, + wrap_name, + llvm_module_.get()); + + /* at least we know the name of the 1st argument :) */ + auto ix = wrap_lvfn->args().begin(); + ix->setName(".env"); + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "entry", wrap_lvfn); + + ir_builder.SetInsertPoint(block); + + std::vector args; + + /* call to native_lvfn, + * forwarding all args of wrap_lvfn, except the first + */ + { + args.reserve(expr->n_arg()); + + int i_wrap_arg = 0; + for (auto & arg : wrap_lvfn->args()) { + if (i_wrap_arg > 0) + args.push_back(&arg); + + ++i_wrap_arg; + } + } + + /* {caller,callee} must agree on calling convention, + * so for primitives we need to assume c. + */ + llvm::CallInst * call = ir_builder.CreateCall(native_lvtype, + native_lvfn, + args, + "w.calltmp"); + if (call) { + call->setTailCall(true); + + /* does this work if call returns void? Is this needed with tail call? */ + ir_builder.CreateRet(call); + + llvm::verifyFunction(*wrap_lvfn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + wrap_lvfn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! */ + ir_pipeline_->run_pipeline(*wrap_lvfn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + wrap_lvfn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + wrap_lvfn->eraseFromParent(); + wrap_lvfn = nullptr; + } + + return wrap_lvfn; + } /*codegen_primitive_wrapper*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("apply", apply)); @@ -418,7 +529,6 @@ namespace xo { TypeDescr var_type) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("llvm_fn", (void*)llvm_fn), @@ -475,7 +585,6 @@ namespace xo { MachPipeline::codegen_lambda_decl(ref::brw lambda) { constexpr bool c_debug_flag = true; - using xo::scope; scope log(XO_DEBUG(c_debug_flag), xtag("lambda-name", lambda->name())); @@ -491,18 +600,6 @@ namespace xo { /* establish prototype for this function */ -#ifdef OBSOLETE - llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_retval()); - - std::vector arg_type_v(lambda->n_arg()); - - for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { - arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), - lambda->fn_arg(i)); - } -#endif - llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), lambda->valuetype()); @@ -533,7 +630,6 @@ namespace xo { 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())); diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 23ee48c4..fdfda7d5 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,12 +58,16 @@ namespace xo { **/ llvm::FunctionType * type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { int n_fn_arg = fn_td->n_fn_arg(); std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg); + llvm_argtype_v.reserve(n_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_fn_arg; ++i) { From f2fa9978cf558ade904b083d401c12ed75da9dd0 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 7 Jul 2024 18:54:56 -0400 Subject: [PATCH 087/102] xo-jit: + unit test for primitive wrapper --- utest/MachPipeline.test.cpp | 61 ++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 5e0fafc4..880aabf1 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -183,7 +183,66 @@ namespace xo { REQUIRE(actual == expected); } } - } /*TEST_CASE(machpipeline)*/ + } /*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(llvm_addr); + + if (!llvm_addr_flag) { + cerr << "ex2: lookup: symbol not found" + << xtag("symbol", wrapper_name) + << endl; + } else { + cerr << "ex2: lookup: symbol found" + << xtag("llvm_addr", llvm_addr.get().getValue()) + << xtag("symbol", wrapper_name) + << endl; + } + + REQUIRE(llvm_addr_flag); + + auto fn_ptr = llvm_addr.get().toPtr(); + + REQUIRE(fn_ptr); + + auto actual = (*fn_ptr)(nullptr, 4.0); + + REQUIRE(actual == 2.0); + } rp make_ratio() { From 792dcf015713e9817ab1f3988c82a1bba44850b3 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:45:58 -0400 Subject: [PATCH 088/102] xo-jit: + type2llvm::create_closure_lvtype() --- include/xo/jit/type2llvm.hpp | 24 ++++++++++++++++++++++++ src/jit/type2llvm.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index dffe155b..75f33576 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -41,6 +41,28 @@ namespace xo { TypeDescr fn_td, bool wrapper_flag = false); + /** 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 lambda with name @c foo + **/ + static llvm::StructType * + create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda); + /** establish llvm concrete representation for a particular lambda's * runtime local environment: * @@ -62,6 +84,8 @@ namespace xo { * * 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 llvm_cx, diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index fdfda7d5..a5405f86 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -303,6 +303,36 @@ namespace xo { return localenv_llvm_type; } /*create_localenv_llvm_type*/ + llvm::StructType * + type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw lambda) + { + constexpr const char * c_prefix = "c."; + + /* 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, + lambda->valuetype()); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + + std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + lambda->name(); + + llvm::StructType * closure_lvtype + = llvm::StructType::create(llvm_cx->llvm_cx_ref(), + member_lvtype_v, + llvm::StringRef(closure_name), + false /*!is_packed*/); + + return closure_lvtype; + } /*create_closure_lvtype*/ + } /*namespace jit*/ } /*namespace xo*/ From 56b924a286ce277db51fb4cc23e220ac761d7c87 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:46:18 -0400 Subject: [PATCH 089/102] xo-jit: gen lvtype name in type2llvm::create_localenv_llvm_type() --- src/jit/type2llvm.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index a5405f86..231d6de4 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -277,6 +277,8 @@ namespace xo { type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, xo::ref::brw 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); @@ -294,13 +296,16 @@ namespace xo { } } - /* this type doesn't need a name, right? would be "_" + lambda name + "_localenv" */ - llvm::StructType * localenv_llvm_type + /* 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_llvm_type->setBody(member_llvm_type_v, false /*!is_packed*/); + localenv_lvtype->setName(env_name); + localenv_lvtype->setBody(member_llvm_type_v, false /*!is_packed*/); - return localenv_llvm_type; + return localenv_lvtype; } /*create_localenv_llvm_type*/ llvm::StructType * From 19d8a5e846d5b09a2b787b05bdb413963bfc2e40 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 11:47:03 -0400 Subject: [PATCH 090/102] xo-jit: doc: + glossary entries --- docs/glossary.rst | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index c5a22ec1..37b98af9 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,49 @@ Glossary -------- .. glossary:: + 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 From 659c0c400b3fe5478d2689bb78078a46260b6005 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:06 -0400 Subject: [PATCH 091/102] xo-jit: refactor: + closures [wip: not tested] --- include/xo/jit/MachPipeline.hpp | 37 +++- include/xo/jit/type2llvm.hpp | 103 ++++++----- src/jit/MachPipeline.cpp | 297 ++++++++++++++++++-------------- src/jit/activation_record.cpp | 129 +++++++------- src/jit/type2llvm.cpp | 90 +++++----- 5 files changed, 376 insertions(+), 280 deletions(-) diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index ed81c6c8..4e0714e9 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -121,16 +121,45 @@ namespace xo { llvm::Function * codegen_primitive_wrapper(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_apply(ref::brw 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 expr, + llvm::IRBuilder<> & ir_builder); + + llvm::Value * codegen_apply(ref::brw 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 expr); llvm::Function * codegen_lambda_defn(ref::brw expr, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_variable(ref::brw var, llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen_ifexpr(ref::brw ifexpr, 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 lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_variable(ref::brw var, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen_ifexpr(ref::brw ifexpr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); - llvm::Value * codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder); + llvm::Value * codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder); llvm::Value * codegen_toplevel(ref::brw expr); diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 75f33576..d7a04f05 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -17,6 +17,7 @@ namespace xo { **/ struct type2llvm { public: + using FunctionInterface = xo::ast::FunctionInterface; using Lambda = xo::ast::Lambda; using TypeDescr = xo::reflect::TypeDescr; @@ -57,11 +58,55 @@ namespace xo { * unwind_fn [1] | o-------> env * (*)(env*, ctl) * +-------+ * - * @return struct type. typename will be @c c.foo for lambda with name @c foo + * @return struct type. typename will be @c c.foo for a function + * (primitive or lambda) with name @c foo **/ static llvm::StructType * - create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda); + create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw 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 llvm_cx, + TypeDescr fn_td, + const std::string & hint_name); /** establish llvm concrete representation for a particular lambda's * runtime local environment: @@ -128,9 +173,17 @@ namespace xo { private: /** 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 llvm_cx, - TypeDescr fn_td); + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ @@ -164,48 +217,6 @@ namespace xo { static llvm::StructType * env_api_llvm_type(xo::ref::brw llvm_cx); - /** 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_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td); - }; /*type2llvm*/ } /*namespace jit*/ } /*namespace xo*/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index fc44ae88..b458f9fb 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -361,8 +361,29 @@ namespace xo { return wrap_lvfn; } /*codegen_primitive_wrapper*/ + llvm::Value * + MachPipeline::codegen_primitive_closure(ref::brw expr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); + + llvm::Function * pm_wrapper = codegen_primitive_wrapper(expr, ir_builder); + llvm::Value * env_0ptr = llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx_)); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + pm_wrapper, {0}, "wrapfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + env_0ptr, {1}, "nullenvptr" /*name*/); + + return lv_closure; + } /*codegen_primitive_closure*/ + llvm::Value * MachPipeline::codegen_apply(ref::brw apply, + llvm::Value * envptr, llvm::IRBuilder<> & ir_builder) { constexpr bool c_debug_flag = true; @@ -376,14 +397,14 @@ namespace xo { using std::cerr; using std::endl; - /* IR for value in function position. - * Although it will generate a function (or pointer-to-function), - * it need not have inherited type llvm::Function. + /* IR for closure in function position + * see: + * - MachPipeline::codegen_primitive_closure + * - MachPipeline::codegen_lambda_closure + * - type2llvm::create_closure_lvtype */ - llvm::Value * llvm_fnval = nullptr; + llvm::Value * llvm_closure = nullptr; llvmintrinsic intrinsic = llvmintrinsic::invalid; - /* function type in apply node's function position */ - TypeDescr ast_fn_td = apply->fn()->valuetype(); { /* special treatement for primitive in apply position: * allows substituting LLVM intrinsic @@ -392,12 +413,12 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_fnval = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive(pm); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } } else { - llvm_fnval = this->codegen(apply->fn(), ir_builder); + llvm_closure = this->codegen(apply->fn(), envptr, ir_builder); /* we don't need any special checking here. * already know (from xo-level checking) that pointer has the right type. @@ -414,10 +435,13 @@ namespace xo { } } - if (!llvm_fnval) { + if (!llvm_closure) { return nullptr; } + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + #ifdef NOT_USING_DEBUG cerr << "MachPipeline::codegen_apply: fn:" << endl; fn->print(llvm::errs()); @@ -452,12 +476,50 @@ namespace xo { } #endif + llvm::StructType * closure_lvtype + = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, + ast_fn_td, + "" /*name - not required*/); + + llvm::Value * lv_fnptr = nullptr; + { + llvm::Value * fnptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + + std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + + lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + + llvm::Value * lv_fnenvptr = nullptr; + { + llvm::Value * envptr_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 1 /*value*/)); + + std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + + lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, + llvm_closure, + index_v); + } + std::vector args; - args.reserve(apply->argv().size()); + /* +1 for envptr */ + args.reserve(1 + apply->argv().size()); + + + /* we must take envptr from closure, + * and we need to do this using some version of getelementptr + */ + args.push_back(lv_fnenvptr); int i = 0; for (const auto & arg_expr : apply->argv()) { - auto * arg = this->codegen(arg_expr, ir_builder); + auto * arg = this->codegen(arg_expr, envptr, ir_builder); if (log) { /* TODO: print helper for llvm::Value* */ @@ -476,26 +538,28 @@ namespace xo { /* if we have an intrinsic hint, * then instead of invoking a function, * we use some native machine instruction instead. + * + * args[0] not used here, that holds envptr from faux closure */ switch(intrinsic) { case llvmintrinsic::i_neg: - return ir_builder.CreateNeg(args[0]); + return ir_builder.CreateNeg(args[1]); case llvmintrinsic::i_add: - return ir_builder.CreateAdd(args[0], args[1]); + return ir_builder.CreateAdd(args[1], args[2]); case llvmintrinsic::i_sub: - return ir_builder.CreateSub(args[0], args[1]); + return ir_builder.CreateSub(args[1], args[2]); case llvmintrinsic::i_mul: - return ir_builder.CreateMul(args[0], args[1]); + return ir_builder.CreateMul(args[1], args[2]); case llvmintrinsic::i_sdiv: - return ir_builder.CreateSDiv(args[0], args[1]); + return ir_builder.CreateSDiv(args[1], args[2]); case llvmintrinsic::i_udiv: - return ir_builder.CreateUDiv(args[0], args[1]); + return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: - return ir_builder.CreateFAdd(args[0], args[1]); + return ir_builder.CreateFAdd(args[1], args[2]); case llvmintrinsic::fp_mul: - return ir_builder.CreateFMul(args[0], args[1]); + return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: - return ir_builder.CreateFDiv(args[0], args[1]); + return ir_builder.CreateFDiv(args[1], args[2]); case llvmintrinsic::invalid: case llvmintrinsic::fp_sqrt: case llvmintrinsic::fp_pow: @@ -506,65 +570,18 @@ namespace xo { break; } - /* At least as of 18.1.5, LLVM needs us to supply function type - * when making a function call. In particular it doesn't remember - * the function type with each function pointer - */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + = type2llvm::function_td_to_llvm_type(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, - llvm_fnval, + lv_fnptr, args, "calltmp"); } /*codegen_apply*/ -#ifdef OBSOLETE - /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ - llvm::AllocaInst * - MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, - const std::string & var_name, - TypeDescr var_type) - { - constexpr bool c_debug_flag = true; - - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); - - llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), - llvm_fn->getEntryBlock().begin()); - - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx_.borrow(), - var_type); - - log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); - if (log) { - std::string llvm_var_type_str; - llvm::raw_string_ostream ss(llvm_var_type_str); - llvm_var_type->print(ss); - - log(xtag("llvm_var_type", llvm_var_type_str)); - } - - if (!llvm_var_type) - return nullptr; - - llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, - nullptr, - var_name); - log && log(xtag("alloca", (void*)retval), - xtag("align", retval->getAlign().value()), - xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); - - return retval; - } /*create_entry_block_alloca*/ -#endif - - std::vector> MachPipeline::find_lambdas(ref::brw expr) const { @@ -600,24 +617,40 @@ namespace xo { /* establish prototype for this function */ + /* wrapper_flag: llvm function type takes extra first argument, + * supplying environment pointer from surrounding closure. + * + * Note that this argument is not present in lambda, + * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] + */ llvm::FunctionType * llvm_fn_type = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype()); + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ fn = llvm::Function::Create(llvm_fn_type, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); - /* also capture argument names */ + + /* also adopt lambda's formal argument names */ { int i = 0; for (auto & arg : fn->args()) { - log && log("llvm formal param names", - xtag("i", i), - xtag("param", lambda->argv().at(i))); + if (i == 0) { + log && log("llvm inserted env param", + xtag("i", i)); + + arg.setName(".env"); + } else { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i-1))); + + arg.setName(lambda->argv().at(i-1)->name()); + } - arg.setName(lambda->argv().at(i)->name()); ++i; } } @@ -648,6 +681,10 @@ namespace xo { return nullptr; } + /* environment for this lambda's clsoure + * passed as extra 1st argument + */ + llvm::Value * envptr = llvm_fn->args().begin(); /* generate function body */ @@ -667,45 +704,9 @@ namespace xo { return nullptr; } -#ifdef OBSOLETE - { - log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); - - int i = 0; - for (auto & arg : llvm_fn->args()) { - log && log("nested environment", - xtag("i", i), - xtag("param", std::string(arg.getName()))); - - std::string arg_name = std::string(arg.getName()); - - /* stack location for arg[i] */ - llvm::AllocaInst * alloca - = create_entry_block_alloca(llvm_fn, - arg_name, - lambda->fn_arg(i)); - - if (!alloca) { - this->env_stack_.pop(); - return nullptr; - } - - /* store on function entry - * see codegen_variable() for corresponding load - */ - ir_builder.CreateStore(&arg, alloca); - - /* remember stack location for reference + assignment - * in lambda body. - * - */ - env_stack_.top().alloc_var(i, arg_name, alloca); - ++i; - } - } -#endif - - llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + llvm::Value * retval = this->codegen(lambda->body(), + envptr, + ir_builder); if (retval) { /* completes the function.. */ @@ -746,10 +747,33 @@ namespace xo { return llvm_fn; } /*codegen_lambda_defn*/ + llvm::Value * + MachPipeline::codegen_lambda_closure(ref::brw lambda, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) + { + llvm::StructType * closure_lvtype + = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); + + llvm::Function * lvfn = codegen_lambda_decl(lambda); + + llvm::Value * lv_closure = nullptr; + + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "envptr" /*name*/); + + return lv_closure; + } /*codegen_lambda_closure*/ + llvm::Value * MachPipeline::codegen_variable(ref::brw var, + llvm::Value * /*envptr*/, llvm::IRBuilder<> & ir_builder) { + /* TODO: navigate envptr to handle non-local variables */ + if (env_stack_.empty()) { cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" << xtag("x", var->name()) @@ -772,9 +796,11 @@ namespace xo { } /*codegen_variable*/ llvm::Value * - MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen_ifexpr(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { - llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + llvm::Value * test_ir = this->codegen(expr->test(), envptr, ir_builder); /** need test result in a variable **/ llvm::Value * test_with_cmp_ir @@ -813,6 +839,7 @@ namespace xo { ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), + envptr, ir_builder); if (!when_true_ir) @@ -827,7 +854,9 @@ namespace xo { parent_fn->insert(parent_fn->end(), when_false_bb); ir_builder.SetInsertPoint(when_false_bb); - llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + llvm::Value * when_false_ir = this->codegen(expr->when_false(), + envptr, + ir_builder); if (!when_false_ir) return nullptr; @@ -852,21 +881,24 @@ namespace xo { } /*codegen_ifexpr*/ llvm::Value * - MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + MachPipeline::codegen(ref::brw expr, + llvm::Value * envptr, + llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { case exprtype::constant: return this->codegen_constant(ConstantInterface::from(expr)); case exprtype::primitive: - return this->codegen_primitive(PrimitiveInterface::from(expr)); + return this->codegen_primitive_closure(PrimitiveInterface::from(expr), ir_builder); case exprtype::apply: - return this->codegen_apply(Apply::from(expr), ir_builder); + return this->codegen_apply(Apply::from(expr), envptr, ir_builder); case exprtype::lambda: - return this->codegen_lambda_decl(Lambda::from(expr)); + return this->codegen_lambda_closure(Lambda::from(expr), envptr, ir_builder); + //return this->codegen_lambda_decl(Lambda::from(expr)); case exprtype::variable: - return this->codegen_variable(Variable::from(expr), ir_builder); + return this->codegen_variable(Variable::from(expr), envptr, ir_builder); case exprtype::ifexpr: - return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + return this->codegen_ifexpr(IfExpr::from(expr), envptr, ir_builder); case exprtype::invalid: case exprtype::n_expr: return nullptr; @@ -910,6 +942,8 @@ namespace xo { this->codegen_lambda_decl(lambda); } +#ifdef OBSOLETE /* don't do this anymore, obscures lexical context */ + /* Pass 2 */ for (auto lambda : fn_v) { this->codegen_lambda_defn(lambda, @@ -931,6 +965,19 @@ namespace xo { return this->codegen(expr, *(this->llvm_toplevel_ir_builder_.get())); } +#endif + + /* 1. using nullptr as runtime representation for global environment + * 2. may have to elaborate this later? not clear to me + */ + + llvm::Value * env_0ptr + = (llvm::ConstantPointerNull::get + (type2llvm::env_api_llvm_ptr_type(llvm_cx_))); + + return this->codegen(expr, + env_0ptr, + *(this->llvm_toplevel_ir_builder_.get())); } /*codegen_toplevel*/ void diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 413412af..be1275ef 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -155,7 +155,7 @@ namespace xo { = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { + std::array index_v = { {i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr @@ -194,37 +194,44 @@ namespace xo { { int i_arg = 0; for (auto & arg : llvm_fn->args()) { - 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].is_stackonly())); - - if (binding_v_[i_arg].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)); - - if (!binding.llvm_addr_) - return false; - - /* store on function entry - * see codegen_variable() for corresponding load + 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. */ - ir_builder.CreateStore(&arg, binding.llvm_addr_); + } else { + std::string arg_name = std::string(arg.getName()); - /* remember stack location for reference + assignment - * in lambda body. - * - */ - this->alloc_var(arg_name, binding); + 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)); + + 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; @@ -297,7 +304,7 @@ namespace xo { int i_localenv_slot = 0; /* store localenv->parent_env */ - { + { llvm::Value * slot_addr = runtime_localenv_slot_addr(llvm_cx, localenv_llvm_type, @@ -348,42 +355,48 @@ namespace xo { int i_arg = 0; for (llvm::Argument & arg : llvm_fn->args()) { - 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].is_captured())); - - if (binding_v_[i_arg].is_captured()) { - // do something with runtime-local-env for this llvm_fn - - /* remember stack location for reference + assignment - * in lambda body. - * + 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()); - TypeDescr arg_td = lambda_->fn_arg(i_arg); + log && log("nested environment", + xtag("i", i_arg), + xtag("arg[i]", arg_name), + xtag("captured(i)", binding_v_[i_arg-1].is_captured())); - llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); + if (binding_v_[i_arg-1].is_captured()) { + // do something with runtime-local-env for this llvm_fn - llvm::Value * slot_addr - = runtime_localenv_slot_addr(llvm_cx, - localenv_llvm_type, - localenv_alloca, - i_localenv_slot, - tmp_ir_builder); + /* remember stack location for reference + assignment + * in lambda body. + * + */ - if (!slot_addr) - return false; + TypeDescr arg_td = lambda_->fn_arg(i_arg-1); - ++i_localenv_slot; + llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td); - tmp_ir_builder.CreateStore(&arg, slot_addr); + llvm::Value * slot_addr + = runtime_localenv_slot_addr(llvm_cx, + localenv_llvm_type, + localenv_alloca, + i_localenv_slot, + tmp_ir_builder); - runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type }; + if (!slot_addr) + return false; - this->alloc_var(arg_name, binding); + ++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; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 231d6de4..6a982b89 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -26,7 +26,8 @@ namespace xo { /* 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_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()) { @@ -95,9 +96,10 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + TypeDescr fn_td, + bool wrapper_flag) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, wrapper_flag); /** like C: llvm IR doesn't support function-valued variables; * it does however support pointer-to-function-valued variables @@ -227,7 +229,7 @@ namespace xo { /* _env_api: base type for a local environment */ llvm::StructType * env_llvm_type = llvm::StructType::get(llvm_cx->llvm_cx_ref(), - "_env_api"); + "_env_api"); /* _env_api[0]: pointer to a local environment */ llvm::PointerType * envptr_llvm_type @@ -253,25 +255,48 @@ namespace xo { return llvm::PointerType::getUnqual(env_llvm_type); } /*env_api_llvm_ptr_type*/ -#ifdef NOT_USING llvm::StructType * - type2llvm::function_td_to_llvm_closure_type(xo::ref::brw llvm_cx, - TypeDescr fn_td) + type2llvm::create_closureapi_lvtype(xo::ref::brw llvm_cx, + xo::ref::brw fn) { - constexpr bool c_debug_flag = true; - using xo::scope; + constexpr const char * c_prefix = "c."; - scope log(XO_DEBUG(c_debug_flag)); + /* e.g. "c.foo" */ + std::string closure_name = std::string(c_prefix) + fn->name(); - /* closure type doesn't need a name. - * (We might find it convenient to give one anyway) + 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 llvm_cx, + TypeDescr fn_td, + const std::string & hint_name) + { + /* 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::StructType * closure_llvm_type - = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, + fn_td, + true /*wrapper_flag*/); + llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - llvm::PointerType * parent_llvm_type - } /*function_td_to_llvm_fnptr_type*/ -#endif + std::vector member_lvtype_v = { fn_lvtype, env_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)); + + return closure_lvtype; + } /*function_td_to_closureapi_lvtype*/ llvm::StructType * type2llvm::create_localenv_llvm_type(xo::ref::brw llvm_cx, @@ -289,7 +314,7 @@ namespace xo { for (const auto & var : lambda->argv()) { if (lambda->is_captured(var->name())) { - /* var needs a slot in localenv_llvm_type for lambda */ + /* 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())); @@ -308,35 +333,6 @@ namespace xo { return localenv_lvtype; } /*create_localenv_llvm_type*/ - llvm::StructType * - type2llvm::create_closure_lvtype(xo::ref::brw llvm_cx, - xo::ref::brw lambda) - { - constexpr const char * c_prefix = "c."; - - /* 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, - lambda->valuetype()); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); - - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; - - /* e.g. "c.foo" */ - std::string closure_name = std::string(c_prefix) + lambda->name(); - - llvm::StructType * closure_lvtype - = llvm::StructType::create(llvm_cx->llvm_cx_ref(), - member_lvtype_v, - llvm::StringRef(closure_name), - false /*!is_packed*/); - - return closure_lvtype; - } /*create_closure_lvtype*/ } /*namespace jit*/ } /*namespace xo*/ From 26a055eb1c526c82e468f43437829da88c472eeb Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 8 Jul 2024 18:31:37 -0400 Subject: [PATCH 092/102] xo-jit: docs: ++ ABI in glossary --- docs/glossary.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/glossary.rst b/docs/glossary.rst index 37b98af9..6af7073a 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -4,6 +4,10 @@ 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`. From 09f5c141dfd9eaf8561d6472a1f10a31cc8c5470 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:05:00 -0400 Subject: [PATCH 093/102] xo-jit: fnptr -> closures for primitives+lambdas throughout --- include/xo/jit/activation_record.hpp | 11 ++ include/xo/jit/type2llvm.hpp | 33 ++-- src/jit/MachPipeline.cpp | 233 +++++++++++++++++---------- src/jit/activation_record.cpp | 39 ++++- src/jit/type2llvm.cpp | 92 +++++++++-- utest/MachPipeline.test.cpp | 11 +- 6 files changed, 295 insertions(+), 124 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index 82763e6c..e3eb7a66 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -68,6 +68,17 @@ namespace xo { llvm::Type * llvm_type_ = nullptr; }; + inline std::ostream & + operator<<(std::ostream & os, const runtime_binding_detail & x) { + os << ""; + + return os; + } + /** * 1. pattern for a stack frame associated with a user-defined function (some Lambda lm) * diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index d7a04f05..35a6e739 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -38,9 +38,23 @@ namespace xo { * to either {primitive functions, functions-requiring-closures}, * with choice deferred until runtime **/ - static llvm::FunctionType * function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag = false); + static llvm::FunctionType * function_td_to_lvtype(xo::ref::brw 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 llvm_cx, + TypeDescr fn_td, + bool wrapper_flag); /** establish llvm concrete representation for a closure. * @@ -171,19 +185,6 @@ namespace xo { llvm::PointerType * hint_envptr_llvm_type = nullptr); private: - /** 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 llvm_cx, - TypeDescr fn_td, - bool wrapper_flag); /** establish llvm representation for a struct type described by @p struct_td **/ diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index b458f9fb..671f38fa 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -192,7 +192,7 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td); if (!llvm_fn_type) return nullptr; @@ -251,7 +251,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_primitive_wrapper(ref::brw expr, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -266,7 +266,7 @@ namespace xo { std::string wrap_name = std::string(c_prefix) + expr->name(); /* original primitive */ - auto * native_lvfn = codegen_primitive(expr); + auto * native_lvfn = this->codegen_primitive(expr); /* wrapped primitive */ auto * wrap_lvfn = llvm_module_->getFunction(wrap_name); @@ -279,13 +279,15 @@ namespace xo { TypeDescr fn_td = expr->valuetype(); llvm::FunctionType * native_lvtype - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), + fn_td, + false /*!wrapper_flag*/); if (!native_lvtype) return nullptr; llvm::FunctionType * wrapper_lvtype - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), fn_td, true /*wrapper_flag (for closure)*/); @@ -301,7 +303,11 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", wrap_lvfn); - ir_builder.SetInsertPoint(block); + /* don't call SetInsertPoint() on incoming ir_builder argument. + * Want to avoid disturbing top-to-bottom flow + */ + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(block); std::vector args; @@ -323,15 +329,15 @@ namespace xo { /* {caller,callee} must agree on calling convention, * so for primitives we need to assume c. */ - llvm::CallInst * call = ir_builder.CreateCall(native_lvtype, - native_lvfn, - args, - "w.calltmp"); + llvm::CallInst * call = tmp_ir_builder.CreateCall(native_lvtype, + native_lvfn, + args, + "w.calltmp"); if (call) { call->setTailCall(true); /* does this work if call returns void? Is this needed with tail call? */ - ir_builder.CreateRet(call); + tmp_ir_builder.CreateRet(call); llvm::verifyFunction(*wrap_lvfn); @@ -365,6 +371,9 @@ namespace xo { MachPipeline::codegen_primitive_closure(ref::brw expr, llvm::IRBuilder<> & ir_builder) { + constexpr bool c_debug_flag = true; + scope log(XO_DEBUG(c_debug_flag)); + llvm::StructType * closure_lvtype = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), expr); @@ -402,6 +411,8 @@ namespace xo { * - MachPipeline::codegen_primitive_closure * - MachPipeline::codegen_lambda_closure * - type2llvm::create_closure_lvtype + * + * although this refers to a closure, llvm doesn't know that */ llvm::Value * llvm_closure = nullptr; llvmintrinsic intrinsic = llvmintrinsic::invalid; @@ -413,7 +424,7 @@ namespace xo { auto pm = PrimitiveInterface::from(apply->fn()); if (pm) { - llvm_closure = this->codegen_primitive(pm); + llvm_closure = this->codegen_primitive_closure(pm, ir_builder); /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ intrinsic = pm->intrinsic(); } @@ -442,69 +453,93 @@ namespace xo { /* function type in apply node's function position */ TypeDescr ast_fn_td = apply->fn()->valuetype(); -#ifdef NOT_USING_DEBUG - cerr << "MachPipeline::codegen_apply: fn:" << endl; - fn->print(llvm::errs()); - cerr << endl; -#endif + if (log) { + log("MachPipeline::codegen_apply: fn in apply pos..."); + llvm_closure->print(llvm::errs()); + log("...done"); + log("llvm type..."); + llvm_closure->getType()->dump(); + log("...done"); + } /* checks here will be redundant */ -#ifdef REDUNDANT_TYPECHECK - if (apply->argv().size() != ast_fn_td->n_fn_arg()) { - cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" - //<< xtag("f", ast_fn->name()) - << xtag("n1", ast_fn_td->n_fn_arg()) - << xtag("n2", apply->argv().size()) - << endl; - - return nullptr; - } - - /** also check argument types **/ - for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { - if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { - cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" - << xtag("F", apply->fn()) - << xtag("I", i) - << xtag("U", apply->argv()[i]->valuetype()->short_name()) - << xtag("T", ast_fn_td->fn_arg(i)->short_name()) - << endl; - - return nullptr; - } - } -#endif - +#ifdef OBSOLETE llvm::StructType * closure_lvtype = type2llvm::function_td_to_closureapi_lvtype(llvm_cx_, ast_fn_td, "" /*name - not required*/); +#endif llvm::Value * lv_fnptr = nullptr; { +#ifdef MAYBE_VERBOSE + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + llvm::Value * fnptr_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APInt(32 /*bits*/, 0 /*value*/)); - std::array index_v = {{fnptr_slot /*fnptr slot = closure[0]*/}}; + std::array index_v + = {{i0_slot, + fnptr_slot /*fnptr slot = closure[0]*/}}; - lv_fnptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + llvm::Value * lv_fnptr_addr + = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, + llvm_closure, + index_v); + + llvm::Type * fnptr_lvtype + = type2llvm::function_td_to_llvm_fnptr_type(llvm_cx_, + apply->fn()->valuetype(), + true /*wrapper_flag*/); + + /* the thing we're going to call */ + lv_fnptr = ir_builder.CreateLoad(fnptr_lvtype, lv_fnptr_addr); +#endif + + std::array index_v = {{ 0 }}; + + //ir_builder.CreateExtractValue(Value *Agg, ArrayRef Idxs) + + lv_fnptr = ir_builder.CreateExtractValue(llvm_closure, + index_v, + "fnptr"); } llvm::Value * lv_fnenvptr = nullptr; { +#ifdef MAYBE_VERBOSE + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0 /*value*/)); + llvm::Value * envptr_slot = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), llvm::APInt(32 /*bits*/, 1 /*value*/)); - std::array index_v = {{envptr_slot /*envptr slot = closure[1]*/}}; + std::array index_v + = {{i0_slot, + envptr_slot /*envptr slot = closure[1]*/}}; - lv_fnenvptr = ir_builder.CreateInBoundsGEP(closure_lvtype, - llvm_closure, - index_v); + llvm::Value * lv_fnenvptr_addr + = ir_builder.CreateInBoundsGEP(llvm_closure->getType(), //closure_lvtype, + llvm_closure, + index_v); + + llvm::Type * fnenvptr_lvtype + = type2llvm::env_api_llvm_ptr_type(llvm_cx_); + + lv_fnenvptr = ir_builder.CreateLoad(fnenvptr_lvtype, lv_fnenvptr_addr); +#endif + + std::array index_v = {{ 1 }}; + + lv_fnenvptr = ir_builder.CreateExtractValue(llvm_closure, + index_v, + "envptr"); } std::vector args; @@ -524,8 +559,13 @@ namespace xo { if (log) { /* TODO: print helper for llvm::Value* */ std::string llvm_value_str; - llvm::raw_string_ostream ss(llvm_value_str); - arg->print(ss); + + if (arg) { + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + } else { + llvm_value_str = ""; + } log(xtag("i_arg", i), xtag("arg", llvm_value_str)); @@ -533,6 +573,14 @@ namespace xo { args.push_back(arg); ++i; + + if (!arg) { + cerr << "MachPipeline::codegen_apply: failed for i'th argument" + << xtag("i", i) + << endl; + + return nullptr; + } } /* if we have an intrinsic hint, @@ -571,9 +619,9 @@ namespace xo { } llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(this->llvm_cx_, - ast_fn_td, - true /*wrapper_flag*/); + = type2llvm::function_td_to_lvtype(this->llvm_cx_, + ast_fn_td, + true /*wrapper_flag*/); return ir_builder.CreateCall(llvm_fn_type, lv_fnptr, @@ -623,13 +671,13 @@ namespace xo { * Note that this argument is not present in lambda, * so we need care. lambda->fn_arg(i) -> lvfn->arg [i+1] */ - llvm::FunctionType * llvm_fn_type - = type2llvm::function_td_to_llvm_type(llvm_cx_.borrow(), - lambda->valuetype(), - true /*wrapper_flag*/); + llvm::FunctionType * fn_lvtype + = type2llvm::function_td_to_lvtype(llvm_cx_.borrow(), + lambda->valuetype(), + true /*wrapper_flag*/); /* create (initially empty) function */ - fn = llvm::Function::Create(llvm_fn_type, + fn = llvm::Function::Create(fn_lvtype, llvm::Function::ExternalLinkage, lambda->name(), llvm_module_.get()); @@ -660,7 +708,7 @@ namespace xo { llvm::Function * MachPipeline::codegen_lambda_defn(ref::brw lambda, - llvm::IRBuilder<> & ir_builder) + llvm::IRBuilder<> & /*ir_builder*/) { constexpr bool c_debug_flag = true; @@ -690,14 +738,19 @@ namespace xo { auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); - ir_builder.SetInsertPoint(block); + /* since we need to explictly set builder's insert point, + * make a new builder instead of disturbing the top-to-bottom flow of the + * called ir_builder + */ + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(block); /** Actual parameters will need their own activation record. * Track its shape + setup/teardown here. **/ this->env_stack_.push(activation_record(lambda.get())); - bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, ir_builder); + bool ok_flag = this->env_stack_.top().bind_locals(llvm_cx_, llvm_fn, tmp_ir_builder); if (!ok_flag) { this->env_stack_.pop(); @@ -706,11 +759,11 @@ namespace xo { llvm::Value * retval = this->codegen(lambda->body(), envptr, - ir_builder); + tmp_ir_builder); if (retval) { /* completes the function.. */ - ir_builder.CreateRet(retval); + tmp_ir_builder.CreateRet(retval); /* validate! always validate! */ llvm::verifyFunction(*llvm_fn); @@ -742,7 +795,9 @@ namespace xo { this->env_stack_.pop(); - log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + log && log("after pop, env stack size Z", + xtag("Z", env_stack_.size()), + xtag("llvm_fn", (void*)llvm_fn)); return llvm_fn; } /*codegen_lambda_defn*/ @@ -755,14 +810,21 @@ namespace xo { llvm::StructType * closure_lvtype = type2llvm::create_closureapi_lvtype(llvm_cx_.borrow(), lambda); - llvm::Function * lvfn = codegen_lambda_decl(lambda); + llvm::Function * lvfn = codegen_lambda_defn(lambda, ir_builder); + + if (!lvfn) { + cerr << "MachPipeline::codegen_lambda_closure: codegen lambda failed" + << endl; + return nullptr; + } llvm::Value * lv_closure = nullptr; - - lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), - lvfn, {0}, "lmfnptr" /*name*/); - lv_closure = ir_builder.CreateInsertValue(lv_closure, - envptr, {1}, "envptr" /*name*/); + { + lv_closure = ir_builder.CreateInsertValue(llvm::UndefValue::get(closure_lvtype), + lvfn, {0}); //, "lmfnptr" /*name*/); + lv_closure = ir_builder.CreateInsertValue(lv_closure, + envptr, {1}, "closure" /*name*/); + } return lv_closure; } /*codegen_lambda_closure*/ @@ -836,44 +898,45 @@ namespace xo { when_false_bb); /* populate when_true_bb */ - ir_builder.SetInsertPoint(when_true_bb); + llvm::IRBuilder<> tmp_ir_builder(llvm_cx_->llvm_cx_ref()); + tmp_ir_builder.SetInsertPoint(when_true_bb); llvm::Value * when_true_ir = this->codegen(expr->when_true(), envptr, - ir_builder); + tmp_ir_builder); if (!when_true_ir) return nullptr; /* at end of when-true sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_true() may have altered builder's "current block" */ - when_true_bb = ir_builder.GetInsertBlock(); + when_true_bb = tmp_ir_builder.GetInsertBlock(); /* populate when_false_bb */ parent_fn->insert(parent_fn->end(), when_false_bb); - ir_builder.SetInsertPoint(when_false_bb); + tmp_ir_builder.SetInsertPoint(when_false_bb); llvm::Value * when_false_ir = this->codegen(expr->when_false(), envptr, - ir_builder); + tmp_ir_builder); if (!when_false_ir) return nullptr; /* at end of when-false sequence, jump to merge suffix */ - ir_builder.CreateBr(merge_bb); + tmp_ir_builder.CreateBr(merge_bb); /* note: codegen for expr->when_false() may have altered builder's "current block" */ - when_false_bb = ir_builder.GetInsertBlock(); + when_false_bb = tmp_ir_builder.GetInsertBlock(); /* merged suffix sequence */ parent_fn->insert(parent_fn->end(), merge_bb); - ir_builder.SetInsertPoint(merge_bb); + tmp_ir_builder.SetInsertPoint(merge_bb); /** TODO: switch to getInt1Ty here **/ llvm::PHINode * phi_node - = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), - 2 /*#of branches being merged (?)*/, - "iftmp"); + = tmp_ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); phi_node->addIncoming(when_true_ir, when_true_bb); phi_node->addIncoming(when_false_ir, when_false_bb); diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index be1275ef..7b1228e6 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -33,7 +33,12 @@ namespace xo { } /*ctor*/ const runtime_binding_detail * - activation_record::lookup_var(const std::string & x) const { + 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); @@ -41,6 +46,10 @@ namespace xo { 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; } @@ -51,6 +60,14 @@ namespace xo { 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) @@ -76,10 +93,12 @@ namespace xo { constexpr bool c_debug_flag = true; using xo::scope; - scope log(XO_DEBUG(c_debug_flag), - xtag("llvm_fn", (void*)llvm_fn), - xtag("var_name", var_name), - xtag("var_type", var_type->short_name())); + 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); @@ -151,12 +170,16 @@ namespace xo { #endif llvm::IRBuilder<> & tmp_ir_builder) { + llvm::Value * i0_slot + = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), + llvm::APInt(32 /*bits*/, 0)); + llvm::Value * i32_slot = llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(), llvm::APInt(32 /*bits*/, i_slot /*value*/)); - std::array index_v = { - {i32_slot /*environment slot #0*/}}; + std::array index_v = { + {i0_slot, i32_slot /*environment slot #0*/}}; llvm::Value * llvm_localenv_slot_ptr = tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type, @@ -216,7 +239,7 @@ namespace xo { tmp_ir_builder, i_arg, arg_name, - lambda_->fn_arg(i_arg)); + lambda_->fn_arg(i_arg-1)); if (!binding.llvm_addr_) return false; diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index 6a982b89..e5c07cac 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -58,20 +58,29 @@ namespace xo { * that represented by @p fn_td **/ llvm::FunctionType * - type2llvm::function_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + type2llvm::function_td_to_lvtype(xo::ref::brw llvm_cx, + TypeDescr fn_td, + bool wrapper_flag) { - int n_fn_arg = fn_td->n_fn_arg(); + constexpr bool c_debug_flag = false; + + scope log(XO_DEBUG(c_debug_flag)); + + int n_ast_fn_arg = fn_td->n_fn_arg(); + + if (log) { + log(xtag("fn_td", fn_td->short_name())); + log(xtag("n_ast_fn_arg", n_ast_fn_arg)); + } std::vector llvm_argtype_v; - llvm_argtype_v.reserve(n_fn_arg + (wrapper_flag ? 1 : 0)); + 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_fn_arg; ++i) { + 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); @@ -79,12 +88,26 @@ namespace xo { 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; @@ -96,17 +119,23 @@ namespace xo { llvm::PointerType * type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, - TypeDescr fn_td, - bool wrapper_flag) + TypeDescr /*fn_td*/, + bool /*wrapper_flag*/) { - auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td, 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; } @@ -181,20 +210,26 @@ namespace xo { { 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 llvm_cx, - llvm::PointerType * envptr_llvm_type) + llvm::PointerType * /*envptr_llvm_type*/) { +#ifdef OBSOLETE if (!envptr_llvm_type) envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx); @@ -219,6 +254,9 @@ namespace xo { /* _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*/ @@ -231,9 +269,13 @@ namespace xo { = 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 @@ -250,9 +292,11 @@ namespace xo { llvm::PointerType * type2llvm::env_api_llvm_ptr_type(xo::ref::brw 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 * @@ -274,6 +318,10 @@ namespace xo { 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 @@ -283,9 +331,21 @@ namespace xo { llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx, fn_td, true /*wrapper_flag*/); - llvm::StructType * env_lvtype = env_api_llvm_type(llvm_cx); + if (log) { + log(xtag("fn_lvtype", "...")); + fn_lvtype->dump(); + log("...done"); + } - std::vector member_lvtype_v = { fn_lvtype, env_lvtype }; + llvm::PointerType * envptr_lvtype = env_api_llvm_ptr_type(llvm_cx); + + if (log) { + log(xtag("env_lvtype", "...")); + envptr_lvtype->dump(); + log("...done"); + } + + std::vector member_lvtype_v = { fn_lvtype, envptr_lvtype }; llvm::StructType * closure_lvtype = llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v); @@ -295,6 +355,12 @@ namespace xo { 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*/ diff --git a/utest/MachPipeline.test.cpp b/utest/MachPipeline.test.cpp index 880aabf1..ad14ac32 100644 --- a/utest/MachPipeline.test.cpp +++ b/utest/MachPipeline.test.cpp @@ -116,12 +116,19 @@ namespace xo { //auto rng = xo::rng::xoshiro256ss(seed); - scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline")); + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.fptr")); //log && log("(A)", xtag("foo", foo)); - auto jit = MachPipeline::make(); 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))); From 97d095a0556e19cdc12eab6f5e5aa5920273e461 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:35 -0400 Subject: [PATCH 094/102] xo-jit: ++ HOWTO --- HOWTO | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/HOWTO b/HOWTO index 6965105f..c3eed238 100644 --- a/HOWTO +++ b/HOWTO @@ -43,3 +43,30 @@ mp.machgen_current_module() fn=mp.lookup_fn('int (*)(int)', 'sq') fn(16) # -> 256 + +** to figure out what 'IR should look like' for something simple + + write some c++: + + #include + + struct env_type { + env_type * parent; + env_type * (*unwind)(env_type *, int); + }; + + double wrap_sqrt(env_type * env, double x) { + return ::sqrt(x); + } + + int main() { + wrap_sqrt(nullptr, 2.0); + } + + compile to emit IR + + $ clang -cc1 ex_cpp.cpp -emit-llvm + + inspect + + ex_cpp.ll From 0ee004cec685bca1bc5d267af473335d57900070 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:09:52 -0400 Subject: [PATCH 095/102] xo-jit: + example/ex_cpp --- example/ex_cpp/ex_cpp.cpp | 40 ++++++++++++++ example/ex_cpp/ex_cpp.ll | 108 ++++++++++++++++++++++++++++++++++++++ example/ex_cpp/tmp.ll | 58 ++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 example/ex_cpp/ex_cpp.cpp create mode 100644 example/ex_cpp/ex_cpp.ll create mode 100644 example/ex_cpp/tmp.ll diff --git a/example/ex_cpp/ex_cpp.cpp b/example/ex_cpp/ex_cpp.cpp new file mode 100644 index 00000000..65ce9934 --- /dev/null +++ b/example/ex_cpp/ex_cpp.cpp @@ -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; +} diff --git a/example/ex_cpp/ex_cpp.ll b/example/ex_cpp/ex_cpp.ll new file mode 100644 index 00000000..28434d62 --- /dev/null +++ b/example/ex_cpp/ex_cpp.ll @@ -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"} diff --git a/example/ex_cpp/tmp.ll b/example/ex_cpp/tmp.ll new file mode 100644 index 00000000..b8b8993a --- /dev/null +++ b/example/ex_cpp/tmp.ll @@ -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 +} From 3fa4029bc222f70c5c05d64c0fe2303a4de26112 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 10 Jul 2024 16:11:20 -0400 Subject: [PATCH 096/102] xo-jit: + example/ex_cpp/README --- example/ex_cpp/README | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 example/ex_cpp/README diff --git a/example/ex_cpp/README b/example/ex_cpp/README new file mode 100644 index 00000000..b67ad2e8 --- /dev/null +++ b/example/ex_cpp/README @@ -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 From 7e088e02621b3472bb92d22f93e83c4f1512bab2 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:55:52 -0400 Subject: [PATCH 097/102] xo-jit: fix: xo::ref::rp -> xo::rp --- include/xo/jit/IrPipeline.hpp | 4 ++-- include/xo/jit/LlvmContext.hpp | 2 +- include/xo/jit/MachPipeline.hpp | 8 ++++---- src/jit/IrPipeline.cpp | 2 +- src/jit/LlvmContext.cpp | 2 +- src/jit/MachPipeline.cpp | 6 ++++-- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/xo/jit/IrPipeline.hpp b/include/xo/jit/IrPipeline.hpp index d8234e6b..5268d088 100644 --- a/include/xo/jit/IrPipeline.hpp +++ b/include/xo/jit/IrPipeline.hpp @@ -51,7 +51,7 @@ namespace xo { **/ class IrPipeline : public ref::Refcount { public: - explicit IrPipeline(ref::rp llvm_cx); + explicit IrPipeline(rp llvm_cx); void run_pipeline(llvm::Function & fn); @@ -59,7 +59,7 @@ namespace xo { // ----- transforms (also adapted from kaleidescope.cpp) ------ /** keepalive for contained llvm::LLVMContext **/ - ref::rp llvm_cx_; + rp llvm_cx_; /** manages all the passes+analaysis (?) **/ std::unique_ptr llvm_fpmgr_; diff --git a/include/xo/jit/LlvmContext.hpp b/include/xo/jit/LlvmContext.hpp index cf594c82..ac577b9e 100644 --- a/include/xo/jit/LlvmContext.hpp +++ b/include/xo/jit/LlvmContext.hpp @@ -24,7 +24,7 @@ namespace xo { **/ class LlvmContext : public ref::Refcount { public: - static xo::ref::rp make(); + static rp make(); llvm::LLVMContext & llvm_cx_ref() { return *llvm_cx_; } std::unique_ptr & llvm_cx() { return llvm_cx_; } diff --git a/include/xo/jit/MachPipeline.hpp b/include/xo/jit/MachPipeline.hpp index 11589e8c..d6ae4ea2 100644 --- a/include/xo/jit/MachPipeline.hpp +++ b/include/xo/jit/MachPipeline.hpp @@ -67,7 +67,7 @@ namespace xo { public: /* tracking KaleidoscopeJIT::Create() here.. */ static llvm::Expected> make_aux(); - static xo::ref::rp make(); + static rp make(); // ----- access ----- @@ -192,7 +192,7 @@ namespace xo { * jit_->addModule() * Note that this makes the module itself unavailable to us **/ - xo::ref::rp ir_pipeline_; + rp ir_pipeline_; /** owns + manages core "global" llvm data, * including type- and constant- unique-ing tables. @@ -200,7 +200,7 @@ namespace xo { * Not threadsafe, but ok to have multiple threads, * each with its own LLVMContext **/ - ref::rp llvm_cx_; + rp llvm_cx_; /** builder for intermediate-representation objects **/ std::unique_ptr> llvm_toplevel_ir_builder_; @@ -213,7 +213,7 @@ namespace xo { std::unique_ptr llvm_module_; /** map global names to functions/variables **/ - ref::rp global_env_; + rp global_env_; /** map variable names (formal parameters) to * corresponding llvm IR. diff --git a/src/jit/IrPipeline.cpp b/src/jit/IrPipeline.cpp index 285edbe3..cefd9766 100644 --- a/src/jit/IrPipeline.cpp +++ b/src/jit/IrPipeline.cpp @@ -4,7 +4,7 @@ namespace xo { namespace jit { - IrPipeline::IrPipeline(ref::rp llvm_cx) + IrPipeline::IrPipeline(rp llvm_cx) { using std::make_unique; diff --git a/src/jit/LlvmContext.cpp b/src/jit/LlvmContext.cpp index 19349aca..1e4ad007 100644 --- a/src/jit/LlvmContext.cpp +++ b/src/jit/LlvmContext.cpp @@ -4,7 +4,7 @@ namespace xo { namespace jit { - xo::ref::rp + rp LlvmContext::make() { return new LlvmContext(); } diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 9c6bb1cd..15852aee 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -60,7 +60,7 @@ namespace xo { )); } /*make*/ - xo::ref::rp + rp MachPipeline::make() { static llvm::ExitOnError llvm_exit_on_err; @@ -729,7 +729,9 @@ namespace xo { /* generate function body */ - auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "entry", + llvm_fn); ir_builder.SetInsertPoint(block); From ae59fa342828a1912fe9bad47c6195fc852a517d Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:56:41 -0400 Subject: [PATCH 098/102] xo-jit: attach global env to lambda --- src/jit/MachPipeline.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 15852aee..1d85a438 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -714,6 +714,9 @@ namespace xo { global_env_->require_global(lambda->name(), lambda.get()); + /* correct PROVIDED this is a toplevel lambda */ + lambda->attach_envs(this->global_env_); + /* do we already know a function with this name? */ auto * llvm_fn = llvm_module_->getFunction(lambda->name()); From b88eb68547e886e042f77e672652452ceeb56b48 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Mon, 5 Aug 2024 14:58:21 -0400 Subject: [PATCH 099/102] xo-jit: + wip comment --- src/jit/MachPipeline.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 1d85a438..22378c7b 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -975,6 +975,17 @@ namespace xo { * generate code for it too */ + /* WIP. STRATEGY: + * - xo::ast::ClosureExpr (an expression that generates a closure) + * closure = {lambda, env} + * + * - pass 1: + * return list of closure expressions; + * codegen the lambda decls using lambda from each closure + * - pass 2: + * codegen closures: use env chain to resolve variables + */ + /* Pass 1. */ auto fn_v = this->find_lambdas(expr); From 07e5a1f349cfa46d8f5c230f2d585ae10e4ed93c Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sun, 15 Sep 2024 15:32:40 -0500 Subject: [PATCH 100/102] xo-jit: bugfix: track xo::ref::rp -> xo::rp --- include/xo/jit/activation_record.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/xo/jit/activation_record.hpp b/include/xo/jit/activation_record.hpp index e3eb7a66..73c9e52e 100644 --- a/include/xo/jit/activation_record.hpp +++ b/include/xo/jit/activation_record.hpp @@ -114,9 +114,9 @@ namespace xo { using TypeDescr = xo::reflect::TypeDescr; public: - activation_record(const ref::rp & lm); + activation_record(const rp & lm); - const ref::rp lambda() const { return lambda_; } + const rp lambda() const { return lambda_; } /** retrieve @c llvm::Value* representing the primary stack location * for formal parameter @p var_name @@ -189,7 +189,7 @@ namespace xo { * runtime environment records. * **/ - ref::rp lambda_; + rp lambda_; /** @c binding_v_[i] specifies how/where we mean to navigate to * location for formal parameter number *i* of @ref lambda_. From 9439f0f2210cdeb22dd14046e23d889253f7c554 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Wed, 7 May 2025 20:47:22 -0500 Subject: [PATCH 101/102] xo-jit: clang18/llvm18 compile fixes (temp patches) --- CMakeLists.txt | 5 ----- include/xo/jit/Jit.hpp | 2 +- src/jit/MachPipeline.cpp | 7 +++++++ src/jit/activation_record.cpp | 2 +- src/jit/type2llvm.cpp | 7 +++++-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ee6351c..30e3f9cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,9 +31,4 @@ add_subdirectory(utest) # reminder: must come last: docs targets depend on all the other library/utest targets add_subdirectory(docs) -# ---------------------------------------------------------------- -# docs targets depend on all the other library/utest targets -# -#add_subdirectory(docs) - # end CMakeLists.txt diff --git a/include/xo/jit/Jit.hpp b/include/xo/jit/Jit.hpp index f983fb2e..5130b844 100644 --- a/include/xo/jit/Jit.hpp +++ b/include/xo/jit/Jit.hpp @@ -15,7 +15,7 @@ # 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" +# include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h" // need llvm18 # include "llvm/ExecutionEngine/SectionMemoryManager.h" # include "llvm/IR/DataLayout.h" # include "llvm/IR/LLVMContext.h" diff --git a/src/jit/MachPipeline.cpp b/src/jit/MachPipeline.cpp index 850d0801..d45f03db 100644 --- a/src/jit/MachPipeline.cpp +++ b/src/jit/MachPipeline.cpp @@ -604,6 +604,8 @@ namespace xo { return ir_builder.CreateUDiv(args[1], args[2]); case llvmintrinsic::fp_add: return ir_builder.CreateFAdd(args[1], args[2]); + case llvmintrinsic::fp_sub: + return ir_builder.CreateFSub(args[1], args[2]); case llvmintrinsic::fp_mul: return ir_builder.CreateFMul(args[1], args[2]); case llvmintrinsic::fp_div: @@ -954,6 +956,11 @@ namespace xo { llvm::IRBuilder<> & ir_builder) { switch(expr->extype()) { + case exprtype::define: + case exprtype::assign: + case exprtype::sequence: + case exprtype::convert: + break; case exprtype::constant: return this->codegen_constant(ConstantInterface::from(expr)); case exprtype::primitive: diff --git a/src/jit/activation_record.cpp b/src/jit/activation_record.cpp index 7b1228e6..5d0fd3ad 100644 --- a/src/jit/activation_record.cpp +++ b/src/jit/activation_record.cpp @@ -10,7 +10,7 @@ namespace xo { using std::cerr; using std::endl; - activation_record::activation_record(const ref::rp & lm) + activation_record::activation_record(const rp & lm) : lambda_{lm}, binding_v_(lm->n_arg()) { diff --git a/src/jit/type2llvm.cpp b/src/jit/type2llvm.cpp index e5c07cac..8bfb2a3b 100644 --- a/src/jit/type2llvm.cpp +++ b/src/jit/type2llvm.cpp @@ -205,9 +205,12 @@ namespace xo { } /*struct_td_to_llvm_type*/ llvm::PointerType * - type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx, - TypeDescr pointer_td) + type2llvm::pointer_td_to_llvm_type(xo::ref::brw llvm_cx + , TypeDescr pointer_td + ) { + (void)pointer_td; + assert(pointer_td->is_pointer()); #ifdef OBSOLETE From 855887df71015791fb87af513c8ce3b3ecd06472 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Fri, 9 May 2025 00:00:26 -0500 Subject: [PATCH 102/102] xo-jit: compiler nit: (clang 15) --- include/xo/jit/activation_record.new.hpp | 66 + include/xo/jit/activation_record.orig.hpp | 41 + include/xo/jit/type2llvm.hpp | 3 + src/jit/MachPipeline.new.cpp | 1341 +++++++++++++++++++++ src/jit/MachPipeline.orig.cpp | 1064 ++++++++++++++++ src/jit/activation_record.new.cpp | 47 + src/jit/activation_record.orig.cpp | 45 + 7 files changed, 2607 insertions(+) create mode 100644 include/xo/jit/activation_record.new.hpp create mode 100644 include/xo/jit/activation_record.orig.hpp create mode 100644 src/jit/MachPipeline.new.cpp create mode 100644 src/jit/MachPipeline.orig.cpp create mode 100644 src/jit/activation_record.new.cpp create mode 100644 src/jit/activation_record.orig.cpp diff --git a/include/xo/jit/activation_record.new.hpp b/include/xo/jit/activation_record.new.hpp new file mode 100644 index 00000000..7c70ba3f --- /dev/null +++ b/include/xo/jit/activation_record.new.hpp @@ -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 +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record(llvm::Function * llvm_fn, + llvm::AllocaInst * frame) : frame_{frame} { + int i_arg = 0; + for (auto & arg : llvm_fn->args()) { + std::string arg_name = std::string(arg.getName()); + + name2ix_map_[arg_name] = 2 + i_arg; + } + } + + std::int32_t lookup_var(const std::string & var_name) const; + +#ifdef OBSOLETE + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); +#endif + + private: + /** stack frame for a user-defined function (lambda) **/ + llvm::AllocaInst * frame_ = nullptr; + + /** for each formal parameter, + * reports its position in stack frame. + * This is the position to use with getelementptr, + * i.e. +2 to skip first two slots, that are reserved + * for nextframe pointer (slot 0) + unwind pointer (slot 1) + **/ + std::map name2ix_map_; + +#ifdef OBSOLETE + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ +#endif + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/activation_record.orig.hpp b/include/xo/jit/activation_record.orig.hpp new file mode 100644 index 00000000..c2aba2dd --- /dev/null +++ b/include/xo/jit/activation_record.orig.hpp @@ -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 +# include +#pragma GCC diagnostic pop +#include +//#include + +namespace xo { + namespace jit { + /** scope for a stack frame associated with a user-defined function + * + * each function needs its own IR builder, to keep track of things like insert point + **/ + class activation_record { + public: + activation_record() = default; + + llvm::AllocaInst * lookup_var(const std::string & var_name) const; + + llvm::AllocaInst * alloc_var(const std::string & var_name, + llvm::AllocaInst * alloca); + + private: + /** maps named slots in a stack frame to logical addresses **/ + std::map frame_; /* <-> kaleidoscope NamedValues */ + }; /*activation_record*/ + + } /*namespace jit*/ +} /*namespace xo*/ + + +/** end activation_record.hpp **/ diff --git a/include/xo/jit/type2llvm.hpp b/include/xo/jit/type2llvm.hpp index 35a6e739..fb108a3e 100644 --- a/include/xo/jit/type2llvm.hpp +++ b/include/xo/jit/type2llvm.hpp @@ -8,7 +8,10 @@ #include "LlvmContext.hpp" #include "xo/expression/Lambda.hpp" #include "xo/reflect/TypeDescr.hpp" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" #include +#pragma GCC diagnostic pop //#include namespace xo { diff --git a/src/jit/MachPipeline.new.cpp b/src/jit/MachPipeline.new.cpp new file mode 100644 index 00000000..c4faff4a --- /dev/null +++ b/src/jit/MachPipeline.new.cpp @@ -0,0 +1,1341 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_frame_alloca(llvm::Function * llvm_fn, + llvm::StructType * frame_llvm_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn)); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + if (!frame_llvm_type) + return nullptr; + + if (log) { + std::string llvm_frame_type_str; + llvm::raw_string_ostream ss(llvm_frame_type_str); + frame_llvm_type->print(ss); + + log(xtag("frame_llvm_type", llvm_frame_type_str)); + } + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(frame_llvm_type, + nullptr, + llvm_fn->getName()); + + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_frame_alloca*/ + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + namespace { + /** A function type: + * + * _baseframe* (*) (_baseframe* + **/ + llvm::FunctionType * + require_baseframe_unwind_llvm_type(xo::ref::brw llvm_cx, + llvm::PointerType * frameptr_llvm_type) + { + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(2); + + /* 1st arg is frame pointer */ + llvm_argtype_v.push_back(frameptr_llvm_type); + + /* 2nd arg is an i32. + * 0 -> unwind. + * 1 -> lift to heap (someday) + */ + llvm_argtype_v.push_back + (llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref())); + + /* return value is frame pointer */ + llvm::Type * retval_llvm_type = frameptr_llvm_type; + + auto * unwind_llvm_type = llvm::FunctionType::get(retval_llvm_type, + llvm_argtype_v, + false /*!varargs*/); + + return unwind_llvm_type; + } /*require_baseframe_unwind_llvm_type*/ + + /** Each lambda gets its own stack frame definition. + * However all the various frame representations share the same 'baseframe' + * prefix. + * + * _baseframe: + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> frame* (*)(frame*, ctl) + * +-------+ + * + * This helper function generates an llvm::Type* for a baseframe. + * It only needs to be invoked once (per LlvmContext, I guess ..) + **/ + llvm::StructType * + require_baseframe_llvm_type(xo::ref::brw llvm_cx) + { + /* _baseframe: base type for a stack frame */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref(), + "_baseframe"); + + /* _baseframe*: pointer to a stack frame */ + llvm::PointerType * frameptr_llvm_type + = llvm::PointerType::getUnqual(frame_llvm_type); + + /* unwind function = frame[1] */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + frameptr_llvm_type); + + /* _baseframe members */ + std::vector llvm_membertype_v; + { + llvm_membertype_v.reserve(2); + + /* frame[0] = pointer to next frame */ + llvm_membertype_v.push_back(frameptr_llvm_type); + /* frame[1] = unwind function */ + llvm_membertype_v.push_back(unwind_llvm_type); + } + + frame_llvm_type->setBody(frameptr_llvm_type /*frame[0]*/, + unwind_llvm_type /*frame[1]*/); + + return frame_llvm_type; + } /*require_baseframe_llvm_type*/ + + llvm::PointerType * + require_baseframe_ptr_llvm_type(xo::ref::brw llvm_cx) + { + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + return llvm::PointerType::getUnqual(baseframe_llvm_type); + } + + /** need a supporting type for stack frame + * - so we can handle variables with non-trivial dtors + * (e.g. smart pointers) + * - so we can implement nested lexical scoping + * - so we can walk stack for exception handling + * - eventually: so we can eventually implement trampoline + * + * frame representation: + * + * ^ + * | + * +-------+ | + * next_frame [0] | o-------/ + * +-------| + * unwind_fn [1] | o-------> baseframe* (*)(baseframe*, ctl) + * +-------| + * arg[i] [2+i] | ... | + * +-------+ + * + * invoke frame.unwind_fn to dispose of a frame + * - ctl=0 dtor. deal with smart pointers etc. + * - ctl=1 copy. lift frame into heap for lambda capture + * + * every frame is a subtype of _baseframe (ofc llvm doesn't know this). + * See baseframe_llvm_type(), baseframe_unwind_llvm_type() above + * + * editor bait: activation_record_to_llvm_type + **/ + llvm::StructType * + frame_to_llvm_type(xo::ref::brw llvm_cx, + ref::brw lambda, + llvm::Function * lambda_llvm_fn) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /* frame type doesn't need a name */ + llvm::StructType * frame_llvm_type + = llvm::StructType::get(llvm_cx->llvm_cx_ref()); + + /* _baseframe */ + llvm::StructType * baseframe_llvm_type + = require_baseframe_llvm_type(llvm_cx); + + /* _baseframe*: pointer to a generic stack frame */ + llvm::PointerType * baseframeptr_llvm_type + = llvm::PointerType::getUnqual(baseframe_llvm_type); + + /* _baseframe* (*)(_baseframe*, i32) */ + llvm::FunctionType * unwind_llvm_type + = require_baseframe_unwind_llvm_type(llvm_cx, + baseframeptr_llvm_type); + + /* llvm_argtype_v: + * - llvm_argtype_v[0] = llvm::Type* for pointer to next_frame + * - llvm_argtype_v[1] = llvm::Type* for unwind_fn + * - llvm_argtype_v[2+i] = llvm::Type* for lambda->fn_arg(i) + */ + std::vector llvm_argtype_v; + { + llvm_argtype_v.reserve(2 + lambda_llvm_fn->arg_size()); + + /* frame pointer */ + llvm_argtype_v.push_back(baseframeptr_llvm_type); + /* unwind function */ + llvm_argtype_v.push_back(unwind_llvm_type); + + int i_arg = 0; + for (auto & arg : lambda_llvm_fn->args()) { + log && log(xtag("i_arg", i_arg), + xtag("param", std::string(arg.getName()))); + + llvm_argtype_v.push_back(td_to_llvm_type(llvm_cx, + lambda->fn_arg(i_arg))); + + ++i_arg; + } + } + + frame_llvm_type->setBody(llvm_argtype_v); + + return frame_llvm_type; + } /*frame_to_llvm_type*/ + } /*namespace*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + /* generate function body -- code to execute later if/when function is called */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /* create stack frame */ + llvm::StructType * frame_llvm_type + = frame_to_llvm_type(llvm_cx_, + lambda, + llvm_fn); + + llvm::AllocaInst * frame_alloca + = create_entry_frame_alloca(llvm_fn, + frame_llvm_type); + + if (!frame_alloca) + return nullptr; + + /** Actual parameters will need their own activation record. + * Track its shape here. + * + * Local variables will be formal parameters to a nested lambda; + **/ + this->env_stack_.push(activation_record(llvm_fn, frame_alloca)); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i_slot = 0; + int n_slot = 2 + lambda->n_arg(); + + auto arg_ix = llvm_fn->arg_begin(); + + for (i_slot = 0; i_slot < n_slot; ++i_slot) { + // TODO: move the frame-slot computation into helper function + + /* argument i_slot-2 */ + llvm::Value * frame_slot_ptr = nullptr; + { + /* note: we have to create instructions here because + * the llvm IR is invariant w.r.t. data layout. + * But we can't compute byte offsets until later + * when data layout is revealed. + */ + + llvm::Value * i32_zero + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, 0)); + llvm::Value * i32_slot + = llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APInt(32, i_slot)); + + std::array index_v = { + {i32_zero /*deref frame pointer*/, + i32_slot /*field# relative to frame pointer*/}}; + + /* location in stack frame of arg #i */ + frame_slot_ptr + = ir_builder.CreateInBoundsGEP(frame_llvm_type, + frame_alloca, + index_v); + } + + if (i_slot == 0) { + /* frame pointer */ + assert(false); + } else if (i_slot == 1) { + /* unwind function */ + assert(false); + } else { + int i_arg = i_slot - 2; + + std::string arg_name = std::string(lambda->argv().at(i_arg)->name()); + + /* store param on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&(*arg_ix), + frame_slot_ptr /*destination*/); + + ++arg_ix; + } + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + /* frame pointer */ + llvm::Value * + MachPipeline::codegen_global_frameptr(llvm::IRBuilder<> & ir_builder) + { + /* only want to jit this once */ + + /* though really: this would be once per thread */ + + llvm::Type * baseframe_ptr_llvm_type + = require_baseframe_ptr_llvm_type(llvm_cx_); + + /** nullptr - initial value for global frame pointer **/ + llvm::Constant * nullptr_llvm_value + = llvm::ConstantPointerNull::get(baseframe_ptr_llvm_type); + + llvm::Value * fp_llvm_value + = llvm::GlobalVariable(baseframe_ptr_llvm_type, + false /*!isConstant*/ + llvm::GlobalValues::LinkOnceOdrLinkage, + nullptr_llvm_value /*Initializer*/, + "frame_pointer"); + + } /*codegen_global_frameptr*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + std::int32_t i_slot = env_stack_.top().lookup_var(var->name()); + //llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/MachPipeline.orig.cpp b/src/jit/MachPipeline.orig.cpp new file mode 100644 index 00000000..69cf0d02 --- /dev/null +++ b/src/jit/MachPipeline.orig.cpp @@ -0,0 +1,1064 @@ +/* @file MachPipeline.cpp */ + +#include "MachPipeline.hpp" +#include + +namespace xo { + using xo::ast::exprtype; + using xo::ast::Expression; + using xo::ast::ConstantInterface; + //using xo::ast::FunctionInterface; + using xo::ast::PrimitiveInterface; + using xo::ast::Lambda; + using xo::ast::Variable; + using xo::ast::Apply; + using xo::ast::IfExpr; + using xo::ast::llvmintrinsic; + using xo::reflect::Reflect; + using xo::reflect::StructMember; + using xo::reflect::TypeDescr; + using llvm::orc::ExecutionSession; + using llvm::DataLayout; + using std::cerr; + using std::endl; + + namespace jit { + void + MachPipeline::init_once() { + static bool s_init_once = false; + + if (!s_init_once) { + s_init_once = true; + + llvm::InitializeNativeTarget(); + llvm::InitializeNativeTargetAsmPrinter(); + llvm::InitializeNativeTargetAsmParser(); + } + } /*init_once*/ + + /* tracking KaleidoscopeJIT::Create() here.. + * + * Verified: + * + 'execution session' as per Kaleidoscope JIT, + * can instantiate from python + * + 'jit object layer' + * (realtime dynamic library object linking layer) + * + 'jit_compile_layer' + * + 'jit_our_dynamic_lib' + */ + llvm::Expected> + MachPipeline::make_aux() + { + MachPipeline::init_once(); + + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(Jit::Create()); + + return std::unique_ptr(new MachPipeline(std::move(jit) + )); + } /*make*/ + + xo::ref::rp + MachPipeline::make() { + static llvm::ExitOnError llvm_exit_on_err; + + std::unique_ptr jit = llvm_exit_on_err(make_aux()); + + return jit.release(); + } /*make*/ + + MachPipeline::MachPipeline(std::unique_ptr jit) + : jit_{std::move(jit)} + { + this->recreate_llvm_ir_pipeline(); + } + + void + MachPipeline::recreate_llvm_ir_pipeline() + { + //llvm_cx_ = std::make_unique(); + llvm_cx_ = LlvmContext::make(); + llvm_toplevel_ir_builder_ = std::make_unique>(llvm_cx_->llvm_cx_ref()); + + llvm_module_ = std::make_unique("xojit", llvm_cx_->llvm_cx_ref()); + llvm_module_->setDataLayout(this->jit_->data_layout()); + + if (!llvm_cx_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm context"); + } + if (!llvm_toplevel_ir_builder_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm IR builder"); + } + if (!llvm_module_.get()) { + throw std::runtime_error("MachPipeline::ctor: expected non-empty llvm module"); + } + + ir_pipeline_ = new IrPipeline(llvm_cx_); + } /*recreate_llvm_ir_pipeline*/ + + const DataLayout & + MachPipeline::data_layout() const { + return this->jit_->data_layout(); + } + + const ExecutionSession * + MachPipeline::xsession() const { + return this->jit_->xsession(); + } + + /** identifies target host/architecture for machine code. + * e.g. "x86_64-unknown-linux-gnu" + **/ + const std::string & + MachPipeline::target_triple() const { + // although this getter is defined, seems to be empty in practice + //return llvm_module_->getTargetTriple(); + + return this->jit_->target_triple(); + } + + std::vector + MachPipeline::get_function_name_v() { + std::vector retval; + for (const auto & fn_name : *llvm_module_) + retval.push_back(fn_name.getName().str()); + + return retval; + } /*get_function_names*/ + + void + MachPipeline::dump_execution_session() { + this->jit_->dump_execution_session(); + } + + llvm::Value * + MachPipeline::codegen_constant(ref::brw expr) + { + TypeDescr td = expr->value_td(); + + if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } else if (Reflect::is_native(td)) { + return llvm::ConstantInt::get(llvm_cx_->llvm_cx_ref(), + llvm::APSInt(*(expr->value_tp().recover_native()))); + } + + return nullptr; + } /*codegen_constant*/ + + namespace { + /** REMINDER: + * 1. creation of llvm types is idempotent + * (duplicate calls will receive the same llvm::Type* pointer) + * 2. llvm::Types are never deleted. + **/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td); + + /** obtain llvm representation for a function type with the same signature as + * that represented by @p fn_td + **/ + llvm::FunctionType * + function_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + int n_fn_arg = fn_td->n_fn_arg(); + + std::vector llvm_argtype_v; + llvm_argtype_v.reserve(n_fn_arg); + + /** check function args are all known **/ + for (int i = 0; i < n_fn_arg; ++i) { + TypeDescr arg_td = fn_td->fn_arg(i); + + llvm::Type * llvm_argtype = td_to_llvm_type(llvm_cx, arg_td); + + if (!llvm_argtype) + return nullptr; + + llvm_argtype_v.push_back(llvm_argtype); + } + + TypeDescr retval_td = fn_td->fn_retval(); + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx, retval_td); + + if (!llvm_retval) + return nullptr; + + auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval, + llvm_argtype_v, + false /*!varargs*/); + return llvm_fn_type; + } + + llvm::PointerType * + function_td_to_llvm_fnptr_type(xo::ref::brw llvm_cx, + TypeDescr fn_td) + { + auto * llvm_fn_type = function_td_to_llvm_type(llvm_cx, fn_td); + + /** like C: llvm IR doesn't support function-valued variables; + * it does however support pointer-to-function-valued variables + **/ + auto * llvm_ptr_type + = llvm::PointerType::get(llvm_fn_type, + 0 /*numbered address space*/); + + return llvm_ptr_type; + } + + /** + * Generate llvm::Type correspoinding to a TypeDescr for a struct. + **/ + llvm::StructType * + struct_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr struct_td) + { + // see + // [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]] + + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + /* note: object pointer ignored for struct types, + * since number of members is known at compile time + */ + int n_member = struct_td->n_child(nullptr /*&object*/); + + /* one type for each struct member */ + std::vector llvm_membertype_v; + llvm_membertype_v.reserve(n_member); + + for (int i = 0; i < n_member; ++i) { + StructMember const & sm = struct_td->struct_member(i); + + llvm_membertype_v.push_back(td_to_llvm_type(llvm_cx, + sm.get_member_td())); + } + + std::string struct_name = std::string(struct_td->short_name()); + + /* structs with names: within an llvmcontext, must be unique + * + * We can however compare the offsets recorded in xo::reflect with + * offsets chosen by llvm, *once we've created the llvm type* + * + * Also, we can't guarantee that a c++ type was completely reflected -- + * it's possible one or more members were omitted, in which case + * it's unlikely at best that llvm chooses the same layout. + * + * Instead: tell llvm to make packed struct, + * and introduce dummy members for padding. + * + * A consequence is we have to maintain mapping between llvm's + * member numbering and xo::reflect's + */ + llvm::StructType * llvm_struct_type + = llvm::StructType::create(llvm_cx_ref, + llvm_membertype_v, + llvm::StringRef(struct_name), + false /*!isPacked*/); + + /* TODO: inspect (how) offsets that llvm is using + * we need them to match what C++ chose + * + * (because we want jitted llvm code to interoperate with + * C++ library code that has structs) + */ + + // GetElementPtrInst is interesting, + // but I think that's for generating code + + return llvm_struct_type; + } /*struct_td_to_llvm_type*/ + + llvm::PointerType * + pointer_td_to_llvm_type(xo::ref::brw llvm_cx, + TypeDescr pointer_td) + { + assert(pointer_td->is_pointer()); + + TypeDescr dest_td = pointer_td->fixed_child_td(0); + + llvm::Type * llvm_dest_type = td_to_llvm_type(llvm_cx, dest_td); + + llvm::PointerType * llvm_ptr_type + = llvm::PointerType::getUnqual(llvm_dest_type); + + return llvm_ptr_type; + } /*pointer_td_llvm_type*/ + + llvm::Type * + td_to_llvm_type(xo::ref::brw llvm_cx, TypeDescr td) { + auto & llvm_cx_ref = llvm_cx->llvm_cx_ref(); + + if (td->is_function()) { + /* in this context, we're looking for a representation for a value, + * i.e. something that can be stored in a variable + */ + return function_td_to_llvm_fnptr_type(llvm_cx, td); + } else if (td->is_struct()) { + return struct_td_to_llvm_type(llvm_cx, td); + } else if (td->is_pointer()) { + return pointer_td_to_llvm_type(llvm_cx, td); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt1Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt8Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt16Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt32Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getInt64Ty(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getFloatTy(llvm_cx_ref); + } else if (Reflect::is_native(td)) { + return llvm::Type::getDoubleTy(llvm_cx_ref); + } else { + cerr << "td_to_llvm_type: no llvm type available for T" + << xtag("T", td->short_name()) + << endl; + return nullptr; + } + } + } + + llvm::Type * + MachPipeline::codegen_type(TypeDescr td) { + return td_to_llvm_type(llvm_cx_.borrow(), td); + } + + llvm::Function * + MachPipeline::codegen_primitive(ref::brw expr) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag)); + + /** note: documentation (such as it is) for llvm::Function here: + * + * https://llvm.org/doxygenL/classllvm_1_1Function.html + **/ + + auto * fn = llvm_module_->getFunction(expr->name()); + + if (fn) { + /** function with this name already known to llvm module; + * use that definition + * + * TODO: verify that signatures match! + **/ + return fn; + } + + /** establish prototype for this function **/ + + TypeDescr fn_td = expr->valuetype(); + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), fn_td); + + if (!llvm_fn_type) + return nullptr; + + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + expr->name(), + llvm_module_.get()); + +#ifdef NOT_USING + // set names for arguments (for diagnostics?). Monkey-see-kaleidoscope-monkey-do here + { + int i_arg = 0; + for (auto & arg : fn->args()) { + std::stringstream ss; + ss << "x_" << i_arg; + + arg.setName(ss.str()); + ++i_arg; + } + } +#endif + + if (expr->explicit_symbol_def()) { + static llvm::ExitOnError llvm_exit_on_err; + + auto name = expr->name(); + auto fn_addr = expr->function_address(); + + log && log(xtag("sym", name), + xtag("mangled_sym", this->jit_->mangle(name))); + + llvm_exit_on_err(this->jit_->intern_symbol(name, fn_addr)); + +#ifdef NOT_USING + if (!llvm_result) { + cerr << "MachPipeline::codegen_primitive" + << ": intern_symbol failed" + << xtag("name", expr->name()) + << xtag("addr", expr->function_address()) + << endl; + + return nullptr; + } +#endif + } else { + log && log("not requiring absolute address", xtag("sym", expr->name())); + } + +#ifdef OBSOLETE + log && log("returning llvm function"); +#endif + + return fn; + } /*codegen_primitive*/ + + llvm::Value * + MachPipeline::codegen_apply(ref::brw apply, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("apply", apply)); + + // see here: + // https://stackoverflow.com/questions/54905211/how-to-implement-function-pointer-by-using-llvm-c-api + + using std::cerr; + using std::endl; + + /* IR for value in function position. + * Although it will generate a function (or pointer-to-function), + * it need not have inherited type llvm::Function. + */ + llvm::Value * llvm_fnval = nullptr; + llvmintrinsic intrinsic = llvmintrinsic::invalid; + /* function type in apply node's function position */ + TypeDescr ast_fn_td = apply->fn()->valuetype(); + { + /* special treatement for primitive in apply position: + * allows substituting LLVM intrinsic + */ + if (apply->fn()->extype() == exprtype::primitive) { + auto pm = PrimitiveInterface::from(apply->fn()); + + if (pm) { + llvm_fnval = this->codegen_primitive(pm); + /* hint, when available. use faster alternative to IRBuilder::CreateCall below */ + intrinsic = pm->intrinsic(); + } + } else { + llvm_fnval = this->codegen(apply->fn(), ir_builder); + + /* we don't need any special checking here. + * already know (from xo-level checking) that pointer has the right type. + * + * Specifically, xo::ast::Apply::make() requires the expression in function position + * have suitable function type. + * + * Now: we have an llvm::Value (fn_value) representing the pointer. + * However it's not an llvm::Function instance, and we can't get one. + * + * (Older LLVM versions allowed getting the element type from a pointer, + * for some reasons that's deprecated at least in 18.1.5) + */ + } + } + + if (!llvm_fnval) { + return nullptr; + } + +#ifdef NOT_USING_DEBUG + cerr << "MachPipeline::codegen_apply: fn:" << endl; + fn->print(llvm::errs()); + cerr << endl; +#endif + + /* checks here will be redundant */ + +#ifdef REDUNDANT_TYPECHECK + if (apply->argv().size() != ast_fn_td->n_fn_arg()) { + cerr << "MachPipeline::codegen_apply: error: callee f expecting n1 args where n2 supplied" + //<< xtag("f", ast_fn->name()) + << xtag("n1", ast_fn_td->n_fn_arg()) + << xtag("n2", apply->argv().size()) + << endl; + + return nullptr; + } + + /** also check argument types **/ + for (size_t i = 0, n = ast_fn_td->n_fn_arg(); i < n; ++i) { + if (apply->argv()[i]->valuetype() != ast_fn_td->fn_arg(i)) { + cerr << "MachPipeline::codegen_apply: error: callee F for arg# I seeeing U instead of expected T" + << xtag("F", apply->fn()) + << xtag("I", i) + << xtag("U", apply->argv()[i]->valuetype()->short_name()) + << xtag("T", ast_fn_td->fn_arg(i)->short_name()) + << endl; + + return nullptr; + } + } +#endif + + std::vector args; + args.reserve(apply->argv().size()); + + int i = 0; + for (const auto & arg_expr : apply->argv()) { + auto * arg = this->codegen(arg_expr, ir_builder); + + if (log) { + /* TODO: print helper for llvm::Value* */ + std::string llvm_value_str; + llvm::raw_string_ostream ss(llvm_value_str); + arg->print(ss); + + log(xtag("i_arg", i), + xtag("arg", llvm_value_str)); + } + + args.push_back(arg); + ++i; + } + + /* if we have an intrinsic hint, + * then instead of invoking a function, + * we use some native machine instruction instead. + */ + switch(intrinsic) { + case llvmintrinsic::i_neg: + return ir_builder.CreateNeg(args[0]); + case llvmintrinsic::i_add: + return ir_builder.CreateAdd(args[0], args[1]); + case llvmintrinsic::i_sub: + return ir_builder.CreateSub(args[0], args[1]); + case llvmintrinsic::i_mul: + return ir_builder.CreateMul(args[0], args[1]); + case llvmintrinsic::i_sdiv: + return ir_builder.CreateSDiv(args[0], args[1]); + case llvmintrinsic::i_udiv: + return ir_builder.CreateUDiv(args[0], args[1]); + case llvmintrinsic::fp_add: + return ir_builder.CreateFAdd(args[0], args[1]); + case llvmintrinsic::fp_mul: + return ir_builder.CreateFMul(args[0], args[1]); + case llvmintrinsic::fp_div: + return ir_builder.CreateFDiv(args[0], args[1]); + case llvmintrinsic::invalid: + case llvmintrinsic::fp_sqrt: + case llvmintrinsic::fp_pow: + case llvmintrinsic::fp_sin: + case llvmintrinsic::fp_cos: + case llvmintrinsic::fp_tan: + case llvmintrinsic::n_intrinsic: /* n_intrinsic: not reachable */ + break; + } + + /* At least as of 18.1.5, LLVM needs us to supply function type + * when making a function call. In particular it doesn't remember + * the function type with each function pointer + */ + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(this->llvm_cx_, ast_fn_td); + + return ir_builder.CreateCall(llvm_fn_type, + llvm_fnval, + args, + "calltmp"); + + } /*codegen_apply*/ + + /* in kaleidoscope7.cpp: CreateEntryBlockAlloca */ + llvm::AllocaInst * + MachPipeline::create_entry_block_alloca(llvm::Function * llvm_fn, + const std::string & var_name, + TypeDescr var_type) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("llvm_fn", (void*)llvm_fn), + xtag("var_name", var_name), + xtag("var_type", var_type->short_name())); + + llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(), + llvm_fn->getEntryBlock().begin()); + + llvm::Type * llvm_var_type = td_to_llvm_type(llvm_cx_.borrow(), + var_type); + + log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type)); + if (log) { + std::string llvm_var_type_str; + llvm::raw_string_ostream ss(llvm_var_type_str); + llvm_var_type->print(ss); + + log(xtag("llvm_var_type", llvm_var_type_str)); + } + + if (!llvm_var_type) + return nullptr; + + llvm::AllocaInst * retval = tmp_ir_builder.CreateAlloca(llvm_var_type, + nullptr, + var_name); + log && log(xtag("alloca", (void*)retval), + xtag("align", retval->getAlign().value()), + xtag("size", retval->getAllocationSize(jit_->data_layout()).value())); + + return retval; + } /*create_entry_block_alloca*/ + + + std::vector> + MachPipeline::find_lambdas(ref::brw expr) const + { + std::vector> retval_v; + + expr->visit_preorder( + [&retval_v](ref::brw x) + { + if (x->extype() == exprtype::lambda) { + retval_v.push_back(Lambda::from(x)); + } + }); + + return retval_v; + } /*find_lambdas*/ + + llvm::Function * + MachPipeline::codegen_lambda_decl(ref::brw lambda) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * fn = llvm_module_->getFunction(lambda->name()); + + if (fn) { + return fn; + } + + /* establish prototype for this function */ + +#ifdef OBSOLETE + llvm::Type * llvm_retval = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_retval()); + + std::vector arg_type_v(lambda->n_arg()); + + for (size_t i = 0, n = lambda->n_arg(); i < n; ++i) { + arg_type_v[i] = td_to_llvm_type(llvm_cx_.borrow(), + lambda->fn_arg(i)); + } +#endif + + llvm::FunctionType * llvm_fn_type + = function_td_to_llvm_type(llvm_cx_.borrow(), + lambda->valuetype()); + + /* create (initially empty) function */ + fn = llvm::Function::Create(llvm_fn_type, + llvm::Function::ExternalLinkage, + lambda->name(), + llvm_module_.get()); + /* also capture argument names */ + { + int i = 0; + for (auto & arg : fn->args()) { + log && log("llvm formal param names", + xtag("i", i), + xtag("param", lambda->argv().at(i))); + + arg.setName(lambda->argv().at(i)->name()); + ++i; + } + } + + return fn; + } /*codegen_lambda_decl*/ + + llvm::Function * + MachPipeline::codegen_lambda_defn(ref::brw lambda, + llvm::IRBuilder<> & ir_builder) + { + constexpr bool c_debug_flag = true; + using xo::scope; + + scope log(XO_DEBUG(c_debug_flag), + xtag("lambda-name", lambda->name())); + + global_env_[lambda->name()] = lambda.get(); + + /* do we already know a function with this name? */ + auto * llvm_fn = llvm_module_->getFunction(lambda->name()); + + if (!llvm_fn) { + /** function with this name not declared? **/ + cerr << "MachPipeline::codegen_lambda: function f not declared" + << xtag("f", lambda->name()) + << endl; + + return nullptr; + } + + + /* generate function body */ + + auto block = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), "entry", llvm_fn); + + ir_builder.SetInsertPoint(block); + + /** Actual parameters will need their own activation record. + * Track its shape here. + **/ + this->env_stack_.push(activation_record()); + + { + log && log("lambda: stack size Z", xtag("Z", env_stack_.size())); + + int i = 0; + for (auto & arg : llvm_fn->args()) { + log && log("nested environment", + xtag("i", i), + xtag("param", std::string(arg.getName()))); + + std::string arg_name = std::string(arg.getName()); + + /* stack location for arg[i] */ + llvm::AllocaInst * alloca + = create_entry_block_alloca(llvm_fn, + arg_name, + lambda->fn_arg(i)); + + if (!alloca) { + this->env_stack_.pop(); + return nullptr; + } + + /* store on function entry + * see codegen_variable() for corresponding load + */ + ir_builder.CreateStore(&arg, alloca); + + /* remember stack location for reference + assignment + * in lambda body. + * + */ + env_stack_.top().alloc_var(arg_name, alloca); + ++i; + } + } + + llvm::Value * retval = this->codegen(lambda->body(), ir_builder); + + if (retval) { + /* completes the function.. */ + ir_builder.CreateRet(retval); + + /* validate! always validate! */ + llvm::verifyFunction(*llvm_fn); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-before-opt", buf)); + } + + /* optimize! improves IR */ + ir_pipeline_->run_pipeline(*llvm_fn); // llvm_fpmgr_->run(*llvm_fn, *llvm_famgr_); + + if (log) { + std::string buf; + llvm::raw_string_ostream ss(buf); + llvm_fn->print(ss); + + log(xtag("IR-after-opt", buf)); + } + } else { + /* oops, something went wrong */ + llvm_fn->eraseFromParent(); + + llvm_fn = nullptr; + } + + this->env_stack_.pop(); + + log && log("after pop, env stack size Z", xtag("Z", env_stack_.size())); + + return llvm_fn; + } /*codegen_lambda_defn*/ + + llvm::Value * + MachPipeline::codegen_variable(ref::brw var, + llvm::IRBuilder<> & ir_builder) + { + if (env_stack_.empty()) { + cerr << "MachPipeline::codegen_variable: expected non-empty environment stack" + << xtag("x", var->name()) + << endl; + + return nullptr; + } + + llvm::AllocaInst * alloca = env_stack_.top().lookup_var(var->name()); + + if (!alloca) + return nullptr; + + /* code to load value from stack */ + return ir_builder.CreateLoad(alloca->getAllocatedType(), + alloca, + var->name().c_str()); + } /*codegen_variable*/ + + llvm::Value * + MachPipeline::codegen_ifexpr(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + llvm::Value * test_ir = this->codegen(expr->test(), ir_builder); + + /** need test result in a variable **/ + llvm::Value * test_with_cmp_ir + = ir_builder.CreateFCmpONE(test_ir, + llvm::ConstantFP::get(llvm_cx_->llvm_cx_ref(), + llvm::APFloat(0.0)), + "iftest"); + + llvm::Function * parent_fn = ir_builder.GetInsertBlock()->getParent(); + + /* when_true_bb, when_false_bb, merge_bb: + * initially-empty basic-blocks for {when_true, when_false, merged} codegen + */ + + /* when_true branch inserted at (current) end of function */ + llvm::BasicBlock * when_true_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_true", + parent_fn); + llvm::BasicBlock * when_false_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "when_false"); + + llvm::BasicBlock * merge_bb + = llvm::BasicBlock::Create(llvm_cx_->llvm_cx_ref(), + "merge"); + + /* IR to direct control flow to one of {when_true_bb, when_false_bb}, + * depending on result of test_with_cmp_ir + */ + ir_builder.CreateCondBr(test_with_cmp_ir, + when_true_bb, + when_false_bb); + + /* populate when_true_bb */ + ir_builder.SetInsertPoint(when_true_bb); + + llvm::Value * when_true_ir = this->codegen(expr->when_true(), + ir_builder); + + if (!when_true_ir) + return nullptr; + + /* at end of when-true sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_true() may have altered builder's "current block" */ + when_true_bb = ir_builder.GetInsertBlock(); + + /* populate when_false_bb */ + parent_fn->insert(parent_fn->end(), when_false_bb); + ir_builder.SetInsertPoint(when_false_bb); + + llvm::Value * when_false_ir = this->codegen(expr->when_false(), ir_builder); + if (!when_false_ir) + return nullptr; + + /* at end of when-false sequence, jump to merge suffix */ + ir_builder.CreateBr(merge_bb); + /* note: codegen for expr->when_false() may have altered builder's "current block" */ + when_false_bb = ir_builder.GetInsertBlock(); + + /* merged suffix sequence */ + parent_fn->insert(parent_fn->end(), merge_bb); + ir_builder.SetInsertPoint(merge_bb); + + /** TODO: switch to getInt1Ty here **/ + llvm::PHINode * phi_node + = ir_builder.CreatePHI(llvm::Type::getDoubleTy(llvm_cx_->llvm_cx_ref()), + 2 /*#of branches being merged (?)*/, + "iftmp"); + phi_node->addIncoming(when_true_ir, when_true_bb); + phi_node->addIncoming(when_false_ir, when_false_bb); + + return phi_node; + } /*codegen_ifexpr*/ + + llvm::Value * + MachPipeline::codegen(ref::brw expr, llvm::IRBuilder<> & ir_builder) + { + switch(expr->extype()) { + case exprtype::constant: + return this->codegen_constant(ConstantInterface::from(expr)); + case exprtype::primitive: + return this->codegen_primitive(PrimitiveInterface::from(expr)); + case exprtype::apply: + return this->codegen_apply(Apply::from(expr), ir_builder); + case exprtype::lambda: + return this->codegen_lambda_decl(Lambda::from(expr)); + case exprtype::variable: + return this->codegen_variable(Variable::from(expr), ir_builder); + case exprtype::ifexpr: + return this->codegen_ifexpr(IfExpr::from(expr), ir_builder); + case exprtype::invalid: + case exprtype::n_expr: + return nullptr; + break; + } + + cerr << "MachPipeline::codegen: error: no handler for expression of type T" + << xtag("T", expr->extype()) + << endl; + + return nullptr; + } /*codegen*/ + + llvm::Value * + MachPipeline::codegen_toplevel(ref::brw expr) + { + /* - Pass 1. + * get set of lambdas. + * Generate decls for all. + * + * TODO: for lexical scoping (not implemented yet) + * will need traversal that maintains stack + * of ancestor lambdas, or at least their + * activation records. May want to generalize + * activation_record so we can track the set of variables + * before generating AllocaInst's. + * + * - Pass 2. + * Generate code for lambdas. + * + * - Pass 3. + * If toplevel expressions isn't a lambda + * (? won't make sense at present when called from python) + * generate code for it too + */ + + /* Pass 1. */ + auto fn_v = this->find_lambdas(expr); + + for (auto lambda : fn_v) { + this->codegen_lambda_decl(lambda); + } + + /* Pass 2 */ + for (auto lambda : fn_v) { + this->codegen_lambda_defn(lambda, + *(this->llvm_toplevel_ir_builder_.get())); + } + + /* Pass 3 */ + if (expr->extype() == exprtype::lambda) { + /* code already generated in pass 2; + * look it up + */ + + return llvm_module_->getFunction(Lambda::from(expr)->name()); + } else { + /* toplevel expression isn't a lambda, + * so code for it hasn't been generated. + * Do that now + */ + return this->codegen(expr, + *(this->llvm_toplevel_ir_builder_.get())); + } + } /*codegen_toplevel*/ + + void + MachPipeline::dump_current_module() + { + /* dump module contents to console */ + + llvm_module_->dump(); + } + + void + MachPipeline::machgen_current_module() + { + static llvm::ExitOnError llvm_exit_on_err; + + auto tracker = this->jit_->dest_dynamic_lib_ref().createResourceTracker(); + + /* invalidates llvm_cx_->llvm_cx_ref(); will discard and re-create + * + * Note that @ref ir_pipeline_ holds reference, which is invalidated here + */ + auto ts_module = llvm::orc::ThreadSafeModule(std::move(llvm_module_), + std::move(llvm_cx_->llvm_cx())); + + /* note does not discard llvm_cx_->llvm_cx(), it's already been moved */ + this->llvm_cx_ = nullptr; + + llvm_exit_on_err(this->jit_->add_llvm_module(std::move(ts_module), tracker)); + + this->recreate_llvm_ir_pipeline(); + } /*machgen_current_module*/ + + std::string_view + MachPipeline::mangle(const std::string & sym) const + { + return this->jit_->mangle(sym); + } /*mangle*/ + + llvm::Expected + MachPipeline::lookup_symbol(const std::string & sym) + { + /* llvm_sym: ExecutorSymbolDef */ + auto llvm_sym_expected = this->jit_->lookup(sym); + + if (llvm_sym_expected) { + auto llvm_addr = llvm_sym_expected.get().getAddress(); + + return llvm_addr; + } else { + return llvm_sym_expected.takeError(); + } + } /*lookup_symbol*/ + + void + MachPipeline::display(std::ostream & os) const { + os << ""; + } + + std::string + MachPipeline::display_string() const { + return tostr(*this); + } + } /*namespace jit*/ +} /*namespace xo*/ + +/* end MachPipeline.cpp */ diff --git a/src/jit/activation_record.new.cpp b/src/jit/activation_record.new.cpp new file mode 100644 index 00000000..cc1aaae3 --- /dev/null +++ b/src/jit/activation_record.new.cpp @@ -0,0 +1,47 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + int32_t + activation_record::lookup_var(const std::string & x) const { + + auto ix = name2ix_map_.find(x); + + if (ix == name2ix_map_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return -1; + } + + return ix->second; + } /*lookup_var*/ + +#ifdef OBSOLETE + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ +#endif + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */ diff --git a/src/jit/activation_record.orig.cpp b/src/jit/activation_record.orig.cpp new file mode 100644 index 00000000..c7f40362 --- /dev/null +++ b/src/jit/activation_record.orig.cpp @@ -0,0 +1,45 @@ +/* @file activation_record.cpp */ + +#include "activation_record.hpp" +#include "xo/indentlog/print/tag.hpp" +#include + +namespace xo { + namespace jit { + using std::cerr; + using std::endl; + + llvm::AllocaInst * + activation_record::lookup_var(const std::string & x) const { + + auto ix = frame_.find(x); + + if (ix == frame_.end()) { + cerr << "activation_record::lookup_var: no binding for variable x" + << xtag("x", x) + << endl; + return nullptr; + } + + return ix->second; + } /*lookup_var*/ + + llvm::AllocaInst * + activation_record::alloc_var(const std::string & x, + llvm::AllocaInst * alloca) + { + if (frame_.find(x) != frame_.end()) { + cerr << "activation_record::alloc_var: variable x already present in frame" + << xtag("x", x) + << endl; + return nullptr; + } + + frame_[x] = alloca; + return alloca; + } /*alloc_var*/ + } /*namespace jit*/ +} /*namespace xo*/ + + +/* end activation_record.cpp */