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

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

View file

@ -0,0 +1,50 @@
# jit/CMakeLists.txt
set(SELF_LIB xo_jit)
set(SELF_SRCS
LlvmContext.cpp
IrPipeline.cpp
MachPipeline.cpp
intrinsics.cpp
activation_record.cpp
type2llvm.cpp
)
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
xo_dependency(${SELF_LIB} xo_expression)
find_package(LLVM REQUIRED CONFIG)
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
message(STATUS "LLVM_DIR=${LLVM_DIR}")
message(STATUS "LLVM_DEFINITIONS=${LLVM_DEFINITIONS}")
message(STATUS "LLVM_INCLUDE_DIRS=[${LLVM_INCLUDE_DIRS}]")
separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS})
message(STATUS "LLVM_DEFINITIONS_LIST=[${LLVM_DEFINITIONS_LIST}]")
# LLVM library directory
execute_process(
COMMAND llvm-config --libdir
COMMAND tr -d '\n'
OUTPUT_VARIABLE LLVM_LIBRARY_DIR
)
message(STATUS "LLVM_LIBRARY_DIR=[${LLVM_LIBRARY_DIR}]")
# Find the libraries that correspond to the LLVM components
execute_process(
COMMAND llvm-config --libs all
COMMAND tr -d '\n'
OUTPUT_VARIABLE LLVM_LIBS
)
message(STATUS "LLVM_LIBS=[${LLVM_LIBS}]")
target_include_directories(${SELF_LIB} PUBLIC ${LLVM_INCLUDE_DIRS})
target_compile_definitions(${SELF_LIB} PUBLIC ${LLVM_DEFINITIONS_LIST})
target_link_directories(${SELF_LIB} PUBLIC ${LLVM_LIBRARY_DIR})
target_link_libraries(${SELF_LIB} PUBLIC ${LLVM_LIBS})
# end CMakeLists.txt

View file

@ -0,0 +1,55 @@
/* @file IrPipeline.cpp */
#include "IrPipeline.hpp"
namespace xo {
namespace jit {
IrPipeline::IrPipeline(rp<LlvmContext> llvm_cx)
{
using std::make_unique;
this->llvm_cx_ = std::move(llvm_cx);
this->llvm_fpmgr_ = make_unique<llvm::FunctionPassManager>();
this->llvm_lamgr_ = std::make_unique<llvm::LoopAnalysisManager>();
this->llvm_famgr_ = std::make_unique<llvm::FunctionAnalysisManager>();
this->llvm_cgamgr_ = std::make_unique<llvm::CGSCCAnalysisManager>();
this->llvm_mamgr_ = std::make_unique<llvm::ModuleAnalysisManager>();
this->llvm_pic_ = std::make_unique<llvm::PassInstrumentationCallbacks>();
/* reference kept alive by @ref llvm_cx_ */
this->llvm_si_ = std::make_unique<llvm::StandardInstrumentations>(llvm_cx_->llvm_cx_ref(),
/*DebugLogging*/ true);
this->llvm_si_->registerCallbacks(*llvm_pic_, llvm_mamgr_.get());
/** transform passes **/
this->llvm_fpmgr_->addPass(llvm::InstCombinePass());
/* NOTE: llvm 19 adds mem2reg transform here.
* speculating that PromotePass() does same/goodenough thing in llvm 18.
* This pays off, works first try!
*/
this->llvm_fpmgr_->addPass(llvm::PromotePass());
this->llvm_fpmgr_->addPass(llvm::ReassociatePass());
this->llvm_fpmgr_->addPass(llvm::GVNPass());
this->llvm_fpmgr_->addPass(llvm::SimplifyCFGPass());
/** tracking for analysis passes that share info? **/
llvm::PassBuilder llvm_pass_builder;
llvm_pass_builder.registerModuleAnalyses(*llvm_mamgr_);
llvm_pass_builder.registerFunctionAnalyses(*llvm_famgr_);
llvm_pass_builder.crossRegisterProxies(*llvm_lamgr_, *llvm_famgr_, *llvm_cgamgr_, *llvm_mamgr_);
} /*ctor*/
void
IrPipeline::run_pipeline(llvm::Function & fn)
{
llvm_fpmgr_->run(fn, *llvm_famgr_);
} /*run_pipeline*/
} /*namespace jit*/
} /*namespace xo*/
/* end IrPipeline.cpp */

