Add 'xo-jit/' from commit '855887df71'
git-subtree-dir: xo-jit git-subtree-mainline:35555df976git-subtree-split:855887df71
This commit is contained in:
commit
757dfed99c
49 changed files with 7305 additions and 0 deletions
50
xo-jit/src/jit/CMakeLists.txt
Normal file
50
xo-jit/src/jit/CMakeLists.txt
Normal 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
|
||||
55
xo-jit/src/jit/IrPipeline.cpp
Normal file
55
xo-jit/src/jit/IrPipeline.cpp
Normal 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
10
xo-jit/src/jit/Jit.cpp
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/* @file Jit.cpp */
|
||||
|
||||
#include "Jit.hpp"
|
||||
|
||||
namespace xo {
|
||||
namespace jit {
|
||||
} /*namespace jit*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end Jit.cpp */
|
||||
20
xo-jit/src/jit/LlvmContext.cpp
Normal file
20
xo-jit/src/jit/LlvmContext.cpp
Normal 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 */
|
||||
1132
xo-jit/src/jit/MachPipeline.cpp
Normal file
1132
xo-jit/src/jit/MachPipeline.cpp
Normal file
File diff suppressed because it is too large
Load diff
1341
xo-jit/src/jit/MachPipeline.new.cpp
Normal file
1341
xo-jit/src/jit/MachPipeline.new.cpp
Normal file
File diff suppressed because it is too large
Load diff
1064
xo-jit/src/jit/MachPipeline.orig.cpp
Normal file
1064
xo-jit/src/jit/MachPipeline.orig.cpp
Normal file
File diff suppressed because it is too large
Load diff
435
xo-jit/src/jit/activation_record.cpp
Normal file
435
xo-jit/src/jit/activation_record.cpp
Normal 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 */
|
||||
47
xo-jit/src/jit/activation_record.new.cpp
Normal file
47
xo-jit/src/jit/activation_record.new.cpp
Normal 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 */
|
||||
45
xo-jit/src/jit/activation_record.orig.cpp
Normal file
45
xo-jit/src/jit/activation_record.orig.cpp
Normal 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 */
|
||||
20
xo-jit/src/jit/intrinsics.cpp
Normal file
20
xo-jit/src/jit/intrinsics.cpp
Normal 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 */
|
||||
409
xo-jit/src/jit/type2llvm.cpp
Normal file
409
xo-jit/src/jit/type2llvm.cpp
Normal 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 */
|
||||
Loading…
Add table
Add a link
Reference in a new issue