diff --git a/xo-expression/.gitignore b/xo-expression/.gitignore new file mode 100644 index 00000000..f3b23fc3 --- /dev/null +++ b/xo-expression/.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/xo-expression/CMakeLists.txt b/xo-expression/CMakeLists.txt new file mode 100644 index 00000000..468778de --- /dev/null +++ b/xo-expression/CMakeLists.txt @@ -0,0 +1,36 @@ +# expression/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_expression 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 expression lib before configuring examples +add_subdirectory(src/expression) + +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/xo-expression/LESSONS b/xo-expression/LESSONS new file mode 100644 index 00000000..c719f02e --- /dev/null +++ b/xo-expression/LESSONS @@ -0,0 +1,9 @@ +* want reflection to work without requiring std::type_info, + so that in xo-expression we can create function types that + C++ compiler has never encountered. + +** This means we need the ability to construct the canonical name + for a type without c++ compiler's assistance. + +** need lookup using function ingredients (return types + arg types + noexcept) + to intern lambda types diff --git a/xo-expression/LICENSE b/xo-expression/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/xo-expression/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/xo-expression/README.md b/xo-expression/README.md new file mode 100644 index 00000000..151c0493 --- /dev/null +++ b/xo-expression/README.md @@ -0,0 +1,63 @@ +# xo-expression library + +A library for representing abstract syntax trees for EGAD (a small expression-based language). +See also +- [github/Rconybea/xo-jit](https://github.com/Rconybea/xo-jit) + EGAD code generation via LLVM +- [github/Rconybea/xo-pyexpression](https://github.com/Rconybea/xo-pyexpression) + build EGAD expressions from a python session +- [github/Rconybea/xo-pyjit](https://github.com/Rconybea/xo-pyjit) + compile + run EGAD expressions from a python session + +## 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 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 +``` + +Note: can use `xo-build -n` to dry-run here + +### copy `xo-expression` repository locally +``` +$ xo-build --clone xo-expression +``` + +or eqivalently +``` +$ git clone git@github.com:Rconybea/xo-expression.git +``` + +### build + install xo-expression +``` +$ xo-build --configure --build --install xo-expression +``` + +or equivalently: +``` +$ PREFIX=/usr/local # or wherever you prefer +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -S xo-expression -B xo-jit/.build +$ cmake --build xo-expression/.build +$ cmake --install xo-expression/.build +``` + +### build for unit test coverage +``` +$ cmake -DCMAKE_BUILD_TYPE=coverage -DCMAKE_INSTALL_PREFIX=$PREFIX xo-expression/.build-ccov +$ cmake --build xo-expression/.build-ccov +``` + +### LSP support +``` +$ cd xo-expression +$ ln -s build/compile_commands.json # lsp will look for compile_commands.json in the root of the source tree +``` diff --git a/xo-expression/TODO b/xo-expression/TODO new file mode 100644 index 00000000..c2a91ce8 --- /dev/null +++ b/xo-expression/TODO @@ -0,0 +1,3 @@ +- In xo-reflect, TypeDescr objects are immortal. + In original context rationale was recording information for already-established C++ types. + Maybe want to revisit that choice when associating TypeDescr objects with expressions? diff --git a/xo-expression/cmake/xo-bootstrap-macros.cmake b/xo-expression/cmake/xo-bootstrap-macros.cmake new file mode 100755 index 00000000..aba31169 --- /dev/null +++ b/xo-expression/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/xo-expression/cmake/xo_expressionConfig.cmake.in b/xo-expression/cmake/xo_expressionConfig.cmake.in new file mode 100644 index 00000000..e7f7f1be --- /dev/null +++ b/xo-expression/cmake/xo_expressionConfig.cmake.in @@ -0,0 +1,6 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(reflect) +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-expression/example/CMakeLists.txt b/xo-expression/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/xo-expression/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/xo-expression/example/ex1/CMakeLists.txt b/xo-expression/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..d7e8b3b6 --- /dev/null +++ b/xo-expression/example/ex1/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-expression/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_expression_ex1) +set(SELF_SRCS ex1.cpp) + +if (XO_ENABLE_EXAMPLES) + xo_add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_self_dependency(${SELF_EXE} xo_expression) + xo_dependency(${SELF_EXE} refcnt) +endif() + +# end CMakeLists.txt diff --git a/xo-expression/example/ex1/ex1.cpp b/xo-expression/example/ex1/ex1.cpp new file mode 100644 index 00000000..5dd91fe2 --- /dev/null +++ b/xo-expression/example/ex1/ex1.cpp @@ -0,0 +1,39 @@ +/** @file ex1.cpp **/ + +#include "xo/expression/Constant.hpp" +#include "xo/expression/Primitive.hpp" +#include "xo/expression/llvmintrinsic.hpp" +#include +#include + +int +main() { + using xo::ast::make_constant; + using xo::ast::make_primitive; + using xo::ast::llvmintrinsic; + using std::cout; + using std::endl; + + { + auto expr = make_constant(7); + } + + { + auto expr = make_primitive("sqrt", + &::sqrt, + false /*!explicit_symbol_def*/, + llvmintrinsic::fp_sqrt); + + auto expr_td = expr->value_td(); + + cout << "expr_td: " << expr_td->short_name() << endl; + cout << "expr_td->is_function(): " << expr_td->is_function() << endl; + cout << "expr_td->fn_retval(): " << expr_td->fn_retval()->short_name() << endl; + cout << "expr_td->n_fn_arg(): " << expr_td->n_fn_arg() << endl; + for (uint32_t i = 0; i < expr_td->n_fn_arg(); ++i) + cout << "expr_td->fn_arg(" << i << "): " << expr_td->fn_arg(i)->short_name() << endl; + cout << "expr_td->fn_is_noexcept(): " << expr_td->fn_is_noexcept() << endl; + } +} + +/** end ex1.cpp **/ diff --git a/xo-expression/include/xo/expression/Apply.hpp b/xo-expression/include/xo/expression/Apply.hpp new file mode 100644 index 00000000..e95dd76a --- /dev/null +++ b/xo-expression/include/xo/expression/Apply.hpp @@ -0,0 +1,163 @@ +/** @file Apply.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" + +//#include + +namespace xo { + namespace ast { + + /** @class Apply + * @brief syntax for a function call. + * + * In general we don't know function to be invoked + * until runtime, depending on the nature of Expression. + **/ + class Apply : public Expression { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** create new apply-expression instance + **/ + static rp make(const rp & fn, + const std::vector> & argv); + + /** create apply-expression to add two 64-bit floating-point numbers **/ + static rp make_add2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to subtract two 64-bit floating-point numbers **/ + static rp make_sub2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to multiply two 64-bit floating-point numbers **/ + static rp make_mul2_f64(const rp & lhs, + const rp & rhs); + /** create apply-expression to divide two 64-bit floating-point numbers **/ + static rp make_div2_f64(const rp & lhs, + const rp & rhs); + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & fn() const { return fn_; } + const std::vector> & argv() const { return argv_; } + + virtual std::set get_free_variables() const override { + std::set retval = fn_->get_free_variables(); + + for (const auto & arg : argv_) { + std::set arg_free_set + = arg->get_free_variables(); + + for (const auto & name : arg_free_set) + retval.insert(name); + } + + return retval; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += fn_->visit_preorder(visitor_fn); + + for (const auto & arg : argv_) + n += arg->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += fn_->visit_layer(visitor_fn); + + for (const auto & arg : argv_) + n += arg->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->fn_ = fn_->xform_layer(xform_fn); + + for (auto & arg : argv_) + arg = arg->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + fn_->attach_envs(p); + + for (const auto & arg : argv_) + arg->attach_envs(p); + } + + virtual void display(std::ostream & os) const override; + + private: + Apply(TypeDescr apply_valuetype, + const rp & fn, + const std::vector> & argv) + : Expression(exprtype::apply, apply_valuetype), + fn_{fn}, argv_(argv) + {} + + private: + /** function to invoke **/ + rp fn_; + /** argument expressions, in l-to-r order **/ + std::vector> argv_; + }; /*Apply*/ + +#ifdef NOT_USING + namespace detail { + /** Use: + ** std::vector> + **/ + + template + struct apply_push_args; + + template <> + struct apply_push_args<> { + static void push_all(std::vector> * /*p_argv*/) {} + }; + + template + struct apply_push_args { + static void push_all(std::vector> * p_argv, + const ref::rp & x, Rest... rest) + { + p_argv->push_back(x); + apply_push_args::push_all(p_argv, rest...); + }; + }; + } +#endif + + /* reminder: initializer-lists are compile-time only */ + inline rp + make_apply(const rp & fn, + const std::initializer_list> args) { + std::vector> argv(args); + return Apply::make(fn, argv); + } /*make_apply*/ + + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end Apply.hpp **/ diff --git a/xo-expression/include/xo/expression/AssignExpr.hpp b/xo-expression/include/xo/expression/AssignExpr.hpp new file mode 100644 index 00000000..0e7d5e5a --- /dev/null +++ b/xo-expression/include/xo/expression/AssignExpr.hpp @@ -0,0 +1,61 @@ +/* file AssignExpr.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "Expression.hpp" +#include "Variable.hpp" + +namespace xo { + namespace ast { + /** @class AssignExpr + * @brief Provide expression for assigning to a variable + * + * def pi = 3.14159265; + * def foo = 0.0; + * foo := pi / 2; + **/ + class AssignExpr : public Expression { + public: + static rp make(const rp & lhs, + const rp & rhs); + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & lhs() const { return lhs_; } + const rp & rhs() const { return rhs_; } + + std::set calc_free_variables() const; + + // ----- inherited from Expression ----- + + virtual std::set get_free_variables() const override; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override; + virtual std::size_t visit_layer(VisitFn visitor_fn) override; + virtual rp xform_layer(TransformFn xform_fn) override; + virtual void attach_envs(ref::brw p) override; + + virtual void display(std::ostream & os) const override; + + private: + AssignExpr(const rp & lhs, + const rp & rhs); + + private: + /** assign to this variable. **/ + rp lhs_; + /** assign value of this expression to variable @p lhs **/ + rp rhs_; + + /** free variables for this assignment **/ + std::set free_var_set_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end AssignExpr.hpp */ diff --git a/xo-expression/include/xo/expression/Constant.hpp b/xo-expression/include/xo/expression/Constant.hpp new file mode 100644 index 00000000..b2c9f71d --- /dev/null +++ b/xo-expression/include/xo/expression/Constant.hpp @@ -0,0 +1,103 @@ +/** @file Constant.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "ConstantInterface.hpp" +#include + +namespace xo { + namespace ast { + /** @class Constant + * @brief syntax for a literal constant. + * + * Require: + * 1. T must be a POD type (plain old data) + * We need this to be true so that we can generate + * code for constructing a T instance by memcopying + * @ref value_ + * + * @tp T type of captured literal. + **/ + template + class Constant : public ConstantInterface { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** create constant expression representing literal value x **/ + static rp make(const T & x) { + TypeDescr x_valuetype = Reflect::require(); + + return new Constant(x_valuetype, x); + } + + const T & value() const { return value_; } + + // ----- ConstantInterface ----- + + virtual TypeDescr value_td() const override { return value_td_; } + virtual TaggedPtr value_tp() const override { + /* note: idk why, but need to spell this out in two steps with gcc 13.2 */ + const void * erased_cptr = &value_; + void * erased_ptr = const_cast(erased_cptr); + + return TaggedPtr(value_td_, erased_ptr); + } + + // ----- Expression ----- + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + + virtual void display(std::ostream & os) const override { + os << "short_name()); + else + os << xtag("type", "nullptr");; + os << xtag("value", value_); + os << ">"; + } + + private: + explicit Constant(TypeDescr x_type, const T & x) + : ConstantInterface(exprtype::constant, x_type), + value_td_{Reflect::require()}, + value_(x) + { + static_assert(std::is_standard_layout_v && std::is_trivial_v); + } + + private: + /** type description for T **/ + TypeDescr value_td_; + /** value of this constant **/ + T value_; + }; /*Constant*/ + + template + rp>> + make_constant(const T & x) { + return Constant::make(x); + } + + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Constant.hpp **/ diff --git a/xo-expression/include/xo/expression/ConstantInterface.hpp b/xo-expression/include/xo/expression/ConstantInterface.hpp new file mode 100644 index 00000000..0b10ec41 --- /dev/null +++ b/xo-expression/include/xo/expression/ConstantInterface.hpp @@ -0,0 +1,51 @@ +/** @file ConstantInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include "xo/reflect/Reflect.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include + +namespace xo { + namespace ast { + /** @class ConstantInterface + * @brief syntax for a literal constant. + **/ + class ConstantInterface : public Expression { + public: + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** @p extype sets expression-type; could be constant|primitive **/ + ConstantInterface(exprtype extype, TypeDescr valuetype) : Expression{extype, valuetype} {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + /** type description for representation of literal value **/ + virtual TypeDescr value_td() const = 0; + /** reflection-tagged pointer to literal value of this constant **/ + virtual TaggedPtr value_tp() const = 0; + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + return std::set(); + } + + virtual void attach_envs(ref::brw /*p*/) override {} + + + }; /*ConstantInterface*/ + + } /*namespace ast*/ +} /*namespace xo*/ + +/** end ConstantInterface.hpp **/ diff --git a/xo-expression/include/xo/expression/ConvertExpr.hpp b/xo-expression/include/xo/expression/ConvertExpr.hpp new file mode 100644 index 00000000..8c5f9b70 --- /dev/null +++ b/xo-expression/include/xo/expression/ConvertExpr.hpp @@ -0,0 +1,109 @@ +/* file ConvertExpr.hpp + * + * author: Roland Conybeare, Aug 2024 + */ + +#pragma once + +#include "Expression.hpp" + +namespace xo { + namespace ast { + /** @class Convertexpr + * @brief Convenience for automatic type conversion + * + * This is equivalent to calling a built-in primitive + * that performs the conversion. + * + * We rely on this for convenience, for example to parse + * code like + * + * def foo : i16 = 0 + **/ + class ConvertExpr : public Expression { + public: + static rp make(TypeDescr dest_type, + rp arg); + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & arg() const { return arg_; } + + // ----- Expression ----- + + virtual std::set get_free_variables() const override; + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += arg_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->arg_->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->arg_ = this->arg_->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + arg_->attach_envs(p); + } + + virtual void display(std::ostream & os) const override; + + protected: + ConvertExpr(TypeDescr dest_type, + rp arg) + : Expression(exprtype::convert, dest_type), + arg_{std::move(arg)} + {} + + protected: + /** source expression. Convert + * @c arg_->valuetype() to @c dest_type_ + **/ + rp arg_; + }; + + /** @class ConvertExprAccess + * @brief ConvertExpr with writeable members. + * + * Convenient when scaffolding a parser, + * e.g. see xo-parser + **/ + class ConvertExprAccess : public ConvertExpr { + public: + static rp make(TypeDescr dest_type, + rp arg); + static rp make_empty(); + + void assign_arg(rp arg) { this->arg_ = std::move(arg); } + + private: + ConvertExprAccess(TypeDescr dest_type, + rp arg) + : ConvertExpr(dest_type, + std::move(arg)) + {} + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end ConvertExpr.hpp */ diff --git a/xo-expression/include/xo/expression/DefineExpr.hpp b/xo-expression/include/xo/expression/DefineExpr.hpp new file mode 100644 index 00000000..e7ffce65 --- /dev/null +++ b/xo-expression/include/xo/expression/DefineExpr.hpp @@ -0,0 +1,128 @@ +/* file DefineExpr.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +#include "Expression.hpp" + +namespace xo { + namespace ast { + /** @class DefineExpr + * @brief Provide definition for a constant, variable or function + * + * At toplevel, introduces a new global variable. + * In a nested context, + * + * def foo = rhsexpr + * body... + * + * is equivalent to + * + * (lambda (foo) body...)(rhsexpr) + **/ + class DefineExpr : public Expression { + public: + static rp make(std::string name, + rp value); + + + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & lhs_name() const { return lhs_name_; } + const rp & rhs() const { return rhs_; } + + std::set calc_free_variables() const; + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + return this->free_var_set_; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += rhs_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->rhs_->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->rhs_ = this->rhs_->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + rhs_->attach_envs(p); + } + + virtual void display(std::ostream & os) const override; + + protected: + /** + * + **/ + DefineExpr(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs); + + protected: + /** symbol name for this definition **/ + std::string lhs_name_; + /** right-hand side of definition **/ + rp rhs_; + + /** free variables for this definition **/ + std::set free_var_set_; + }; + + /** @class DefineExprAccess + * @brief DefineExpr with writeable members. + * + * Convenient when scaffolding a parser, + * e.g. see xo-parser + **/ + class DefineExprAccess : public DefineExpr { + public: + static rp make(std::string lhs_name, + rp rhs); + static rp make_empty(); + + void assign_lhs_name(const std::string & x) { + this->lhs_name_ = x; + } + + void assign_rhs(const rp & x); + + private: + DefineExprAccess(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs) + : DefineExpr(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)) + {} + }; + + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end DefineExpr.hpp */ diff --git a/xo-expression/include/xo/expression/Environment.hpp b/xo-expression/include/xo/expression/Environment.hpp new file mode 100644 index 00000000..b827a575 --- /dev/null +++ b/xo-expression/include/xo/expression/Environment.hpp @@ -0,0 +1,41 @@ +/* file Environment.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "binding_path.hpp" + +namespace xo { + namespace ast { + class Expression; + + class Environment : public ref::Refcount { + public: + /** true if this is toplevel (global) environment. + * Toplevel environment doesn't have slot numbers. + * + * Variables that bind in the global environment have unique + * names, which we rely on instead of slot numbers. + **/ + virtual bool is_global_env() const = 0; + + /** lookup binding path for @p vname in this environment. + * + * Reports ingredients needed to address variable at runtime, + * in runtime analog of this environment + **/ + virtual binding_path lookup_binding(const std::string & vname) const = 0; + + /** lookup variable-expression @p vname in this environment. + * returns llvm::Value representing code that produces a value for vname + **/ + virtual ref::brw lookup_var(const std::string & vname) const = 0; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Environment.hpp */ diff --git a/xo-expression/include/xo/expression/Expression.hpp b/xo-expression/include/xo/expression/Expression.hpp new file mode 100644 index 00000000..1b71ffcd --- /dev/null +++ b/xo-expression/include/xo/expression/Expression.hpp @@ -0,0 +1,90 @@ +/** @file Expression.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "GeneralizedExpression.hpp" +#include +#include + +namespace xo { + namespace ast { + class Variable; /* see Variable.hpp */ + class Lambda; /* see Lamnbda.hpp */ + class Environment; /* see Environment.hpp */ + + /** @class Expression + * @brief abstract syntax tree for an EGAD program + * + * (Expression Graph with Automagic Derivation) + * + * Things you can do with an Expression: + * - evaluate it using an interpreter + * - execute it on a VM + * - compile using LLVM + * see xo-jit/ + * + * Expressions are immutable. This means they can resused + * across jit interactions + * + * Every expression evaluates to a value with a particular type + **/ + class Expression : public GeneralizedExpression { + public: + using VisitFn = std::function + )>; + using TransformFn = std::function + (ref::brw)>; + using TypeDescr = xo::reflect::TypeDescr; + + public: + explicit Expression(exprtype extype, TypeDescr valuetype) + : GeneralizedExpression(extype, valuetype) {} + + /** find free named variables in this expression. + * comprises the set of names that don't match formal parameters in + * enclosing lambdas. + **/ + virtual std::set get_free_variables() const = 0; + + /** visit each Expression node in this AST, + * and invoke @p fn for each. + * Returns the number of nodes visited. + * Preorder: call @p fn for a node before visiting children + **/ + virtual std::size_t visit_preorder(VisitFn visitor_fn) = 0; + + /** visit each Expression node in this AST, + * including immediately-nested Lambda nodes; + * but do not recurse into the params/body of such nested Lambdas. + * Returns the number of nodes visited + **/ + virtual std::size_t visit_layer(VisitFn visitor_fn) = 0; + + /** traverse ast @ref visit_preorder but do not visit Lambdas **/ + virtual rp xform_layer(TransformFn visitor_fn) = 0; + + /** attach an environment to each lambda expression X in this subtree, + * that will: + * - resolve names matching X's arguments (formal parameters) to + * from @p X.argv + * - resolve free variables from @p parent + **/ + virtual void attach_envs(ref::brw parent) = 0; + + /** append to *p_set the set of free variables in this expression. + * returns the number of free variables introduced + * + * @param env stack of lexcically-enclosing lamnbda expressions, + * in nesting order, i.e. outermost first, innertmost last + **/ + //virtual std::int32_t find_free_vars(std::vector> env) = 0; + + }; /*Expression*/ + + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Expression.hpp **/ diff --git a/xo-expression/include/xo/expression/FunctionInterface.hpp b/xo-expression/include/xo/expression/FunctionInterface.hpp new file mode 100644 index 00000000..a51dc69b --- /dev/null +++ b/xo-expression/include/xo/expression/FunctionInterface.hpp @@ -0,0 +1,34 @@ +/** @file FunctionInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +//#include + +namespace xo { + namespace ast { + class FunctionInterface : public Expression { + public: + FunctionInterface(exprtype extype, TypeDescr fn_type) + : Expression(extype, fn_type) {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + virtual const std::string & name() const = 0; + virtual int n_arg() const = 0; // { return this->value_td()->n_fn_arg(); } + virtual TypeDescr fn_retval() const = 0; // { return this->value_td()->fn_retval(); } + virtual TypeDescr fn_arg(uint32_t i) const = 0; // { return this->value_td()->fn_arg(i); } + + private: + }; /*FunctionInterface*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end FunctionInterface.hpp **/ diff --git a/xo-expression/include/xo/expression/GeneralizedExpression.hpp b/xo-expression/include/xo/expression/GeneralizedExpression.hpp new file mode 100644 index 00000000..7866284e --- /dev/null +++ b/xo-expression/include/xo/expression/GeneralizedExpression.hpp @@ -0,0 +1,60 @@ +/** @file GeneralizedExpression.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "xo/refcnt/Refcounted.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "exprtype.hpp" +//#include + +namespace xo { + namespace ast { + /** @class GeneralizedExpression + * @brief abstract syntax tree (non-executable) for schematica + * + * 'Generalized' because it includes both kernel and macro expressions. + * Every macro expression automatically translates to an equivalent kernel expression. + * Kernel expressions are directly executable. + **/ + class GeneralizedExpression : public ref::Refcount { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + GeneralizedExpression(exprtype extype, TypeDescr valuetype) + : extype_{extype}, valuetype_{valuetype}{} + + exprtype extype() const { return extype_; } + TypeDescr valuetype() const { return valuetype_; } + + /** write human-readable representation to stream **/ + virtual void display(std::ostream & os) const = 0; + /** human-readable string representation **/ + virtual std::string display_string() const; + + protected: + /** useful when scaffolding expressions in a parser **/ + void assign_valuetype(TypeDescr x) { valuetype_ = x; } + + private: + /** expression type (constant | apply | ..) for this expression **/ + exprtype extype_ = exprtype::invalid; + /** type information (when available) for values produced by this + * expression. + **/ + TypeDescr valuetype_ = nullptr; + }; + + inline std::ostream & + operator<<(std::ostream & os, const GeneralizedExpression & x) { + x.display(os); + return os; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end GeneralizedExpression.hpp **/ diff --git a/xo-expression/include/xo/expression/GlobalEnv.hpp b/xo-expression/include/xo/expression/GlobalEnv.hpp new file mode 100644 index 00000000..9cbf328f --- /dev/null +++ b/xo-expression/include/xo/expression/GlobalEnv.hpp @@ -0,0 +1,60 @@ +/* file GlobalEnv.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "Environment.hpp" +#include +#include + +namespace xo { + namespace ast { + class GlobalEnv : public Environment { + public: + /** create instance. Probably only need one of these **/ + static rp make() { return new GlobalEnv(); } + + ref::brw require_global(const std::string & vname, + ref::brw expr) { + global_map_[vname] = expr.get(); + return expr; + } /*require_global*/ + + // ----- Environment ----- + + virtual bool is_global_env() const override { return true; } + + virtual binding_path lookup_binding(const std::string & /*vname*/) const override { + /* i_link: -1 for global environment + * j_slot: not used + */ + return { -1, 0 }; + } + + virtual ref::brw lookup_var(const std::string & vname) const override { + auto ix = global_map_.find(vname); + + if (ix == global_map_.end()) { + /* not found */ + return ref::brw::from_native(nullptr); + } + + return ix->second; + } + + private: + GlobalEnv() = default; + + private: + /* for assignable globals, need to allocate memory + * addresses for these. + */ + std::map> global_map_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end GlobalEnv.hpp */ diff --git a/xo-expression/include/xo/expression/IfExpr.hpp b/xo-expression/include/xo/expression/IfExpr.hpp new file mode 100644 index 00000000..0f62f86d --- /dev/null +++ b/xo-expression/include/xo/expression/IfExpr.hpp @@ -0,0 +1,145 @@ +/** @file IfExpr.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include +#include +//#include + +namespace xo { + namespace ast { + /** @class IfExpr + * @brief abstract syntax tree for a function definition + * + **/ + class IfExpr : public Expression { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** create expression for conditional execution of + * @p when_true or @p when_false, depending on result + * of evaluating expression @p test + **/ + static rp make(const rp & test, + const rp & when_true, + const rp & when_false); + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const rp & test() const { return test_; } + const rp & when_true() const { return when_true_; } + const rp & when_false() const { return when_false_; } + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + std::set retval = test_->get_free_variables(); + + std::set free_vars; + free_vars = when_true_->get_free_variables(); + for (const auto & s : free_vars) + retval.insert(s); + + free_vars = when_false_->get_free_variables(); + for (const auto & s : free_vars) + retval.insert(s); + + return retval; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->test_->visit_preorder(visitor_fn); + n += this->when_true_->visit_preorder(visitor_fn); + n += this->when_false_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + n += this->test_->visit_layer(visitor_fn); + n += this->when_true_->visit_layer(visitor_fn); + n += this->when_false_->visit_layer(visitor_fn); + + return n; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + this->test_ = this->test_->xform_layer(xform_fn); + this->when_true_ = this->when_true_->xform_layer(xform_fn); + this->when_false_= this->when_false_->xform_layer(xform_fn); + + return xform_fn(this); + } + + virtual void attach_envs(ref::brw p) override { + test_->attach_envs(p); + when_true_->attach_envs(p); + when_false_->attach_envs(p); + } + +#ifdef NOT_USING + virtual std::int32_t find_free_vars(std::set> * p_set) override { + return (test_->find_free_vars(p_set) + + when_true_->find_free_vars(p_set) + + when_false_->find_free_vars(p_set)); + } +#endif + + virtual void display(std::ostream & os) const override; + + private: + /** + * @p ifexpr_type type for value produced by if-expression. + * same as both when_true->valuetype() and + * when_false->valuetype(). + * @p test test-expression; always execute + * @p when_true then-branch; executes only when test succeeds + * @p when_false else-branch; executes only when test fails + **/ + IfExpr(TypeDescr ifexpr_type, + rp test, + rp when_true, + rp when_false) + : Expression(exprtype::ifexpr, ifexpr_type), + test_{std::move(test)}, + when_true_{std::move(when_true)}, + when_false_{std::move(when_false)} {} + + private: + /** if: + * (if x y z) + * + * executes x; if true execute y; otherwise execute z + **/ + rp test_; + rp when_true_; + rp when_false_; + }; /*IfExpr*/ + + inline rp + make_ifexpr(const rp & test, + const rp & when_true, + const rp & when_false) + { + return IfExpr::make(test, when_true, when_false); + } + } /*namespace ast*/ +} /*namespace xo*/ + +/** end IfExpr.hpp **/ diff --git a/xo-expression/include/xo/expression/Lambda.hpp b/xo-expression/include/xo/expression/Lambda.hpp new file mode 100644 index 00000000..452cafc9 --- /dev/null +++ b/xo-expression/include/xo/expression/Lambda.hpp @@ -0,0 +1,208 @@ +/** @file Lambda.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include "FunctionInterface.hpp" +#include "Variable.hpp" +#include "LocalEnv.hpp" +#include +#include +#include +//#include + +namespace xo { + namespace ast { + /** @class Lambda + * @brief abstract syntax tree for a function definition + * + **/ + class Lambda : public FunctionInterface { + public: + /** + * @p name Name for this lambda -- must be unique + * @p argv Formal parameters, in left-to-right order + * @p body Expression for body of this function + **/ + static rp make(const std::string & name, + const std::vector> & argv, + const rp & body); + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & type_str() const { return type_str_; } + const std::vector> & argv() const { return local_env_->argv(); } + const rp & body() const { return body_; } + + const std::string& i_argname(int i_arg) const { return local_env_->lookup_arg(i_arg)->name(); } + bool needs_closure_flag() const { return !free_var_set_.empty(); } + bool is_captured(const std::string& var) const { return (captured_var_set_.find(var) != captured_var_set_.end()); } + + // ----- FunctionInterface ----- + + virtual const std::string & name() const override { return name_; } + /** return number of arguments expected by this function **/ + virtual int n_arg() const override { return local_env_->n_arg(); } + virtual TypeDescr fn_retval() const override { return body_->valuetype(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return local_env_->fn_arg(i); } + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + return this->free_var_set_; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & arg : local_env_->argv()) + n += arg->visit_preorder(visitor_fn); + + n += body_->visit_preorder(visitor_fn); + + return n; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + std::size_t n = 1; + + visitor_fn(this); + + return n; + } + + virtual rp xform_layer(TransformFn /*xform_fn*/) override { + /* a layer is bounded by lambdas, don't enter them */ + return this; + } + + virtual void attach_envs(ref::brw p) override; + + virtual void display(std::ostream & os) const override; + + protected: + /** create type description for lambda with arguments @p argv + * and body expression @p body + **/ + static TypeDescr assemble_lambda_td(const std::vector> & argv, + const rp & body); + + /** create string description for function signature, + * consistent with c++ expectation + **/ + static std::string assemble_type_str(TypeDescr lambda_td); + + /** @param lambda_type. function type for this lambda. + * We arbitrarily choose the form "Retval(*)(Args...)" + **/ + Lambda(const std::string & name, + TypeDescr lambda_type, + const rp & local_env, + const rp & body); + + /** compute free-variable set for this lambda **/ + std::set calc_free_variables() const; + + /** ensure at most one Variable instance with a particular name + * in this lambda, but ignore nested lambdas. + * + * Goal is to unify variables that can use the same binding + * path to determine memory location at runtime. + **/ + std::map> regularize_layer_vars(); + + /** compute derived members + * (type_str_, free_var_set_, captured_var_set_, layer_var_map_, + * nested_lambda_map_) + * once .body_ is established + **/ + void complete_assembly_from_body(); + + protected: + /** lambda name. Initially supporting only form like + * (define (foo x y z) + * (+ (* x x) (* y y) (* z z))) + * + * In any case need to supply names for distinct + * things-for-which-code-is-generated so that they can be linked etc. + **/ + std::string name_; + /** e.g. + * "double(double,double)" for function of two doubles that + * returns a double + **/ + std::string type_str_; + /** function body **/ + rp body_; + + /** free variables for this lambda **/ + std::set free_var_set_; + + /** variables that appear free in some nested lambda **/ + std::set captured_var_set_; + + /** map giving unique identity to each variable appearing in this layer. + * includes: + * - formal parameters + * - free variables in @ref body_ + * excludes: + * - any variables appearing in nested lambdas + * (whether formals or free variables) + **/ + std::map> layer_var_map_; + + /** all lambdas nested once inside this lambda's body **/ + std::map> nested_lambda_map_; + + /** established (once) by @ref attach_envs. + * + * @note data dependency on ancestor expressions that don't exist yet + * when Lambda constructor runs, so we need to assign @ref local_env_ + * later. + **/ + rp local_env_; + }; /*Lambda*/ + + inline rp + make_lambda(const std::string & name, + const std::vector> & argv, + const rp & body) + { + return Lambda::make(name, argv, body); + } + + class LambdaAccess : public Lambda { + public: + static rp make(const std::string & name, + const std::vector> & argv, + const rp & body); + static rp make_empty(); + + /** assign body + compute derived members + * (see complete_assembly_from_body()) + **/ + void assign_body(const rp & body); + + private: + /** lambda_type, body can be null here, + * in which case fill in with assign methods + **/ + LambdaAccess(const std::string & name, + TypeDescr lambda_type, + const rp & local_env, + const rp & body); + + + }; + } /*namespace ast*/ +} /*namespace xo*/ + +/** end Lambda.hpp **/ diff --git a/xo-expression/include/xo/expression/LocalEnv.hpp b/xo-expression/include/xo/expression/LocalEnv.hpp new file mode 100644 index 00000000..603bbfda --- /dev/null +++ b/xo-expression/include/xo/expression/LocalEnv.hpp @@ -0,0 +1,100 @@ +/* file LocalEnv.hpp + * + * author: Roland Conybeare, Jun 2024 + */ + +#pragma once + +#include "Environment.hpp" +#include "Variable.hpp" +#include "xo/reflect/TypeDescr.hpp" + +namespace xo { + namespace ast { + class Lambda; + + /** @brief LocalEnv + * + * @class Local environment for a lambda. + * Lists the Variables corresponding to this lambda's formal + * parameters, but also links to @ref Environment for + * innermost enclosing @ref Lambda. + **/ + class LocalEnv : public Environment { + public: + using TypeDescr = xo::reflect::TypeDescr; + + public: + /** named ctor idiom. Create instance with local variables per @p argv **/ + static rp make(const std::vector> & argv) { + return new LocalEnv(argv); + } + + Lambda * origin() const { return origin_; } + const std::vector> & argv() const { return argv_; } + const rp& lookup_arg(int i) const { return argv_[i]; } + int n_arg() const { return argv_.size(); } + TypeDescr fn_arg(uint32_t i) const { return argv_[i]->valuetype(); } + + /** report binding path for a formal parameter. + * Returns sentinel if @p vname doesn't appear in @ref argv_ + **/ + binding_path lookup_local_binding(const std::string & vname) const; + + /** single-assign this environment's origin **/ + void assign_origin(Lambda * p) { + assert(origin_ == nullptr); + origin_ = p; + } + + /** single-assign this environment's parent **/ + void assign_parent(ref::brw p) { + assert(parent_env_.get() == nullptr); + parent_env_ = p.get(); + } + + // ----- Environment ----- + + virtual bool is_global_env() const override { return false; } + + virtual binding_path lookup_binding(const std::string & vname) const override; + + virtual ref::brw lookup_var(const std::string & target) const override { + for (const auto & arg : argv_) { + if (arg->name() == target) + return arg; + } + + /* here: target not found in local vars, + * delegate to innermost ancestor + */ + return parent_env_->lookup_var(target); + } + + private: + LocalEnv(const std::vector> & argv) + : origin_{nullptr}, argv_(argv) {} + + private: + /** Lambnda for which this environment created. + * + * Invariant: + * @code + * origin_->local_env_ == this + * @endcode + **/ + Lambda * origin_ = nullptr; + + /** formal argument names **/ + std::vector> argv_; + + /** parent environment. A free variable in this lambda's + * body will be resolved by referring them to @ref parent_env_. + **/ + rp parent_env_; + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end LocalEnv.hpp */ diff --git a/xo-expression/include/xo/expression/Primitive.hpp b/xo-expression/include/xo/expression/Primitive.hpp new file mode 100644 index 00000000..2305bfe4 --- /dev/null +++ b/xo-expression/include/xo/expression/Primitive.hpp @@ -0,0 +1,164 @@ +/** @file Primitive.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "PrimitiveInterface.hpp" +#include "llvmintrinsic.hpp" +#include "xo/reflect/Reflect.hpp" +//#include + +extern "C" { + /* these symbols needed to link primitives */ + + /* see Primitive_f64::make() */ + double add2_f64(double x, double y); +}; + +namespace xo { + namespace ast { + /** @class Primitive + * @brief syntax for a constant that refers to a known function. + * + * Two cases here: + * 1. (always) primitive refers to a compiled (C/C++) function that we can invoke at runtime + * 2. (sometimes) primitive also refers to a function that is supported directly in llvm + * (e.g. floating-point addition). In that case @ref intrinsic_ + * identifies that direct support, provided it knows at codegen time which primitive + * is being invoked + * + * In any case, a primitive serves as both declaration and definition + * (May be possible to relax this to declaration-only using null value_ as sentinel..?) + * + * @tparam FunctionPointer a function-pointer type, e.g. double(*)(double). + * Must be in this "canonical form". std::function + * won't work here. + **/ + template + class Primitive: public PrimitiveInterface { + public: + using Reflect = xo::reflect::Reflect; + using TaggedPtr = xo::reflect::TaggedPtr; + using TypeDescr = xo::reflect::TypeDescr; + using fptr_type = FunctionPointer; + + public: + static rp make(const std::string & name, + FunctionPointer fnptr, + bool explicit_symbol_def, + llvmintrinsic intrinsic) { + TypeDescr fn_type = Reflect::require(); + + return new Primitive(fn_type, name, fnptr, explicit_symbol_def, intrinsic); + } + + /** see classes below for intrinsics **/ + + FunctionPointer value() const { return value_; } + + TypeDescr value_td() const { return value_td_; } + TaggedPtr value_tp() const { + /* note: idk why, but need to spell this out in two steps with gcc 13.2 */ + const void * erased_cptr = &value_; + void * erased_ptr = const_cast(erased_cptr); + + return TaggedPtr(value_td_, erased_ptr); + } + + // ----- PrimitiveInterface ----- + + virtual llvmintrinsic intrinsic() const override { return intrinsic_; } + virtual bool explicit_symbol_def() const override { return explicit_symbol_def_; } + virtual void_function_type function_address() const override { return reinterpret_cast(value_); } + + // ----- FunctionInterface ----- + + virtual std::string const & name() const override { return name_; } + virtual int n_arg() const override { return this->value_td()->n_fn_arg(); } + virtual TypeDescr fn_retval() const override { return this->value_td()->fn_retval(); } + virtual TypeDescr fn_arg(uint32_t i) const override { return this->value_td()->fn_arg(i); } + + // ----- Expression ----- + + virtual void display(std::ostream & os) const override { + os << "value_td()->short_name()) + << xtag("value", this->value()) + << ">"; + } + + private: + Primitive(TypeDescr fn_type, + const std::string & name, + FunctionPointer fnptr, + bool explicit_symbol_def, + llvmintrinsic intrinsic) + : PrimitiveInterface(fn_type), + name_{name}, + value_td_{Reflect::require_function()}, + value_{fnptr}, + explicit_symbol_def_{explicit_symbol_def}, + intrinsic_{intrinsic} + { + if (!value_td_->is_function()) + throw std::runtime_error("Primitive: expected function pointer"); + if (!value_td_->fn_retval()) + throw std::runtime_error("Primitive: expected non-null function return value"); + } + + + private: + // from Expression: + // exprtype extype_ + + /** name of this primitive, e.g. '+', 'sqrt' **/ + std::string name_; + /** type description for FunctionPointer **/ + TypeDescr value_td_; + /** address of executable function **/ + FunctionPointer value_; + /** for LLVM: if true, use Jit.intern_symbol() to provide explicit binding. + * + * Not obvious what distinguishes functions like ::sin(), ::sqrt() + * (which work without this) from symbols like ::mul_i32(), which require it. + **/ + bool explicit_symbol_def_ = false; + /** invalid: generate call (IRBuilder::CreateCall) + * all others: generate direct use of LLVM intrinsic + **/ + llvmintrinsic intrinsic_; + }; /*Primitive*/ + + /** adopt function @p x as a callable primitive function named @p name **/ + template + rp> + make_primitive(const std::string & name, + FunctionPointer x, + bool explicit_symbol_def, + llvmintrinsic intrinsic) + { + return Primitive::make(name, x, explicit_symbol_def, intrinsic); + } + + class Primitive_f64 : public Primitive { + public: + using PrimitiveType = Primitive; + + public: + /** add2_f64: add two 64-bit floating-point numbers **/ + static rp make_add2_f64(); + /** sub2_f64: subtract two 64-bit floating-point numbers **/ + static rp make_sub2_f64(); + /** mul2_f64: multiply two 64-bit floating-point numbers **/ + static rp make_mul2_f64(); + /** div2_f64: divide two 64-bit floating-point numbers **/ + static rp make_div2_f64(); + }; + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end Primitive.hpp **/ diff --git a/xo-expression/include/xo/expression/PrimitiveInterface.hpp b/xo-expression/include/xo/expression/PrimitiveInterface.hpp new file mode 100644 index 00000000..c8b0758b --- /dev/null +++ b/xo-expression/include/xo/expression/PrimitiveInterface.hpp @@ -0,0 +1,77 @@ +/** @file PrimitiveInterface.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "FunctionInterface.hpp" +#include "llvmintrinsic.hpp" + +//#include + +#include +namespace xo { + namespace ast { + class PrimitiveInterface : public FunctionInterface { + public: + using void_function_type = void (*)(); + + public: + PrimitiveInterface(TypeDescr fn_type) + : FunctionInterface(exprtype::primitive, fn_type) {} + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + /** if true, Jit will try to explicitly symbol for this primitive + * (instead of looking it up in host process). + * Don't know if this works. + * Do know it's not needed for ::sin(), ::sqrt(). + * Do know that my extern "C" functions like ::mul_i32(), ::mul_f64() + * need something else. + **/ + virtual bool explicit_symbol_def() const = 0; + + /** function address for this primitive **/ + virtual void_function_type function_address() const = 0; + + /** get llvm intrinsic hint for this primitive **/ + virtual llvmintrinsic intrinsic() const = 0; + + // virtual const std::string & name() const; + // virtual int n_arg() const; + // virtual TypeDescr fn_retval() const; + // virtual TypeDescr fn_arg(uint32_t i) const; + + // ----- Expression ----- + + virtual std::set get_free_variables() const override { + return std::set(); + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + + virtual void attach_envs(ref::brw /*p*/) override {} + + private: + }; /*PrimitiveInterface*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end PrimitiveInterface.hpp **/ diff --git a/xo-expression/include/xo/expression/Sequence.hpp b/xo-expression/include/xo/expression/Sequence.hpp new file mode 100644 index 00000000..af2fb0e3 --- /dev/null +++ b/xo-expression/include/xo/expression/Sequence.hpp @@ -0,0 +1,52 @@ +/** @file Sequence.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +//#include + +namespace xo { + namespace ast { + class Sequence : public Expression { + public: + Sequence(const std::vector> & xv) + : Expression(exprtype::sequence, + xv[xv.size() - 1]->valuetype()), + expr_v_(xv) {} + + static rp make(const std::vector> & xv) { return new Sequence(xv); } + + std::size_t size() const { return expr_v_.size(); } + const rp & operator[](std::size_t i) const { return expr_v_[i]; } + + // ----- from Expression ----- + + /** note: broken if .expr_v_ contains any def-exprs + * (will treat references to so-defined vars as free). + * must rewrite these first + **/ + virtual std::set get_free_variables() const override; + virtual std::size_t visit_preorder(VisitFn visitor_fn) override; + /** note: borken if .expr_v_ contains any def-exprs **/ + virtual std::size_t visit_layer(VisitFn visitor_fn) override; + virtual rp xform_layer(TransformFn visitor_fn) override; + virtual void attach_envs(ref::brw parent) override; + + // ----- from GeneralizedExpression ---- + + virtual void display(std::ostream & os) const override; + + private: + /** sequence of expressions; evaluate in left-to-right order. + **/ + std::vector> expr_v_; + }; + } /*namespace ast*/ + +} /*namespace xo*/ + + +/** end Sequence.hpp **/ diff --git a/xo-expression/include/xo/expression/Variable.hpp b/xo-expression/include/xo/expression/Variable.hpp new file mode 100644 index 00000000..43936f70 --- /dev/null +++ b/xo-expression/include/xo/expression/Variable.hpp @@ -0,0 +1,88 @@ +/** @file Variable.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "Expression.hpp" +#include "binding_path.hpp" + +namespace xo { + namespace ast { + + /** @class Variable + * @brief syntax for a variable reference + **/ + class Variable : public Expression { + public: + /** create expression representing a variable + * identified by @p name, that can take on values + * described by @p var_type. + **/ + static rp make(const std::string & name, + TypeDescr var_type) { + return new Variable(name, var_type); + } + + /** return copy of x: same var, different object identity **/ + static rp copy(ref::brw x) { + return new Variable(x->name(), x->valuetype()); + } + + /** downcast from Expression **/ + static ref::brw from(ref::brw x) { + return ref::brw::from(x); + } + + const std::string & name() const { return name_; } + + virtual std::set get_free_variables() const override { + std::set retval; + retval.insert(this->name_); + return retval; + } + + virtual std::size_t visit_preorder(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual std::size_t visit_layer(VisitFn visitor_fn) override { + visitor_fn(this); + return 1; + } + + virtual rp xform_layer(TransformFn xform_fn) override { + return xform_fn(this); + } + + virtual void attach_envs(ref::brw /*p*/) override; + + virtual void display(std::ostream & os) const override; + + private: + Variable(const std::string & name, + TypeDescr var_type) + : Expression(exprtype::variable, var_type), + name_{name} {} + + private: + /** variable name **/ + std::string name_; + /** navigate environment via this path to find runtime memory + * location for this variable + **/ + binding_path path_; + }; /*Variable*/ + + inline rp + make_var(const std::string & name, + reflect::TypeDescr var_type) { + return Variable::make(name, var_type); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end Variable.hpp **/ diff --git a/xo-expression/include/xo/expression/binding_path.hpp b/xo-expression/include/xo/expression/binding_path.hpp new file mode 100644 index 00000000..7aef8c51 --- /dev/null +++ b/xo-expression/include/xo/expression/binding_path.hpp @@ -0,0 +1,29 @@ +/* file binding_path.hpp + * + * author: Roland Conybeare, Jul 2024 + */ + +#pragma once + +namespace xo { + namespace ast { + /** @class path + * + * @brief path from the *use* of a variable to the environment + * providing its location. + **/ + struct binding_path { + /** @of parent links to traverse. -1 if global. -2 if sentinel **/ + int i_link_ = -2; + /** for variables bound in some local environment: + * slot# within that environment. + * + * Ignored if @ref i_link_ is -1 + **/ + int j_slot_ = 0; + }; /*binding_path*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end binding_path.hpp */ diff --git a/xo-expression/include/xo/expression/exprtype.hpp b/xo-expression/include/xo/expression/exprtype.hpp new file mode 100644 index 00000000..e9b8393c --- /dev/null +++ b/xo-expression/include/xo/expression/exprtype.hpp @@ -0,0 +1,81 @@ +/** @file exprtype.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +//#include + +namespace xo { + namespace ast { + /** @enum exprtype + * @brief enum to identify subclasses of xo::ast::Expression. + * + **/ + enum class exprtype { + /** sentinel value **/ + invalid = -1, + + /** literal constant. must satisfy both standard_layout_type + trivial **/ + constant, + /** a literal constant that refers to a linkable named function **/ + primitive, + /** variable/function definition **/ + define, + /** variable assignment **/ + assign, + /** function call **/ + apply, + /** function definition **/ + lambda, + /** variable reference **/ + variable, + /** if-then-else **/ + ifexpr, + /** sequence **/ + sequence, + /** type conversion **/ + convert, + + /** not an expression. comes last, counts entries **/ + n_expr + }; + + inline const char * + expr2str(exprtype x) + { + switch(x) { + case exprtype::invalid: return "?exprtype"; + case exprtype::constant: return "constant"; + case exprtype::primitive: return "primitive"; + case exprtype::define: return "define"; + case exprtype::assign: return "assign"; + case exprtype::apply: return "apply"; + case exprtype::lambda: return "lambda"; + case exprtype::variable: return "variable"; + case exprtype::ifexpr: return "if_expr"; + case exprtype::sequence: return "sequence"; + case exprtype::convert: return "convert"; + default: break; + } + + return "???exprtype???"; + } + + /** @brief number of built-in expression types, repr convenient for array sizing **/ + static constexpr std::size_t n_exprtype = static_cast(exprtype::n_expr); + + inline std::ostream & + operator<<(std::ostream & os, + exprtype x) + { + os << expr2str(x); + return os; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/** end exprtype.hpp **/ diff --git a/xo-expression/include/xo/expression/llvmintrinsic.hpp b/xo-expression/include/xo/expression/llvmintrinsic.hpp new file mode 100644 index 00000000..bc484640 --- /dev/null +++ b/xo-expression/include/xo/expression/llvmintrinsic.hpp @@ -0,0 +1,127 @@ +/** @file llvmintrinsic.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +//#include + +namespace xo { + namespace ast { + /** @enum llvminstrinsic + * @brief enum to identify an LLVM instrinsic, e.g. @c IRBuilder::CreateFAdd + * + * Associate an @c llvminstrinsic with an AST @c Primitive p. + * Later, in @c xo::jit::IrPipeline + * - when generating code for @c xo::ast::Apply + * - with *p* is in the function-call position + * can use the associated llvm instrinsic instead of generating a function call + * @c Primitive::value + * + * @note llvm will still sometimes need to use + * @c Primitive::value (and generate a function call sequence), + * for example when handling an @c xo::ast::Apply instance + * where the function position is a computed function. + * @endnote + * + * @note + * LLVM requires separate intrinsics for {ints, floats}. + * It does not need separate intrinsics for different sizes. + * For example IRBuilder::CreateAdd works for + * {8-bit, 16-bit, 32-bit, 64-bit, 128-bit} x {signed, unsigned} integers + * Integer division is an exception; need to choose between i_sdiv and i_udiv + * @endnote + * + * @note + * NSW stands for 'no signed wrap' -> poison value if overflow (costs more) + * NUW stands for 'no unsigned wrap' -> poison value if overflow (costs more) + * @endnote + **/ + enum class llvmintrinsic { + // see /nix/store/x5yz...llvm-18.1.5-dev/include/llvm/IR/IRBuilder.h + + /** sentinel value **/ + invalid = -1, + + /** -> IRBuilder::CreateNeg (negate 1 integer) **/ + i_neg, + + /** -> IRBuilder::CreateAdd (add 2 integers, overflow silently) **/ + i_add, + + /** -> IRBuilder::CreateSub (subtract 2 integers, overflow silently) **/ + i_sub, + + /** -> IRBuilder::CreateMul (multiply 2 integers, overflow silently) **/ + i_mul, + + /** -> IRBuilder::CreateSdiv (divide 2 signed integers) **/ + i_sdiv, + + /** -> IRBuilder::CreateUdiv (divide 2 unsigned integers) **/ + i_udiv, + + /** -> IRBuilder::CreateFadd (add 2 floating-point numbers) **/ + fp_add, + + /** -> IRBuilder::CreateFsub (subtract 2 floating-pointer numbers) **/ + fp_sub, + + /** -> IRBuilder::CreateFmul (multiply 2 floating-point numbers) **/ + fp_mul, + + /** -> IRBuilder::CreateFdiv (divide 2 floating-point numbers) **/ + fp_div, + + /** + * want to do whatever llvm IR @c llvm.sqrt.f64 and friends do. + * Not sure if that's an always-available function of something else + **/ + fp_sqrt, + + /** WIP **/ + fp_pow, + + /** WIP **/ + fp_sin, + + /** WIP **/ + fp_cos, + + /** WIP **/ + fp_tan, + + /** not an intrinsic. comes last, counts entries **/ + n_intrinsic + }; + + inline const char * + llvmintrinsic2str(llvmintrinsic x) + { + switch(x) { + case llvmintrinsic::invalid: return "?llvminstrinsic"; + case llvmintrinsic::i_neg: return "i_neg"; + case llvmintrinsic::i_add: return "i_add"; + case llvmintrinsic::i_sub: return "i_sub"; + case llvmintrinsic::i_mul: return "i_mul"; + case llvmintrinsic::i_sdiv: return "i_sdiv"; + case llvmintrinsic::i_udiv: return "i_udiv"; + case llvmintrinsic::fp_add: return "fp_add"; + case llvmintrinsic::fp_sub: return "fp_sub"; + case llvmintrinsic::fp_mul: return "fp_mul"; + case llvmintrinsic::fp_div: return "fp_div"; + case llvmintrinsic::fp_sqrt: return "fp_sqrt"; + case llvmintrinsic::fp_pow: return "fp_pow"; + case llvmintrinsic::fp_sin: return "fp_sin"; + case llvmintrinsic::fp_cos: return "fp_cos"; + case llvmintrinsic::fp_tan: return "fp_tan"; + default: break; + } + + return "???llvmintrinsic???"; + } /*llvmintrinsic2str*/ + } /*namespace ast*/ +} /*namespace xo*/ + +/** end llvmintrinsic.hpp **/ diff --git a/xo-expression/src/expression/Apply.cpp b/xo-expression/src/expression/Apply.cpp new file mode 100644 index 00000000..497565b1 --- /dev/null +++ b/xo-expression/src/expression/Apply.cpp @@ -0,0 +1,72 @@ +/* @file Apply.cpp */ + +#include "Apply.hpp" +#include "Primitive.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + namespace ast { + rp + Apply::make(const rp & fn, + const std::vector> & argv) + { + /* extract result type from function type */ + TypeDescr fn_valuetype = fn->valuetype(); + + if (!fn_valuetype->is_function()) { + throw std::runtime_error + (tostr("Apply::make: found expression F in function position," + " with value-type FT where a function type expected", + xtag("FT", fn_valuetype->short_name()), + xtag("F", fn_valuetype))); + } + + TypeDescr fn_retval_type = fn_valuetype->fn_retval(); + + return new Apply(fn_retval_type, fn, argv); + } + + rp + Apply::make_add2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_add2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_sub2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_sub2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_mul2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_mul2_f64(), + {lhs, rhs}); + } + + rp + Apply::make_div2_f64(const rp & lhs, + const rp & rhs) + { + return Apply::make(Primitive_f64::make_div2_f64(), + {lhs, rhs}); + } + + void + Apply::display(std::ostream & os) const { + os << ""; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Apply.cpp */ diff --git a/xo-expression/src/expression/AssignExpr.cpp b/xo-expression/src/expression/AssignExpr.cpp new file mode 100644 index 00000000..8d56a058 --- /dev/null +++ b/xo-expression/src/expression/AssignExpr.cpp @@ -0,0 +1,93 @@ +/* file AssignExpr.cpp + * + * author: Roland Conybeare + */ + +#include "AssignExpr.hpp" +#include "xo/indentlog/print/tag.hpp" + +namespace xo { + namespace ast { + rp + AssignExpr::make(const rp & lhs, + const rp & rhs) + { + return new AssignExpr(lhs, rhs); + } + + AssignExpr::AssignExpr(const rp & lhs, + const rp & rhs) + : Expression(exprtype::assign, rhs->valuetype()), + lhs_{lhs}, rhs_{rhs} + { + this->free_var_set_ = this->calc_free_variables(); + } + + std::set + AssignExpr::calc_free_variables() const + { + std::set retval = lhs_->get_free_variables(); + + std::set tmp = rhs_->get_free_variables(); + + for (const auto & name : tmp) + retval.insert(name); + + return retval; + } + + std::set + AssignExpr::get_free_variables() const { + return free_var_set_; + } + + std::size_t + AssignExpr::visit_preorder(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + n += lhs_->visit_preorder(visitor_fn); + n += rhs_->visit_preorder(visitor_fn); + + return n; + } + + std::size_t + AssignExpr::visit_layer(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + n += lhs_->visit_layer(visitor_fn); + n += rhs_->visit_layer(visitor_fn); + + return n; + } + + rp + AssignExpr::xform_layer(TransformFn xform_fn) { + this->lhs_ = Variable::from(lhs_->xform_layer(xform_fn)).promote(); + this->rhs_ = rhs_->xform_layer(xform_fn); + + return xform_fn(this); + } + + void + AssignExpr::attach_envs(ref::brw p) { + lhs_->attach_envs(p); + rhs_->attach_envs(p); + } + + void + AssignExpr::display(std::ostream & os) const { + os << ""; + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end AssignExpr.cpp */ diff --git a/xo-expression/src/expression/CMakeLists.txt b/xo-expression/src/expression/CMakeLists.txt new file mode 100644 index 00000000..0241ec67 --- /dev/null +++ b/xo-expression/src/expression/CMakeLists.txt @@ -0,0 +1,24 @@ +# expression/CMakeLists.txt + +set(SELF_LIB xo_expression) +set(SELF_SRCS + GeneralizedExpression.cpp + Expression.cpp + DefineExpr.cpp + AssignExpr.cpp + Apply.cpp + Lambda.cpp + Variable.cpp + IfExpr.cpp + Sequence.cpp + LocalEnv.cpp + ConvertExpr.cpp + Primitive.cpp +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_dependency(${SELF_LIB} reflect) +#xo_dependency(${SELF_LIB} indentlog) +#xo_dependency(${SELF_LIB} subsys) + +# end CMakeLists.txt diff --git a/xo-expression/src/expression/ConvertExpr.cpp b/xo-expression/src/expression/ConvertExpr.cpp new file mode 100644 index 00000000..2e741f1b --- /dev/null +++ b/xo-expression/src/expression/ConvertExpr.cpp @@ -0,0 +1,53 @@ +/* file ConvertExpr.cpp + * + * author: Roland Conybeare + */ + +#include "ConvertExpr.hpp" + +namespace xo { + namespace ast { + rp + ConvertExpr::make(TypeDescr dest_type, + rp arg) + { + return new ConvertExpr(dest_type, + std::move(arg)); + } + + std::set + ConvertExpr::get_free_variables() const { + if (this->arg_) + return this->arg_->get_free_variables(); + else + return std::set(); + } + + void + ConvertExpr::display(std::ostream & os) const { + os << "valuetype()->short_name()) + << xtag("arg", arg_) + << ">"; + } + + // ----- ConvertExprAccess ----- + + rp + ConvertExprAccess::make(TypeDescr dest_type, + rp arg) + { + return new ConvertExprAccess(dest_type, + std::move(arg)); + } + + rp + ConvertExprAccess::make_empty() { + return new ConvertExprAccess(nullptr /*dest_type*/, + nullptr /*arg*/); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end ConvertExpr.cpp */ diff --git a/xo-expression/src/expression/DefineExpr.cpp b/xo-expression/src/expression/DefineExpr.cpp new file mode 100644 index 00000000..1c67188c --- /dev/null +++ b/xo-expression/src/expression/DefineExpr.cpp @@ -0,0 +1,99 @@ +/* file DefineExpr.cpp + * + * author: Roland Conybeare + */ + +#include "DefineExpr.hpp" + +namespace xo { + namespace ast { + rp + DefineExpr::make(std::string lhs_name, + rp rhs) + { + TypeDescr rhs_valuetype = nullptr; + + if (rhs) + rhs_valuetype = rhs->valuetype(); + + return new DefineExpr(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)); + } /*make*/ + + DefineExpr::DefineExpr(TypeDescr rhs_valuetype, + std::string lhs_name, + rp rhs) + : Expression(exprtype::define, rhs_valuetype), + lhs_name_{std::move(lhs_name)}, + rhs_{std::move(rhs)} + { + this->free_var_set_ = this->calc_free_variables(); + } + + std::set + DefineExpr::calc_free_variables() const + { + std::set retval; + + if (rhs_) + retval = rhs_->get_free_variables(); + + /* but remove this variable */ + if (!this->lhs_name().empty()) + retval.erase(this->lhs_name()); + + return retval; + } /*calc_free_variables*/ + + void + DefineExpr::display(std::ostream & os) const { + os << ""; + } /*display*/ + + // ----- DefineExprAccess ----- + + rp + DefineExprAccess::make(std::string lhs_name, + rp rhs) + { + TypeDescr rhs_valuetype = nullptr; + + if (rhs) + rhs_valuetype = rhs->valuetype(); + + return new DefineExprAccess(rhs_valuetype, + std::move(lhs_name), + std::move(rhs)); + } + + rp + DefineExprAccess::make_empty() + { + return new DefineExprAccess(nullptr /*rhs_valuetype*/, + "" /*lhs_name*/, + nullptr /*rhs*/); + } + + void + DefineExprAccess::assign_rhs(const rp & x) + { + assert(x); + + this->rhs_ = x; + + if (x) { + this->assign_valuetype(x->valuetype()); + } + + this->free_var_set_ = this->calc_free_variables(); + } + + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end DefineExpr.cpp */ diff --git a/xo-expression/src/expression/Expression.cpp b/xo-expression/src/expression/Expression.cpp new file mode 100644 index 00000000..e9f02932 --- /dev/null +++ b/xo-expression/src/expression/Expression.cpp @@ -0,0 +1,11 @@ +/* @file Expression.cpp */ + +#include "Expression.hpp" + +namespace xo { + namespace ast { + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Expression.cpp */ diff --git a/xo-expression/src/expression/GeneralizedExpression.cpp b/xo-expression/src/expression/GeneralizedExpression.cpp new file mode 100644 index 00000000..6239f700 --- /dev/null +++ b/xo-expression/src/expression/GeneralizedExpression.cpp @@ -0,0 +1,14 @@ +/* @file GeneralizedExpression.cpp */ + +#include "GeneralizedExpression.hpp" + +namespace xo { + namespace ast { + std::string + GeneralizedExpression::display_string() const { + return tostr(*this); + } + } /*namespace ast*/ +} /*namespace xo*/ + +/* end GeneralizedExpression.cpp */ diff --git a/xo-expression/src/expression/IfExpr.cpp b/xo-expression/src/expression/IfExpr.cpp new file mode 100644 index 00000000..bd5d231f --- /dev/null +++ b/xo-expression/src/expression/IfExpr.cpp @@ -0,0 +1,45 @@ +/* @file IfExpr.cpp */ + +#include "IfExpr.hpp" +#include "xo/indentlog/print/vector.hpp" + +namespace xo { + namespace ast { + rp + IfExpr::make(const rp & test, + const rp & when_true, + const rp & when_false) + { + /** TODO: verify test returns _boolean_ type **/ + + if (when_true->valuetype() != when_false->valuetype()) { + throw std::runtime_error + (tostr("IfExpr::make:" + " types {T1,T2} found for branches of if-expr" + " where equal types expected", + xtag("T1", when_true->valuetype()->canonical_name()), + xtag("T2", when_false->valuetype()->canonical_name()))); + } + + /* arbitrary choice here */ + auto ifexpr_type = when_true->valuetype(); + + return new IfExpr(ifexpr_type, + test, + when_true, + when_false); + } /*make*/ + + void + IfExpr::display(std::ostream & os) const { + os << ""; + } /*display*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end IfExpr.cpp */ diff --git a/xo-expression/src/expression/Lambda.cpp b/xo-expression/src/expression/Lambda.cpp new file mode 100644 index 00000000..5eae7973 --- /dev/null +++ b/xo-expression/src/expression/Lambda.cpp @@ -0,0 +1,361 @@ +/* @file Lambda.cpp */ + +#include "Lambda.hpp" +#include "xo/reflect/TypeDescr.hpp" +#include "xo/reflect/function/FunctionTdx.hpp" +#include "xo/indentlog/print/vector.hpp" +#include +#include + +namespace xo { + using xo::reflect::TypeDescr; + using xo::reflect::TypeDescrBase; + using xo::reflect::FunctionTdxInfo; + using std::stringstream; + + namespace ast { + TypeDescr + Lambda::assemble_lambda_td(const std::vector> & argv, + const rp & body) + { + if (!body) + return nullptr; + + /** assemble function type. + * + * NOTE: need this to be unique! + **/ + + std::vector arg_td_v; + { + arg_td_v.reserve(argv.size()); + + for (const auto & arg : argv) { + arg_td_v.push_back(arg->valuetype()); + } + } + + auto function_info + = FunctionTdxInfo(body->valuetype(), + arg_td_v, + false /*!is_noexcept*/); + + TypeDescr lambda_td + = TypeDescrBase::require_by_fn_info(function_info); + + return lambda_td; + } + + std::string + Lambda::assemble_type_str(TypeDescr lambda_td) { + assert(lambda_td); + + std::stringstream ss; + + ss << lambda_td->fn_retval()->short_name() + << "("; + + for (std::size_t i = 0, n = lambda_td->n_fn_arg(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << lambda_td->fn_arg(i)->short_name(); + } + ss << ")"; + + return ss.str(); + } + + rp + Lambda::make(const std::string & name, + const std::vector> & argv, + const rp & body) + { + using xo::reflect::FunctionTdx; + + rp env = LocalEnv::make(argv); + + TypeDescr lambda_td = assemble_lambda_td(argv, body); + + rp retval + = new Lambda(name, + lambda_td, + env, + body); + + /* need two-phase construction b/c pointer cycle */ + env->assign_origin(retval.get()); + + return retval; + } /*make*/ + + std::set + Lambda::calc_free_variables() const + { + std::set retval + = body_->get_free_variables(); + + /* but remove formals. */ + for (const auto & var : local_env_->argv()) + retval.erase(var->name()); + + return retval; + } /*calc_free_variables*/ + + std::map> + Lambda::regularize_layer_vars() + { + /* regularize local_env+body: make sure exactly one instance + * (i.e. with object identity) of a Variable appears + * within one layer of a lambda body. + * + * Here 'layer' means excluding appearance in any nested lambdas + * (i.e. whether or not such appearance would resolve to the same + * memory location). + * + * Motivation is to unify Variables that would use the same + * binding_path to resolve their runtime location. + */ + std::map> var_map; + + for (const auto & arg : local_env_->argv()) { + /* each arg name can appear at most once + * in a particular lambda's parameter list + */ + assert(var_map.find(arg->name()) == var_map.end()); + + var_map[arg->name()] = arg; + } + + this->body_ + = (body_->xform_layer + ([&var_map](ref::brw x) -> rp + { + if (x->extype() == exprtype::variable) { + ref::brw var = Variable::from(x); + + auto ix = var_map.find(var->name()); + if (ix == var_map.end()) { + /* add to var_map, copy to ensure Variable + * not shared with any other layer + */ + + var_map[var->name()] = Variable::copy(var); + + return var.get(); + } else { + /* substitute already-encountered var_map[] member */ + return ix->second.get(); + } + } else { + return x.get(); + } + })); + + return var_map; + } /*regularize_layer_vars*/ + + void + Lambda::complete_assembly_from_body() { + if (body_) { + TypeDescr lambda_td + = assemble_lambda_td(this->local_env_->argv(), body_); + + if (lambda_td) + this->type_str_ = assemble_type_str(lambda_td); + + this->layer_var_map_ = this->regularize_layer_vars(); + + this->free_var_set_ = this->calc_free_variables(); + + std::map> nested_lambda_map; + { + this->body_->visit_layer + ([&nested_lambda_map] + (ref::brw expr) + { + if (expr->extype() == exprtype::lambda) { + ref::brw lm = Lambda::from(expr); + + nested_lambda_map[lm->name()] = lm.get(); + } + }); + } + this->nested_lambda_map_ = std::move(nested_lambda_map); + + /* establish the set of captured local vars. + * These are any formal parameters that appear free in + * any layer of a nested lambda. + */ + std::set captured_var_set; + { + for (const auto & ix : nested_lambda_map_) { + std::set nested_free_var_set + = ix.second->get_free_variables(); + + for (const auto & jx : nested_free_var_set) { + /* check whether variable *jx is one of this lambda's + * formals + */ + auto bind = this->local_env_->lookup_local_binding(jx); + + if (bind.i_link_ == 0) { + /* yup, it's a formal parameter of this lambda */ + captured_var_set.insert(jx); + } + } + } + } + + this->captured_var_set_ = std::move(captured_var_set); + + /* in particular: + * - establish binding path (intrusively) for each variable + * assigns Variable::path_ + */ + this->body_->attach_envs(local_env_); + } + } + + Lambda::Lambda(const std::string & name, + TypeDescr lambda_td, + const rp & local_env, + const rp & body) + : FunctionInterface(exprtype::lambda, lambda_td), + name_{name}, + body_{body}, + local_env_{local_env} + { +#ifdef OBSOLETE + stringstream ss; + ss << "double"; + ss << "("; + for (std::size_t i = 0, n = this->n_arg(); i < n; ++i) { + if (i > 0) + ss << ","; + ss << "double"; + } + ss << ")"; +#endif + + if (lambda_td) + this->type_str_ = assemble_type_str(lambda_td); + + /* ensure variables are unique within layer for this lambda */ + this->layer_var_map_ = this->regularize_layer_vars(); + + this->free_var_set_ = this->calc_free_variables(); + + std::map> nested_lambda_map; + { + this->body_->visit_layer + ([&nested_lambda_map] + (ref::brw expr) + { + if (expr->extype() == exprtype::lambda) { + ref::brw lm = Lambda::from(expr); + + nested_lambda_map[lm->name()] = lm.get(); + } + }); + } + this->nested_lambda_map_ = std::move(nested_lambda_map); + + /* establish the set of captured local vars. + * These are any formal parameters that appear free in + * any layer of a nested lambda. + */ + std::set captured_var_set; + { + for (const auto & ix : nested_lambda_map_) { + std::set nested_free_var_set + = ix.second->get_free_variables(); + + for (const auto & jx : nested_free_var_set) { + /* check whether variable *jx is one of this lambda's + * formals + */ + auto bind = this->local_env_->lookup_local_binding(jx); + + if (bind.i_link_ == 0) { + /* yup, it's a formal parameter of this lambda */ + captured_var_set.insert(jx); + } + } + } + } + + this->captured_var_set_ = std::move(captured_var_set); + + /* in particular: + * - establish binding path (intrusively) for each variable + * assigns Variable::path_ + */ + this->body_->attach_envs(local_env_); + + } /*ctor*/ + + void + Lambda::attach_envs(ref::brw p) { + local_env_->assign_parent(p); + + /** establish a binding path for each variable **/ + } + + void + Lambda::display(std::ostream & os) const { + os << "argv()) + << xtag("body", body_) + << ">"; + } /*display*/ + + // ----- Lambda Access ----- + + rp + LambdaAccess::make(const std::string & name, + const std::vector> & argv, + const rp & body) + { + TypeDescr lambda_td = assemble_lambda_td(argv, body); + rp env = LocalEnv::make(argv); + + rp retval + = new LambdaAccess(name, + lambda_td, + env, + body); + + /* need two-phase construction b/c pointer cycle */ + env->assign_origin(retval.get()); + + return retval; + } + + rp + LambdaAccess::make_empty() + { + return new LambdaAccess("" /*name*/, + nullptr /*lambda_td*/, + nullptr /*local_env*/, + nullptr /*body*/); + } + + LambdaAccess::LambdaAccess(const std::string & name, + TypeDescr lambda_td, + const rp & local_env, + const rp & body) + : Lambda(name, lambda_td, local_env, body) + {} + + void + LambdaAccess::assign_body(const rp & body) { + this->body_ = body; + + this->complete_assembly_from_body(); + } + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Lambda.cpp */ diff --git a/xo-expression/src/expression/LocalEnv.cpp b/xo-expression/src/expression/LocalEnv.cpp new file mode 100644 index 00000000..8b834278 --- /dev/null +++ b/xo-expression/src/expression/LocalEnv.cpp @@ -0,0 +1,44 @@ +/* file LocalEnv.cpp + * + * author: Roland Conybeare + */ + +#include "LocalEnv.hpp" + +namespace xo { + namespace ast { + binding_path + LocalEnv::lookup_local_binding(const std::string & vname) const + { + int j_slot = 0; + for (const auto & arg : argv_) { + if (arg->name() == vname) + return { 0 /*i_link*/, j_slot }; + ++j_slot; + } + + return { -2 /*i_link: sentinel*/, 0 }; + } /*lookup_local_binding*/ + + binding_path + LocalEnv::lookup_binding(const std::string & vname) const + { + { + auto local = this->lookup_local_binding(vname); + + if (local.i_link_ == 0) + return local; + } + + auto free = parent_env_->lookup_binding(vname); + + if (free.i_link_ == -1) + return free; + else + return { free.i_link_ + 1, free.j_slot_ }; + } /*lookup_binding*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end LocalEnv.cpp */ diff --git a/xo-expression/src/expression/Primitive.cpp b/xo-expression/src/expression/Primitive.cpp new file mode 100644 index 00000000..350edae8 --- /dev/null +++ b/xo-expression/src/expression/Primitive.cpp @@ -0,0 +1,89 @@ +/* @file Primitive.cpp */ + +#include "Primitive.hpp" + +extern "C" { + double + add2_f64(double x, double y) { + return x + y; + } + + double + sub2_f64(double x, double y) { + return x - y; + } + + double + mul2_f64(double x, double y) { + return x * y; + } + + double + div2_f64(double x, double y) { + return x / y; + } +} + +namespace xo { + namespace ast { + auto + Primitive_f64::make_add2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("add2_f64", + &add2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_add); + + return s_retval; + } + + auto + Primitive_f64::make_sub2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("sub2_f64", + &sub2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_sub); + + return s_retval; + } + + auto + Primitive_f64::make_mul2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("mul2_f64", + &mul2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_mul); + + return s_retval; + } + + auto + Primitive_f64::make_div2_f64() -> rp + { + static rp s_retval; + + if (!s_retval) + s_retval = Primitive::make("div2_f64", + &div2_f64, + true /*explicit_symbol_def*/, + llvmintrinsic::fp_div); + + return s_retval; + } + + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end Primitive.cpp */ diff --git a/xo-expression/src/expression/Sequence.cpp b/xo-expression/src/expression/Sequence.cpp new file mode 100644 index 00000000..98269e2c --- /dev/null +++ b/xo-expression/src/expression/Sequence.cpp @@ -0,0 +1,78 @@ +/* @file Sequence.cpp */ + +#include "Sequence.hpp" +#include + +namespace xo { + namespace ast { + std::set + Sequence::get_free_variables() const { + std::set retval; + + for (const auto & x : expr_v_) { + std::set free_vars; + free_vars = x->get_free_variables(); + + for (const auto & y : free_vars) + retval.insert(y); + } + + return retval; + } + + std::size_t + Sequence::visit_preorder(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & x : expr_v_) + n += x->visit_preorder(visitor_fn); + + return n; + } + + std::size_t + Sequence::visit_layer(VisitFn visitor_fn) { + std::size_t n = 1; + + visitor_fn(this); + + for (const auto & x : expr_v_) + n += x->visit_layer(visitor_fn); + + return n; + } + + rp + Sequence::xform_layer(TransformFn xform_fn) { + for (std::size_t i = 0, n = expr_v_.size(); i < n; ++i) { + expr_v_[i] = expr_v_[i]->xform_layer(xform_fn); + } + + return xform_fn(this); + } + + void + Sequence::attach_envs(ref::brw p) { + for (const auto & x : expr_v_) + x->attach_envs(p); + } + + void + Sequence::display(std::ostream & os) const { + os << ""; + } + } /*namespace scm*/ +} /*namespace xo*/ + + +/* end Sequence.cpp */ diff --git a/xo-expression/src/expression/Variable.cpp b/xo-expression/src/expression/Variable.cpp new file mode 100644 index 00000000..18434afb --- /dev/null +++ b/xo-expression/src/expression/Variable.cpp @@ -0,0 +1,32 @@ +/* @file Variable.cpp */ + +#include "Variable.hpp" +#include "Environment.hpp" + +namespace xo { + namespace ast { + void + Variable::attach_envs(ref::brw e) { + /** e makes accessible all enclosing lexical scopes **/ + if (this->path_.i_link_ == -2 /*sentinel*/) { + this->path_ = e->lookup_binding(this->name_); + } else { + /* have already established binding for this Variable */ + } + } /*attach_envs*/ + + void + Variable::display(std::ostream & os) const { + os << "valuetype()) + os << xtag("type", this->valuetype()->short_name()); + else + os << xtag("type", "nullptr"); + os << ">"; + } /*display*/ + } /*namespace ast*/ +} /*namespace xo*/ + + +/* end Variable.cpp */ diff --git a/xo-expression/src/expression/intrinsics.cpp b/xo-expression/src/expression/intrinsics.cpp new file mode 100644 index 00000000..27dcd83a --- /dev/null +++ b/xo-expression/src/expression/intrinsics.cpp @@ -0,0 +1,14 @@ +/* @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; +} + +/* end intrinsics.cpp */