10
xo-jit/src/jit/Jit.cpp Normal file
View file

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

View file

@ -0,0 +1,20 @@
/* @file LlvmContext.cpp */
#include "LlvmContext.hpp"
namespace xo {
namespace jit {
rp<LlvmContext>
LlvmContext::make() {
return new LlvmContext();
}
LlvmContext::LlvmContext()
: llvm_cx_{std::make_unique<llvm::LLVMContext>()}
{}
} /*namespace jit*/
} /*namespace xo*/
/* end LlvmContext.cpp */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,435 @@
/* @file activation_record.cpp */
#include "activation_record.hpp"
#include "type2llvm.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <iostream>
namespace xo {
namespace jit {
using std::cerr;
using std::endl;
activation_record::activation_record(const rp<Lambda> & lm)
: lambda_{lm},
binding_v_(lm->n_arg())
{
/* populate binding_v_ */
int n_arg = lm->n_arg();
binding_v_.resize(n_arg);
/* next slot# to use in explicit activation record */
int rt_env_slot = 0;
for (int i_arg = 0; i_arg < n_arg; ++i_arg) {
if (lm->is_captured(lm->i_argname(i_arg))) {
/* local param #i_arg needs a slot in explicit activation record */
binding_v_[i_arg] = runtime_binding_path::local(rt_env_slot);
++rt_env_slot;
} else {
binding_v_[i_arg] = runtime_binding_path::stackonly();
}
}
} /*ctor*/
const runtime_binding_detail *
activation_record::lookup_var(const std::string & x) const
{
constexpr bool c_debug_flag = true;
using xo::scope;
scope log(XO_DEBUG(c_debug_flag));
auto ix = frame_.find(x);
if (ix == frame_.end()) {
cerr << "activation_record::lookup_var: no binding for variable x"
<< xtag("x", x)
<< endl;
cerr << "frame:";
for (const auto & ix : frame_)
cerr << xtag("var", ix.first) << xtag("->", ix.second) << endl;
return nullptr;
}
return &(ix->second);
} /*lookup_var*/
const runtime_binding_detail *
activation_record::alloc_var(const std::string & x,
const runtime_binding_detail & binding)
{
constexpr bool c_debug_flag = true;
using xo::scope;
scope log(XO_DEBUG(c_debug_flag));
log && log(xtag("var", x),
xtag("binding", binding));
if (frame_.find(x) != frame_.end()) {
cerr << "activation_record::alloc_var: variable x already present in frame"
<< xtag("x", x)
<< endl;
return nullptr;
}
frame_[x] = binding;
return &(frame_[x]);
} /*alloc_var*/
/* in kaleidoscope7.cpp: CreateEntryBlockAlloca */
runtime_binding_detail
activation_record::create_entry_block_alloca(ref::brw<LlvmContext> llvm_cx,
//const llvm::DataLayout & data_layout,
llvm::Function * llvm_fn,
llvm::IRBuilder<> & fn_ir_builder,
int i_arg,
const std::string & var_name,
TypeDescr var_type)
{
constexpr bool c_debug_flag = true;
using xo::scope;
scope log(XO_DEBUG(c_debug_flag));
log && log(xtag("llvm_fn", (void*)llvm_fn),
xtag("i_arg", i_arg),
xtag("var_name", var_name),
xtag("var_type", var_type->short_name()));
llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx,
var_type);
log && log(xtag("addr(llvm_var_type)", (void*)llvm_var_type));
if (log) {
std::string llvm_var_type_str;
llvm::raw_string_ostream ss(llvm_var_type_str);
llvm_var_type->print(ss);
log(xtag("llvm_var_type", llvm_var_type_str));
}
if (!llvm_var_type)
return runtime_binding_detail{}; /*sentinel*/
llvm::AllocaInst * stackaddr = fn_ir_builder.CreateAlloca(llvm_var_type,
nullptr,
var_name);
log && log(xtag("alloca", (void*)stackaddr),
xtag("align", stackaddr->getAlign().value())
//xtag("size", retval->getAllocationSize(data_layout).value())
);
return {i_arg, stackaddr, llvm_var_type};
} /*create_entry_block_alloca*/
#ifdef NOT_USING
llvm::AllocaInst *
activation_record::create_runtime_localenv_alloca(ref::brw<LlvmContext> llvm_cx,
//const llvm::DataLayout & data_layout,
llvm::Function * llvm_fn,
llvm::IRBuilder<> & ir_builder)
{
constexpr bool c_debug_flag = true;
using xo::scope;
scope log(XO_DEBUG(c_debug_flag),
xtag("llvm_fn", (void*)llvm_fn));
llvm::StructType * localenv_llvm_type
= type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow());
if (!localenv_llvm_type)
return nullptr;
llvm::AllocaInst * retval = ir_builder.CreateAlloca(localenv_llvm_type,
nullptr /*ArraySize*/,
"_localenv");
log && log(xtag("alloca", (void*)retval),
xtag("align", retval->getAlign().value())
//xtag("size", retval->getAllocationSize(data_layout).value())
);
return retval;
} /*create_runtime_localenv_alloca*/
#endif
llvm::Value *
activation_record::runtime_localenv_slot_addr(ref::brw<LlvmContext> llvm_cx,
llvm::StructType * localenv_llvm_type,
llvm::AllocaInst * localenv_alloca,
int i_slot,
#ifdef NOT_HERE
llvm::Value * llvm_slot_value,
#endif
llvm::IRBuilder<> & tmp_ir_builder)
{
llvm::Value * i0_slot
= llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(),
llvm::APInt(32 /*bits*/, 0));
llvm::Value * i32_slot
= llvm::ConstantInt::get(llvm_cx->llvm_cx_ref(),
llvm::APInt(32 /*bits*/,
i_slot /*value*/));
std::array<llvm::Value*, 2> index_v = {
{i0_slot, i32_slot /*environment slot #0*/}};
llvm::Value * llvm_localenv_slot_ptr
= tmp_ir_builder.CreateInBoundsGEP(localenv_llvm_type,
localenv_alloca,
index_v);
return llvm_localenv_slot_ptr;
#ifdef NOT_HERE
tmp_ir_builder.CreateStore(llvm_value, //llvm_0ptr,
llvm_parent_env_ptr);
#endif
} /*runtime_localenv_slot_addr*/
bool
activation_record::bind_locals(ref::brw<LlvmContext> llvm_cx,
//const llvm::DataLayout & data_layout,
llvm::Function * llvm_fn,
llvm::IRBuilder<> & ir_builder)
{
constexpr bool c_debug_flag = true;
using xo::scope;
scope log(XO_DEBUG(c_debug_flag),
xtag("lambda-name", lambda_->name()));
llvm::IRBuilder<> tmp_ir_builder(&llvm_fn->getEntryBlock(),
llvm_fn->getEntryBlock().begin());
/* 1st pass: handle stackonly variables
*
* We presume this must come first,
* for subsequent mem2reg optimization pass to consider
*/
{
int i_arg = 0;
for (auto & arg : llvm_fn->args()) {
if (i_arg == 0) {
/* 1st argument is injected environment pointer.
* we don't need that to be on the stack,
* since not modifiable and not user-referencable.
*/
} else {
std::string arg_name = std::string(arg.getName());
log && log("nested environment",
xtag("i", i_arg),
xtag("arg[i]", arg_name),
xtag("stackonly(i)", binding_v_[i_arg-1].is_stackonly()));
if (binding_v_[i_arg-1].is_stackonly()) {
/* stack location for arg[i] */
runtime_binding_detail binding
= create_entry_block_alloca(llvm_cx,
//data_layout,
llvm_fn,
tmp_ir_builder,
i_arg,
arg_name,
lambda_->fn_arg(i_arg-1));
if (!binding.llvm_addr_)
return false;
/* store on function entry
* see codegen_variable() for corresponding load
*/
ir_builder.CreateStore(&arg, binding.llvm_addr_);
/* remember stack location for reference + assignment
* in lambda body.
*
*/
this->alloc_var(arg_name, binding);
}
}
++i_arg;
}
}
/* REMINDER: all functions need to follow the closure pattern,
* to accomodate cases where we don't know until runtime
* what kind of function we are invoking.
*
* This means:
* - always represent function in IR by a closure-shaped object
*
* +-------+
* | o---------> native function
* +-------+
* | o---------> runtime localenv
* +-------+ (possibly nullptr)
*
* We hope to optimize away the closures in cases where we know
* their contents at compile time
*
*/
/* 2nd pass: handle captured formal parameters */
if (lambda_->needs_closure_flag()) {
llvm::StructType * localenv_llvm_type
= type2llvm::create_localenv_llvm_type(llvm_cx, lambda_.borrow());
#ifdef NOT_USING
llvm::PointerType * envapiptr_llvm_type
= type2llvm::env_api_llvm_ptr_type(llvm_cx);
#endif
if (!localenv_llvm_type)
return false;
/*
* runtime localenv: ^
* | parent
* +-------+ |
* parent_env [0] | o-------/
* +-------+
* unwind_fn [1] | o-------> env * (*)(env*, ctl)
* +-------+
* arg[i] [2+i] . ... .
* . ... .
* +-------+
*
* ctl=0 unwind. finalization for any arg[i] that requires it.
* returns nullptr
* ctl=1 copy. copy runtime environment to heap destination
* and return address of the copy
*
* arg[] comprises the subset of lambda arg names arg[j] for which
* lambda->is_captured(arg[j]) is true
*/
llvm::AllocaInst * localenv_alloca
= tmp_ir_builder.CreateAlloca(localenv_llvm_type,
nullptr /*ArraySize*/,
"_localenv");
if (!localenv_alloca)
return false;
/* remember environemnt location.
* Will need this if need to copy-to-stack
*/
this->localenv_alloca_ = localenv_alloca;
int i_localenv_slot = 0;
/* store localenv->parent_env */
{
llvm::Value * slot_addr
= runtime_localenv_slot_addr(llvm_cx,
localenv_llvm_type,
localenv_alloca,
i_localenv_slot,
//llvm_0ptr,
tmp_ir_builder);
if (!slot_addr)
return false;
++i_localenv_slot;
/* null pointer for now */
/* TODO: get parent environment (from runtime closure created for this function) */
llvm::Value * llvm_0ptr
= llvm::ConstantPointerNull::get(type2llvm::env_api_llvm_ptr_type(llvm_cx));
tmp_ir_builder.CreateStore(llvm_0ptr,
slot_addr);
}
/* store localenv->unwind_fn */
{
llvm::Value * slot_addr
= runtime_localenv_slot_addr(llvm_cx,
localenv_llvm_type,
localenv_alloca,
i_localenv_slot,
//llvm_0ptr,
tmp_ir_builder);
if (!slot_addr)
return false;
++i_localenv_slot;
/* null function pointer for now */
/* TODO: construct unwind function */
llvm::Value * llvm_0ptr
= (llvm::ConstantPointerNull::get
(type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx)));
tmp_ir_builder.CreateStore(llvm_0ptr,
slot_addr);
}
int i_arg = 0;
for (llvm::Argument & arg : llvm_fn->args()) {
if (i_arg == 0) {
/* to remove all doubt, ignore first arg here.
* it's non-captureable environment pointer
*/
} else {
std::string arg_name = std::string(arg.getName());
log && log("nested environment",
xtag("i", i_arg),
xtag("arg[i]", arg_name),
xtag("captured(i)", binding_v_[i_arg-1].is_captured()));
if (binding_v_[i_arg-1].is_captured()) {
// do something with runtime-local-env for this llvm_fn
/* remember stack location for reference + assignment
* in lambda body.
*
*/
TypeDescr arg_td = lambda_->fn_arg(i_arg-1);
llvm::Type * llvm_var_type = type2llvm::td_to_llvm_type(llvm_cx, arg_td);
llvm::Value * slot_addr
= runtime_localenv_slot_addr(llvm_cx,
localenv_llvm_type,
localenv_alloca,
i_localenv_slot,
tmp_ir_builder);
if (!slot_addr)
return false;
++i_localenv_slot;
tmp_ir_builder.CreateStore(&arg, slot_addr);
runtime_binding_detail binding = { i_arg, slot_addr, llvm_var_type };
this->alloc_var(arg_name, binding);
}
}
++i_arg;
}
}
return true;
} /*bind_locals*/
} /*namespace jit*/
} /*namespace xo*/
/* end activation_record.cpp */

