Add 'xo-expression/' from commit '5ac3c03a0c'

git-subtree-dir: xo-expression
git-subtree-mainline: d0f5ccc1ce
git-subtree-split: 5ac3c03a0c
This commit is contained in:
Roland Conybeare 2025-05-11 01:22:16 -05:00
commit aecabbb144
46 changed files with 3241 additions and 0 deletions

8
xo-expression/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
# emacs projectile config
.projectile
# clangd working space (see emacs+lsp)
.cache
# typical cmake build directory (source-tree-nephew)
.build*
# symlink to builddir/compile_commands.json; should be set manually in dev sandbox
compile_commands.json

View file

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

9
xo-expression/LESSONS Normal file
View file

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

29
xo-expression/LICENSE Normal file
View file

@ -0,0 +1,29 @@
Copyright (c) 2024 Roland Conybeare <git3ub@nym.hush.com>, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of
external contributions to this project including patches, pull requests, etc.

63
xo-expression/README.md Normal file
View file

@ -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
```

3
xo-expression/TODO Normal file
View file

@ -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?

View file

@ -0,0 +1,35 @@
# ----------------------------------------------------------------
# for example:
# $ PREFIX=/usr/local # for example
# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build
#
# will get
# CMAKE_MODULE_PATH
# from xo-cmake-config --cmake-module-path
#
# and expect .cmake macros in
# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake
# ----------------------------------------------------------------
find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED)
if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND")
message(FATAL "could not find xo-cmake-config executable")
endif()
message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}")
if (NOT XO_SUBMODULE_BUILD)
if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix))
# default to typical install location for xo-project-macros
execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH)
message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}")
endif()
endif()
# needs to have been installed somewhere on CMAKE_MODULE_PATH,
# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX)
#
include(xo_macros/xo_cxx)
xo_cxx_bootstrap_message()

View file

@ -0,0 +1,6 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
find_dependency(reflect)
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
check_required_components("@PROJECT_NAME@")

View file

@ -0,0 +1 @@
add_subdirectory(ex1)

View file

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

View file

@ -0,0 +1,39 @@
/** @file ex1.cpp **/
#include "xo/expression/Constant.hpp"
#include "xo/expression/Primitive.hpp"
#include "xo/expression/llvmintrinsic.hpp"
#include <iostream>
#include <cmath>
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 **/

View file

@ -0,0 +1,163 @@
/** @file Apply.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
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<Apply> make(const rp<Expression> & fn,
const std::vector<rp<Expression>> & argv);
/** create apply-expression to add two 64-bit floating-point numbers **/
static rp<Apply> make_add2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to subtract two 64-bit floating-point numbers **/
static rp<Apply> make_sub2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to multiply two 64-bit floating-point numbers **/
static rp<Apply> make_mul2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** create apply-expression to divide two 64-bit floating-point numbers **/
static rp<Apply> make_div2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs);
/** downcast from Expression **/
static ref::brw<Apply> from(ref::brw<Expression> x) {
return ref::brw<Apply>::from(x);
}
const rp<Expression> & fn() const { return fn_; }
const std::vector<rp<Expression>> & argv() const { return argv_; }
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> retval = fn_->get_free_variables();
for (const auto & arg : argv_) {
std::set<std::string> 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<Expression> 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<Environment> 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<Expression> & fn,
const std::vector<rp<Expression>> & argv)
: Expression(exprtype::apply, apply_valuetype),
fn_{fn}, argv_(argv)
{}
private:
/** function to invoke **/
rp<Expression> fn_;
/** argument expressions, in l-to-r order **/
std::vector<rp<Expression>> argv_;
}; /*Apply*/
#ifdef NOT_USING
namespace detail {
/** Use:
** std::vector<ref::rp<Expression>>
**/
template <typename... Args>
struct apply_push_args;
template <>
struct apply_push_args<> {
static void push_all(std::vector<ref::rp<Expression>> * /*p_argv*/) {}
};
template <typename Arg1, typename... Rest>
struct apply_push_args<Arg1, Rest...> {
static void push_all(std::vector<ref::rp<Expression>> * p_argv,
const ref::rp<Expression> & x, Rest... rest)
{
p_argv->push_back(x);
apply_push_args<Rest...>::push_all(p_argv, rest...);
};
};
}
#endif
/* reminder: initializer-lists are compile-time only */
inline rp<Apply>
make_apply(const rp<Expression> & fn,
const std::initializer_list<rp<Expression>> args) {
std::vector<rp<Expression>> argv(args);
return Apply::make(fn, argv);
} /*make_apply*/
} /*namespace ast*/
} /*namespace xo*/
/** end Apply.hpp **/

View file

@ -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<AssignExpr> make(const rp<Variable> & lhs,
const rp<Expression> & rhs);
static ref::brw<AssignExpr> from(ref::brw<Expression> x) {
return ref::brw<AssignExpr>::from(x);
}
const rp<Variable> & lhs() const { return lhs_; }
const rp<Expression> & rhs() const { return rhs_; }
std::set<std::string> calc_free_variables() const;
// ----- inherited from Expression -----
virtual std::set<std::string> 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<Expression> xform_layer(TransformFn xform_fn) override;
virtual void attach_envs(ref::brw<Environment> p) override;
virtual void display(std::ostream & os) const override;
private:
AssignExpr(const rp<Variable> & lhs,
const rp<Expression> & rhs);
private:
/** assign to this variable. **/
rp<Variable> lhs_;
/** assign value of this expression to variable @p lhs **/
rp<Expression> rhs_;
/** free variables for this assignment **/
std::set<std::string> free_var_set_;
};
} /*namespace ast*/
} /*namespace xo*/
/* end AssignExpr.hpp */

View file

@ -0,0 +1,103 @@
/** @file Constant.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "ConstantInterface.hpp"
#include <type_traits>
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 <typename T>
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<Constant> make(const T & x) {
TypeDescr x_valuetype = Reflect::require<T>();
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<void*>(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<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void display(std::ostream & os) const override {
os << "<Constant";
if (value_td_)
os << xtag("type", value_td_->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<T>()},
value_(x)
{
static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>);
}
private:
/** type description for T **/
TypeDescr value_td_;
/** value of this constant **/
T value_;
}; /*Constant*/
template <typename T>
rp<Constant<std::remove_reference_t<T>>>
make_constant(const T & x) {
return Constant<T>::make(x);
}
} /*namespace ast*/
} /*namespace xo*/
/** end Constant.hpp **/

View file

@ -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 <type_traits>
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<ConstantInterface> from(ref::brw<Expression> x) {
return ref::brw<ConstantInterface>::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<std::string> get_free_variables() const override {
return std::set<std::string>();
}
virtual void attach_envs(ref::brw<Environment> /*p*/) override {}
}; /*ConstantInterface*/
} /*namespace ast*/
} /*namespace xo*/
/** end ConstantInterface.hpp **/

View file

@ -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<ConvertExpr> make(TypeDescr dest_type,
rp<Expression> arg);
static ref::brw<ConvertExpr> from(ref::brw<Expression> x) {
return ref::brw<ConvertExpr>::from(x);
}
const rp<Expression> & arg() const { return arg_; }
// ----- Expression -----
virtual std::set<std::string> 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<Expression> xform_layer(TransformFn xform_fn) override {
this->arg_ = this->arg_->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(ref::brw<Environment> p) override {
arg_->attach_envs(p);
}
virtual void display(std::ostream & os) const override;
protected:
ConvertExpr(TypeDescr dest_type,
rp<Expression> arg)
: Expression(exprtype::convert, dest_type),
arg_{std::move(arg)}
{}
protected:
/** source expression. Convert
* @c arg_->valuetype() to @c dest_type_
**/
rp<Expression> 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<ConvertExprAccess> make(TypeDescr dest_type,
rp<Expression> arg);
static rp<ConvertExprAccess> make_empty();
void assign_arg(rp<Expression> arg) { this->arg_ = std::move(arg); }
private:
ConvertExprAccess(TypeDescr dest_type,
rp<Expression> arg)
: ConvertExpr(dest_type,
std::move(arg))
{}
};
} /*namespace ast*/
} /*namespace xo*/
/* end ConvertExpr.hpp */

View file

@ -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<DefineExpr> make(std::string name,
rp<Expression> value);
static ref::brw<DefineExpr> from(ref::brw<Expression> x) {
return ref::brw<DefineExpr>::from(x);
}
const std::string & lhs_name() const { return lhs_name_; }
const rp<Expression> & rhs() const { return rhs_; }
std::set<std::string> calc_free_variables() const;
// ----- Expression -----
virtual std::set<std::string> 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<Expression> xform_layer(TransformFn xform_fn) override {
this->rhs_ = this->rhs_->xform_layer(xform_fn);
return xform_fn(this);
}
virtual void attach_envs(ref::brw<Environment> p) override {
rhs_->attach_envs(p);
}
virtual void display(std::ostream & os) const override;
protected:
/**
*
**/
DefineExpr(TypeDescr rhs_valuetype,
std::string lhs_name,
rp<Expression> rhs);
protected:
/** symbol name for this definition **/
std::string lhs_name_;
/** right-hand side of definition **/
rp<Expression> rhs_;
/** free variables for this definition **/
std::set<std::string> 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<DefineExprAccess> make(std::string lhs_name,
rp<Expression> rhs);
static rp<DefineExprAccess> make_empty();
void assign_lhs_name(const std::string & x) {
this->lhs_name_ = x;
}
void assign_rhs(const rp<Expression> & x);
private:
DefineExprAccess(TypeDescr rhs_valuetype,
std::string lhs_name,
rp<Expression> rhs)
: DefineExpr(rhs_valuetype,
std::move(lhs_name),
std::move(rhs))
{}
};
} /*namespace ast*/
} /*namespace xo*/
/* end DefineExpr.hpp */

View file

@ -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<Expression> lookup_var(const std::string & vname) const = 0;
};
} /*namespace ast*/
} /*namespace xo*/
/* end Environment.hpp */

View file

@ -0,0 +1,90 @@
/** @file Expression.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "GeneralizedExpression.hpp"
#include <functional>
#include <set>
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
<void (ref::brw<Expression>)>;
using TransformFn = std::function
<rp<Expression> (ref::brw<Expression>)>;
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<std::string> 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<Expression> 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<Environment> 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<ref::brw<Lambda>> env) = 0;
}; /*Expression*/
} /*namespace ast*/
} /*namespace xo*/
/** end Expression.hpp **/

View file

@ -0,0 +1,34 @@
/** @file FunctionInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
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<FunctionInterface> from(ref::brw<Expression> x) {
return ref::brw<FunctionInterface>::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 **/

View file

@ -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 <cstdint>
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 **/

View file

@ -0,0 +1,60 @@
/* file GlobalEnv.hpp
*
* author: Roland Conybeare, Jun 2024
*/
#pragma once
#include "Environment.hpp"
#include <map>
#include <string>
namespace xo {
namespace ast {
class GlobalEnv : public Environment {
public:
/** create instance. Probably only need one of these **/
static rp<GlobalEnv> make() { return new GlobalEnv(); }
ref::brw<Expression> require_global(const std::string & vname,
ref::brw<Expression> 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<Expression> lookup_var(const std::string & vname) const override {
auto ix = global_map_.find(vname);
if (ix == global_map_.end()) {
/* not found */
return ref::brw<Expression>::from_native(nullptr);
}
return ix->second;
}
private:
GlobalEnv() = default;
private:
/* for assignable globals, need to allocate memory
* addresses for these.
*/
std::map<std::string, rp<Expression>> global_map_;
};
} /*namespace ast*/
} /*namespace xo*/
/* end GlobalEnv.hpp */

View file

@ -0,0 +1,145 @@
/** @file IfExpr.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
#include <vector>
#include <string>
//#include <cstdint>
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<IfExpr> make(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & when_false);
/** downcast from Expression **/
static ref::brw<IfExpr> from(ref::brw<Expression> x) {
return ref::brw<IfExpr>::from(x);
}
const rp<Expression> & test() const { return test_; }
const rp<Expression> & when_true() const { return when_true_; }
const rp<Expression> & when_false() const { return when_false_; }
// ----- Expression -----
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> retval = test_->get_free_variables();
std::set<std::string> 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<Expression> 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<Environment> 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<ref::brw<Variable>> * 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<Expression> test,
rp<Expression> when_true,
rp<Expression> 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<Expression> test_;
rp<Expression> when_true_;
rp<Expression> when_false_;
}; /*IfExpr*/
inline rp<IfExpr>
make_ifexpr(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & when_false)
{
return IfExpr::make(test, when_true, when_false);
}
} /*namespace ast*/
} /*namespace xo*/
/** end IfExpr.hpp **/

View file

@ -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 <map>
#include <vector>
#include <string>
//#include <cstdint>
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<Lambda> make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body);
/** downcast from Expression **/
static ref::brw<Lambda> from(ref::brw<Expression> x) {
return ref::brw<Lambda>::from(x);
}
const std::string & type_str() const { return type_str_; }
const std::vector<rp<Variable>> & argv() const { return local_env_->argv(); }
const rp<Expression> & 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<std::string> 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<Expression> xform_layer(TransformFn /*xform_fn*/) override {
/* a layer is bounded by lambdas, don't enter them */
return this;
}
virtual void attach_envs(ref::brw<Environment> 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<rp<Variable>> & argv,
const rp<Expression> & 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<LocalEnv> & local_env,
const rp<Expression> & body);
/** compute free-variable set for this lambda **/
std::set<std::string> 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<std::string, rp<Variable>> 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<Expression> body_;
/** free variables for this lambda **/
std::set<std::string> free_var_set_;
/** variables that appear free in some nested lambda **/
std::set<std::string> 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<std::string, rp<Variable>> layer_var_map_;
/** all lambdas nested once inside this lambda's body **/
std::map<std::string, ref::brw<Lambda>> 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<LocalEnv> local_env_;
}; /*Lambda*/
inline rp<Lambda>
make_lambda(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
return Lambda::make(name, argv, body);
}
class LambdaAccess : public Lambda {
public:
static rp<LambdaAccess> make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body);
static rp<LambdaAccess> make_empty();
/** assign body + compute derived members
* (see complete_assembly_from_body())
**/
void assign_body(const rp<Expression> & 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<LocalEnv> & local_env,
const rp<Expression> & body);
};
} /*namespace ast*/
} /*namespace xo*/
/** end Lambda.hpp **/

View file

@ -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<LocalEnv> make(const std::vector<rp<Variable>> & argv) {
return new LocalEnv(argv);
}
Lambda * origin() const { return origin_; }
const std::vector<rp<Variable>> & argv() const { return argv_; }
const rp<Variable>& 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<Environment> 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<Expression> 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<rp<Variable>> & 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<rp<Variable>> argv_;
/** parent environment. A free variable in this lambda's
* body will be resolved by referring them to @ref parent_env_.
**/
rp<Environment> parent_env_;
};
} /*namespace ast*/
} /*namespace xo*/
/* end LocalEnv.hpp */

View file

@ -0,0 +1,164 @@
/** @file Primitive.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "PrimitiveInterface.hpp"
#include "llvmintrinsic.hpp"
#include "xo/reflect/Reflect.hpp"
//#include <cstdint>
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<double(double)>
* won't work here.
**/
template <typename FunctionPointer>
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<Primitive> make(const std::string & name,
FunctionPointer fnptr,
bool explicit_symbol_def,
llvmintrinsic intrinsic) {
TypeDescr fn_type = Reflect::require<FunctionPointer>();
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<void*>(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<void_function_type>(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 << "<Primitive"
<< xtag("name", name_)
<< xtag("type", this->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<FunctionPointer>()},
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 <typename FunctionPointer>
rp<Primitive<FunctionPointer>>
make_primitive(const std::string & name,
FunctionPointer x,
bool explicit_symbol_def,
llvmintrinsic intrinsic)
{
return Primitive<FunctionPointer>::make(name, x, explicit_symbol_def, intrinsic);
}
class Primitive_f64 : public Primitive<double (*)(double, double)> {
public:
using PrimitiveType = Primitive<double (*)(double, double)>;
public:
/** add2_f64: add two 64-bit floating-point numbers **/
static rp<PrimitiveType> make_add2_f64();
/** sub2_f64: subtract two 64-bit floating-point numbers **/
static rp<PrimitiveType> make_sub2_f64();
/** mul2_f64: multiply two 64-bit floating-point numbers **/
static rp<PrimitiveType> make_mul2_f64();
/** div2_f64: divide two 64-bit floating-point numbers **/
static rp<PrimitiveType> make_div2_f64();
};
} /*namespace ast*/
} /*namespace xo*/
/** end Primitive.hpp **/

View file

@ -0,0 +1,77 @@
/** @file PrimitiveInterface.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "FunctionInterface.hpp"
#include "llvmintrinsic.hpp"
//#include <cstdint>
#include <type_traits>
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<PrimitiveInterface> from(ref::brw<Expression> x) {
return ref::brw<PrimitiveInterface>::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<std::string> get_free_variables() const override {
return std::set<std::string>();
}
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<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void attach_envs(ref::brw<Environment> /*p*/) override {}
private:
}; /*PrimitiveInterface*/
} /*namespace ast*/
} /*namespace xo*/
/** end PrimitiveInterface.hpp **/

View file

@ -0,0 +1,52 @@
/** @file Sequence.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include "Expression.hpp"
//#include <cstdint>
namespace xo {
namespace ast {
class Sequence : public Expression {
public:
Sequence(const std::vector<rp<Expression>> & xv)
: Expression(exprtype::sequence,
xv[xv.size() - 1]->valuetype()),
expr_v_(xv) {}
static rp<Sequence> make(const std::vector<rp<Expression>> & xv) { return new Sequence(xv); }
std::size_t size() const { return expr_v_.size(); }
const rp<Expression> & 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<std::string> 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<Expression> xform_layer(TransformFn visitor_fn) override;
virtual void attach_envs(ref::brw<Environment> parent) override;
// ----- from GeneralizedExpression ----
virtual void display(std::ostream & os) const override;
private:
/** sequence of expressions; evaluate in left-to-right order.
**/
std::vector<rp<Expression>> expr_v_;
};
} /*namespace ast*/
} /*namespace xo*/
/** end Sequence.hpp **/

View file

@ -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<Variable> 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<Variable> copy(ref::brw<Variable> x) {
return new Variable(x->name(), x->valuetype());
}
/** downcast from Expression **/
static ref::brw<Variable> from(ref::brw<Expression> x) {
return ref::brw<Variable>::from(x);
}
const std::string & name() const { return name_; }
virtual std::set<std::string> get_free_variables() const override {
std::set<std::string> 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<Expression> xform_layer(TransformFn xform_fn) override {
return xform_fn(this);
}
virtual void attach_envs(ref::brw<Environment> /*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<Variable>
make_var(const std::string & name,
reflect::TypeDescr var_type) {
return Variable::make(name, var_type);
}
} /*namespace ast*/
} /*namespace xo*/
/** end Variable.hpp **/

View file

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

View file

@ -0,0 +1,81 @@
/** @file exprtype.hpp
*
* Author: Roland Conybeare
**/
#pragma once
#include <ostream>
//#include <cstdint>
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<std::size_t>(exprtype::n_expr);
inline std::ostream &
operator<<(std::ostream & os,
exprtype x)
{
os << expr2str(x);
return os;
}
} /*namespace ast*/
} /*namespace xo*/
/** end exprtype.hpp **/

View file

@ -0,0 +1,127 @@
/** @file llvmintrinsic.hpp
*
* Author: Roland Conybeare
**/
#pragma once
//#include <cstdint>
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 **/

View file

@ -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>
Apply::make(const rp<Expression> & fn,
const std::vector<rp<Expression>> & 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>
Apply::make_add2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_add2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_sub2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_sub2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_mul2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_mul2_f64(),
{lhs, rhs});
}
rp<Apply>
Apply::make_div2_f64(const rp<Expression> & lhs,
const rp<Expression> & rhs)
{
return Apply::make(Primitive_f64::make_div2_f64(),
{lhs, rhs});
}
void
Apply::display(std::ostream & os) const {
os << "<Apply"
<< xtag("fn", fn_)
<< xtag("argv", argv_)
<< ">";
}
} /*namespace ast*/
} /*namespace xo*/
/* end Apply.cpp */

View file

@ -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>
AssignExpr::make(const rp<Variable> & lhs,
const rp<Expression> & rhs)
{
return new AssignExpr(lhs, rhs);
}
AssignExpr::AssignExpr(const rp<Variable> & lhs,
const rp<Expression> & rhs)
: Expression(exprtype::assign, rhs->valuetype()),
lhs_{lhs}, rhs_{rhs}
{
this->free_var_set_ = this->calc_free_variables();
}
std::set<std::string>
AssignExpr::calc_free_variables() const
{
std::set<std::string> retval = lhs_->get_free_variables();
std::set<std::string> tmp = rhs_->get_free_variables();
for (const auto & name : tmp)
retval.insert(name);
return retval;
}
std::set<std::string>
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<Expression>
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<Environment> p) {
lhs_->attach_envs(p);
rhs_->attach_envs(p);
}
void
AssignExpr::display(std::ostream & os) const {
os << "<Assign"
<< xtag("lhs", lhs_)
<< xtag("rhs", rhs_)
<< ">";
}
} /*namespace ast*/
} /*namespace xo*/
/* end AssignExpr.cpp */

View file

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

View file

@ -0,0 +1,53 @@
/* file ConvertExpr.cpp
*
* author: Roland Conybeare
*/
#include "ConvertExpr.hpp"
namespace xo {
namespace ast {
rp<ConvertExpr>
ConvertExpr::make(TypeDescr dest_type,
rp<Expression> arg)
{
return new ConvertExpr(dest_type,
std::move(arg));
}
std::set<std::string>
ConvertExpr::get_free_variables() const {
if (this->arg_)
return this->arg_->get_free_variables();
else
return std::set<std::string>();
}
void
ConvertExpr::display(std::ostream & os) const {
os << "<Convert"
<< xtag("dest_type", this->valuetype()->short_name())
<< xtag("arg", arg_)
<< ">";
}
// ----- ConvertExprAccess -----
rp<ConvertExprAccess>
ConvertExprAccess::make(TypeDescr dest_type,
rp<Expression> arg)
{
return new ConvertExprAccess(dest_type,
std::move(arg));
}
rp<ConvertExprAccess>
ConvertExprAccess::make_empty() {
return new ConvertExprAccess(nullptr /*dest_type*/,
nullptr /*arg*/);
}
} /*namespace ast*/
} /*namespace xo*/
/* end ConvertExpr.cpp */

View file

@ -0,0 +1,99 @@
/* file DefineExpr.cpp
*
* author: Roland Conybeare
*/
#include "DefineExpr.hpp"
namespace xo {
namespace ast {
rp<DefineExpr>
DefineExpr::make(std::string lhs_name,
rp<Expression> 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<Expression> 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<std::string>
DefineExpr::calc_free_variables() const
{
std::set<std::string> 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 << "<Define"
<< xtag("name", lhs_name_)
<< xtag("rhs", rhs_)
<< ">";
} /*display*/
// ----- DefineExprAccess -----
rp<DefineExprAccess>
DefineExprAccess::make(std::string lhs_name,
rp<Expression> 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>
DefineExprAccess::make_empty()
{
return new DefineExprAccess(nullptr /*rhs_valuetype*/,
"" /*lhs_name*/,
nullptr /*rhs*/);
}
void
DefineExprAccess::assign_rhs(const rp<Expression> & 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 */

View file

@ -0,0 +1,11 @@
/* @file Expression.cpp */
#include "Expression.hpp"
namespace xo {
namespace ast {
} /*namespace ast*/
} /*namespace xo*/
/* end Expression.cpp */

View file

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

View file

@ -0,0 +1,45 @@
/* @file IfExpr.cpp */
#include "IfExpr.hpp"
#include "xo/indentlog/print/vector.hpp"
namespace xo {
namespace ast {
rp<IfExpr>
IfExpr::make(const rp<Expression> & test,
const rp<Expression> & when_true,
const rp<Expression> & 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 << "<IfExpr"
<< xtag("test", test_)
<< xtag("when_true", when_true_)
<< xtag("when_false", when_false_)
<< ">";
} /*display*/
} /*namespace ast*/
} /*namespace xo*/
/* end IfExpr.cpp */

View file

@ -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 <map>
#include <sstream>
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<rp<Variable>> & argv,
const rp<Expression> & body)
{
if (!body)
return nullptr;
/** assemble function type.
*
* NOTE: need this to be unique!
**/
std::vector<TypeDescr> 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>
Lambda::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
using xo::reflect::FunctionTdx;
rp<LocalEnv> env = LocalEnv::make(argv);
TypeDescr lambda_td = assemble_lambda_td(argv, body);
rp<Lambda> 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<std::string>
Lambda::calc_free_variables() const
{
std::set<std::string> 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<std::string, rp<Variable>>
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<std::string, rp<Variable>> 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<Expression> x) -> rp<Expression>
{
if (x->extype() == exprtype::variable) {
ref::brw<Variable> 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<std::string, ref::brw<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(ref::brw<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
ref::brw<Lambda> 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<std::string> captured_var_set;
{
for (const auto & ix : nested_lambda_map_) {
std::set<std::string> 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<LocalEnv> & local_env,
const rp<Expression> & 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<std::string, ref::brw<Lambda>> nested_lambda_map;
{
this->body_->visit_layer
([&nested_lambda_map]
(ref::brw<Expression> expr)
{
if (expr->extype() == exprtype::lambda) {
ref::brw<Lambda> 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<std::string> captured_var_set;
{
for (const auto & ix : nested_lambda_map_) {
std::set<std::string> 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<Environment> p) {
local_env_->assign_parent(p);
/** establish a binding path for each variable **/
}
void
Lambda::display(std::ostream & os) const {
os << "<Lambda"
<< xtag("name", name_)
<< xtag("argv", local_env_->argv())
<< xtag("body", body_)
<< ">";
} /*display*/
// ----- Lambda Access -----
rp<LambdaAccess>
LambdaAccess::make(const std::string & name,
const std::vector<rp<Variable>> & argv,
const rp<Expression> & body)
{
TypeDescr lambda_td = assemble_lambda_td(argv, body);
rp<LocalEnv> env = LocalEnv::make(argv);
rp<LambdaAccess> 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>
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<LocalEnv> & local_env,
const rp<Expression> & body)
: Lambda(name, lambda_td, local_env, body)
{}
void
LambdaAccess::assign_body(const rp<Expression> & body) {
this->body_ = body;
this->complete_assembly_from_body();
}
} /*namespace ast*/
} /*namespace xo*/
/* end Lambda.cpp */

View file

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

View file

@ -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<PrimitiveType>
{
static rp<PrimitiveType> 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<PrimitiveType>
{
static rp<PrimitiveType> 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<PrimitiveType>
{
static rp<PrimitiveType> 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<PrimitiveType>
{
static rp<PrimitiveType> 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 */

View file

@ -0,0 +1,78 @@
/* @file Sequence.cpp */
#include "Sequence.hpp"
#include <cstddef>
namespace xo {
namespace ast {
std::set<std::string>
Sequence::get_free_variables() const {
std::set<std::string> retval;
for (const auto & x : expr_v_) {
std::set<std::string> 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<Expression>
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<Environment> p) {
for (const auto & x : expr_v_)
x->attach_envs(p);
}
void
Sequence::display(std::ostream & os) const {
os << "<Sequence";
std::size_t i = 0;
for (const auto & x : expr_v_) {
std::string i_str = tostr("[", i, "]");
os << xtag(i_str.c_str(), x);
}
os << ">";
}
} /*namespace scm*/
} /*namespace xo*/
/* end Sequence.cpp */

View file

@ -0,0 +1,32 @@
/* @file Variable.cpp */
#include "Variable.hpp"
#include "Environment.hpp"
namespace xo {
namespace ast {
void
Variable::attach_envs(ref::brw<Environment> 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 << "<Variable"
<< xtag("name", name_);
if (this->valuetype())
os << xtag("type", this->valuetype()->short_name());
else
os << xtag("type", "nullptr");
os << ">";
} /*display*/
} /*namespace ast*/
} /*namespace xo*/
/* end Variable.cpp */

View file

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