commit 432c369a66e985732584b72ae0f881ca4ce60666 Author: Roland Conybeare Date: Thu Jun 13 15:21:17 2024 -0400 xo-jit: initial commit (codegen constants + primitives, sort of) 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 */