View file

@ -0,0 +1,47 @@
/* @file activation_record.cpp */
#include "activation_record.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <iostream>
namespace xo {
namespace jit {
using std::cerr;
using std::endl;
int32_t
activation_record::lookup_var(const std::string & x) const {
auto ix = name2ix_map_.find(x);
if (ix == name2ix_map_.end()) {
cerr << "activation_record::lookup_var: no binding for variable x"
<< xtag("x", x)
<< endl;
return -1;
}
return ix->second;
} /*lookup_var*/
#ifdef OBSOLETE
llvm::AllocaInst *
activation_record::alloc_var(const std::string & x,
llvm::AllocaInst * alloca)
{
if (frame_.find(x) != frame_.end()) {
cerr << "activation_record::alloc_var: variable x already present in frame"
<< xtag("x", x)
<< endl;
return nullptr;
}
frame_[x] = alloca;
return alloca;
} /*alloc_var*/
#endif
} /*namespace jit*/
} /*namespace xo*/
/* end activation_record.cpp */

View file

@ -0,0 +1,45 @@
/* @file activation_record.cpp */
#include "activation_record.hpp"
#include "xo/indentlog/print/tag.hpp"
#include <iostream>
namespace xo {
namespace jit {
using std::cerr;
using std::endl;
llvm::AllocaInst *
activation_record::lookup_var(const std::string & x) const {
auto ix = frame_.find(x);
if (ix == frame_.end()) {
cerr << "activation_record::lookup_var: no binding for variable x"
<< xtag("x", x)
<< endl;
return nullptr;
}
return ix->second;
} /*lookup_var*/
llvm::AllocaInst *
activation_record::alloc_var(const std::string & x,
llvm::AllocaInst * alloca)
{
if (frame_.find(x) != frame_.end()) {
cerr << "activation_record::alloc_var: variable x already present in frame"
<< xtag("x", x)
<< endl;
return nullptr;
}
frame_[x] = alloca;
return alloca;
} /*alloc_var*/
} /*namespace jit*/
} /*namespace xo*/
/* end activation_record.cpp */

View file

@ -0,0 +1,20 @@
/* @file intrinsics.cpp */
#include "intrinsics.hpp"
/* FIXME: don't know how to mangle symbols yet,
* so putting functions invoked from jit into global namespace
*/
extern "C"
int32_t
mul_i32(int32_t x, int32_t y) {
return x * y;
}
extern "C"
double
mul_f64(double x, double y) {
return x * y;
}
/* end intrinsics.cpp */

View file

@ -0,0 +1,409 @@
/* @file type2llvm.cpp */
#include "type2llvm.hpp"
#include "xo/reflect/Reflect.hpp"
//#include "xo/reflect/struct/StructMember.hpp"
namespace xo {
using xo::reflect::Reflect;
using xo::reflect::TypeDescr;
using xo::reflect::StructMember;
using std::cerr;
using std::endl;
namespace jit {
/** REMINDER:
* 1. creation of llvm types is idempotent
* (duplicate calls will receive the same llvm::Type* pointer)
* 2. llvm::Types are never deleted.
**/
llvm::Type *
type2llvm::td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx, TypeDescr td) {
auto & llvm_cx_ref = llvm_cx->llvm_cx_ref();
if (td->is_function()) {
/* in this context, we're looking for a representation for a value,
* i.e. something that can be stored in a variable
*/
//return function_td_to_llvm_fnptr_type(llvm_cx, td);
return function_td_to_closureapi_lvtype(llvm_cx, td, "");
} else if (td->is_struct()) {
return struct_td_to_llvm_type(llvm_cx, td);
} else if (td->is_pointer()) {
return pointer_td_to_llvm_type(llvm_cx, td);
} else if (Reflect::is_native<bool>(td)) {
return llvm::Type::getInt1Ty(llvm_cx_ref);
} else if (Reflect::is_native<char>(td)) {
return llvm::Type::getInt8Ty(llvm_cx_ref);
} else if (Reflect::is_native<short>(td)) {
return llvm::Type::getInt16Ty(llvm_cx_ref);
} else if (Reflect::is_native<int>(td)) {
return llvm::Type::getInt32Ty(llvm_cx_ref);
} else if (Reflect::is_native<long>(td)) {
return llvm::Type::getInt64Ty(llvm_cx_ref);
} else if (Reflect::is_native<float>(td)) {
return llvm::Type::getFloatTy(llvm_cx_ref);
} else if (Reflect::is_native<double>(td)) {
return llvm::Type::getDoubleTy(llvm_cx_ref);
} else {
cerr << "td_to_llvm_type: no llvm type available for T"
<< xtag("T", td->short_name())
<< endl;
return nullptr;
}
} /*td_to_llvm_type*/
/** obtain llvm representation for a function type with the same signature as
* that represented by @p fn_td
**/
llvm::FunctionType *
type2llvm::function_td_to_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
TypeDescr fn_td,
bool wrapper_flag)
{
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG(c_debug_flag));
int n_ast_fn_arg = fn_td->n_fn_arg();
if (log) {
log(xtag("fn_td", fn_td->short_name()));
log(xtag("n_ast_fn_arg", n_ast_fn_arg));
}
std::vector<llvm::Type *> llvm_argtype_v;
llvm_argtype_v.reserve(n_ast_fn_arg + (wrapper_flag ? 1 : 0));
if (wrapper_flag)
llvm_argtype_v.push_back(env_api_llvm_ptr_type(llvm_cx));
/** check function args are all known **/
for (int i = 0; i < n_ast_fn_arg; ++i) {
TypeDescr arg_td = fn_td->fn_arg(i);
llvm::Type * llvm_argtype = type2llvm::td_to_llvm_type(llvm_cx, arg_td);
if (!llvm_argtype)
return nullptr;
if (log) {
log(xtag("arg_td", arg_td->short_name()));
log(xtag("llvm_argtype", "..."));
llvm_argtype->dump();
log("...done");
}
llvm_argtype_v.push_back(llvm_argtype);
}
TypeDescr retval_td = fn_td->fn_retval();
llvm::Type * llvm_retval = type2llvm::td_to_llvm_type(llvm_cx, retval_td);
if (log) {
log(xtag("retval_td", retval_td->short_name()));
log(xtag("llvm_retval", "..."));
llvm_retval->dump();
log("...done");
}
if (!llvm_retval)
return nullptr;
auto * llvm_fn_type = llvm::FunctionType::get(llvm_retval,
llvm_argtype_v,
false /*!varargs*/);
return llvm_fn_type;
} /*function_td_to_llvm_type*/
llvm::PointerType *
type2llvm::function_td_to_llvm_fnptr_type(xo::ref::brw<LlvmContext> llvm_cx,
TypeDescr /*fn_td*/,
bool /*wrapper_flag*/)
{
#ifdef OBSOLETE
auto * llvm_fn_type = function_td_to_lvtype(llvm_cx, fn_td, wrapper_flag);
#endif
/** like C: llvm IR doesn't support function-valued variables;
* it does however support pointer-to-function-valued variables
**/
auto * llvm_ptr_type
= llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref());
#ifdef OBSOLETE
auto * llvm_ptr_type
= llvm::PointerType::get(llvm_fn_type,
0 /*numbered address space*/);
#endif
return llvm_ptr_type;
}
/**
* Generate llvm::Type correspoinding to a TypeDescr for a struct.
**/
llvm::StructType *
type2llvm::struct_td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
TypeDescr struct_td)
{
// see
// [[https://stackoverflow.com/questions/32299166/accessing-struct-members-and-arrays-of-structs-from-llvm-ir]]
auto & llvm_cx_ref = llvm_cx->llvm_cx_ref();
/* note: object pointer ignored for struct types,
* since number of members is known at compile time
*/
int n_member = struct_td->n_child(nullptr /*&object*/);
/* one type for each struct member */
std::vector<llvm::Type *> llvm_membertype_v;
llvm_membertype_v.reserve(n_member);
for (int i = 0; i < n_member; ++i) {
StructMember const & sm = struct_td->struct_member(i);
llvm_membertype_v.push_back(type2llvm::td_to_llvm_type(llvm_cx,
sm.get_member_td()));
}
std::string struct_name = std::string(struct_td->short_name());
/* structs with names: within an llvmcontext, must be unique
*
* We can however compare the offsets recorded in xo::reflect with
* offsets chosen by llvm, *once we've created the llvm type*
*
* Also, we can't guarantee that a c++ type was completely reflected --
* it's possible one or more members were omitted, in which case
* it's unlikely at best that llvm chooses the same layout.
*
* Instead: tell llvm to make packed struct,
* and introduce dummy members for padding.
*
* A consequence is we have to maintain mapping between llvm's
* member numbering and xo::reflect's
*/
llvm::StructType * llvm_struct_type
= llvm::StructType::create(llvm_cx_ref,
llvm_membertype_v,
llvm::StringRef(struct_name),
false /*!isPacked*/);
/* TODO: inspect (how) offsets that llvm is using
* we need them to match what C++ chose
*
* (because we want jitted llvm code to interoperate with
* C++ library code that has structs)
*/
// GetElementPtrInst is interesting,
// but I think that's for generating code
return llvm_struct_type;
} /*struct_td_to_llvm_type*/
llvm::PointerType *
type2llvm::pointer_td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx
, TypeDescr pointer_td
)
{
(void)pointer_td;
assert(pointer_td->is_pointer());
#ifdef OBSOLETE
TypeDescr dest_td = pointer_td->fixed_child_td(0);
llvm::Type * llvm_dest_type = type2llvm::td_to_llvm_type(llvm_cx, dest_td);
llvm::PointerType * llvm_ptr_type
= llvm::PointerType::getUnqual(llvm_dest_type);
#endif
llvm::PointerType * llvm_ptr_type
= llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref());
return llvm_ptr_type;
} /*pointer_td_llvm_type*/
llvm::PointerType *
type2llvm::require_localenv_unwind_llvm_fnptr_type(xo::ref::brw<LlvmContext> llvm_cx,
llvm::PointerType * /*envptr_llvm_type*/)
{
#ifdef OBSOLETE
if (!envptr_llvm_type)
envptr_llvm_type = env_api_llvm_ptr_type(llvm_cx);
std::vector<llvm::Type *> llvm_argtype_v;
llvm_argtype_v.reserve(2);
/* 1st arg is _env_api pointer */
llvm_argtype_v.push_back(envptr_llvm_type);
/* 2nd arg is i32 */
llvm_argtype_v.push_back(llvm::Type::getInt32Ty(llvm_cx->llvm_cx_ref()));
/* return value is _env_api pointer */
llvm::Type * retval_llvm_type = envptr_llvm_type;
/* _env_api* (_env_api*, i32) */
auto * unwind_llvm_type
= llvm::FunctionType::get(retval_llvm_type,
llvm_argtype_v,
false /*!varargs*/);
/* _env_api* (*)(_env_api*, i32) */
auto * unwind_llvm_fnptr_type
= llvm::PointerType::getUnqual(unwind_llvm_type);
#endif
auto * unwind_llvm_fnptr_type
= llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref());
return unwind_llvm_fnptr_type;
} /*require_localenv_unwind_llvm_fnptr_type*/
llvm::StructType *
type2llvm::env_api_llvm_type(xo::ref::brw<LlvmContext> llvm_cx)
{
/* _env_api: base type for a local environment */
llvm::StructType * env_llvm_type
= llvm::StructType::get(llvm_cx->llvm_cx_ref(),
"_env_api");
#ifdef OBSOLETE
/* _env_api[0]: pointer to a local environment */
llvm::PointerType * envptr_llvm_type
= llvm::PointerType::getUnqual(env_llvm_type);
#endif
llvm::PointerType * envptr_llvm_type
= llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref());
/* _env_api[1]: unwwind/copy function */
llvm::PointerType * unwind_llvm_fnptr_type
= type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx,
envptr_llvm_type);
/* now supply _env_api members */
env_llvm_type->setBody(envptr_llvm_type /*_env_api[0]*/,
unwind_llvm_fnptr_type /*_env_api[1]*/);
return env_llvm_type;
} /*env_api_llvm_type*/
llvm::PointerType *
type2llvm::env_api_llvm_ptr_type(xo::ref::brw<LlvmContext> llvm_cx)
{
#ifdef OBSOLETE
llvm::StructType * env_llvm_type = env_api_llvm_type(llvm_cx);
return llvm::PointerType::getUnqual(env_llvm_type);
#endif
return llvm::PointerType::getUnqual(llvm_cx->llvm_cx_ref());
} /*env_api_llvm_ptr_type*/
llvm::StructType *
type2llvm::create_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
xo::ref::brw<FunctionInterface> fn)
{
constexpr const char * c_prefix = "c.";
/* e.g. "c.foo" */
std::string closure_name = std::string(c_prefix) + fn->name();
return function_td_to_closureapi_lvtype(llvm_cx,
fn->valuetype(),
closure_name);
} /*create_closureapi_lvtype*/
llvm::StructType *
type2llvm::function_td_to_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
TypeDescr fn_td,
const std::string & hint_name)
{
constexpr bool c_debug_flag = false;
scope log(XO_DEBUG(c_debug_flag));
/* would be precisely correct to use create_localenv_llvm_type()
* here. However judged not sufficiently helpful.
* Would still
* need environment cast whenever closure in apply position is
* not known at compile time.
*/
llvm::PointerType * fn_lvtype = function_td_to_llvm_fnptr_type(llvm_cx,
fn_td,
true /*wrapper_flag*/);
if (log) {
log(xtag("fn_lvtype", "..."));
fn_lvtype->dump();
log("...done");
}
llvm::PointerType * envptr_lvtype = env_api_llvm_ptr_type(llvm_cx);
if (log) {
log(xtag("env_lvtype", "..."));
envptr_lvtype->dump();
log("...done");
}
std::vector<llvm::Type *> member_lvtype_v = { fn_lvtype, envptr_lvtype };
llvm::StructType * closure_lvtype
= llvm::StructType::get(llvm_cx->llvm_cx_ref(), member_lvtype_v);
//closure_lvtype->setBody(member_lvtype_v);
if (!hint_name.empty())
closure_lvtype->setName(llvm::StringRef(hint_name));
if (log) {
log(xtag("closure_lvtype", "..."));
closure_lvtype->dump();
log("...done");
}
return closure_lvtype;
} /*function_td_to_closureapi_lvtype*/
llvm::StructType *
type2llvm::create_localenv_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
xo::ref::brw<Lambda> lambda)
{
constexpr const char * c_prefix = "e.";
llvm::PointerType * parentenvptr_llvm_type = env_api_llvm_ptr_type(llvm_cx);
llvm::PointerType * unwind_llvm_fnptr_type
= type2llvm::require_localenv_unwind_llvm_fnptr_type(llvm_cx, parentenvptr_llvm_type);
std::vector<llvm::Type *> member_llvm_type_v;
member_llvm_type_v.push_back(parentenvptr_llvm_type);
member_llvm_type_v.push_back(unwind_llvm_fnptr_type);
for (const auto & var : lambda->argv()) {
if (lambda->is_captured(var->name())) {
/* var is captured -> needs a slot in the localenv_llvm_type belonging to this lambda */
member_llvm_type_v.push_back(td_to_llvm_type(llvm_cx,
var->valuetype()));
}
}
/* e.g. "e.foo" */
std::string env_name = std::string(c_prefix) + lambda->name();
llvm::StructType * localenv_lvtype
= llvm::StructType::get(llvm_cx->llvm_cx_ref());
localenv_lvtype->setName(env_name);
localenv_lvtype->setBody(member_llvm_type_v, false /*!is_packed*/);
return localenv_lvtype;
} /*create_localenv_llvm_type*/
} /*namespace jit*/
} /*namespace xo*/
/* end type2llvm.cpp */