Merge branch 'main' of github.com:Rconybea/xo-jit
This commit is contained in:
commit
04bb6891ec
15 changed files with 2030 additions and 392 deletions
27
HOWTO
27
HOWTO
|
|
@ -43,3 +43,30 @@
|
|||
mp.machgen_current_module()
|
||||
fn=mp.lookup_fn('int (*)(int)', 'sq')
|
||||
fn(16) # -> 256
|
||||
|
||||
** to figure out what 'IR should look like' for something simple
|
||||
|
||||
write some c++:
|
||||
|
||||
#include <cmath>
|
||||
|
||||
struct env_type {
|
||||
env_type * parent;
|
||||
env_type * (*unwind)(env_type *, int);
|
||||
};
|
||||
|
||||
double wrap_sqrt(env_type * env, double x) {
|
||||
return ::sqrt(x);
|
||||
}
|
||||
|
||||
int main() {
|
||||
wrap_sqrt(nullptr, 2.0);
|
||||
}
|
||||
|
||||
compile to emit IR
|
||||
|
||||
$ clang -cc1 ex_cpp.cpp -emit-llvm
|
||||
|
||||
inspect
|
||||
|
||||
ex_cpp.ll
|
||||
|
|
|
|||
|
|
@ -4,6 +4,53 @@ Glossary
|
|||
--------
|
||||
|
||||
.. glossary::
|
||||
ABI
|
||||
| Short for Application Binary Interface.
|
||||
| In this context applies to conventions adopted by `xo-jit`.
|
||||
|
||||
c.foo
|
||||
| llvm typename for automatically-generated closure type for a lambda
|
||||
| with name `foo`.
|
||||
|
||||
e.foo
|
||||
| llvm typename for automatically-generated local environment for a
|
||||
| lambda with name `foo`.
|
||||
|
||||
w.foo
|
||||
| llvm typename for automatically-generated wrapper function for a
|
||||
| primitive function `foo`. The wrapper function accepts and ignores
|
||||
| an extra initial argument supplying an environment pointer.
|
||||
|
|
||||
| We apply this practice so that lambdas and primitives support the
|
||||
| same ABI, so that we can support pointers to abstract functions
|
||||
| that might at runtime turn out to be either primitives or lambdas
|
||||
|
||||
lambda
|
||||
| Common use is for lambda to refer to an anonymous function.
|
||||
| In llvm we need all functions to be named, and those names
|
||||
| have to be unique.
|
||||
|
|
||||
| Since all functions have to be named, we cheerfully adopt
|
||||
| the oxymoron 'named lambda'
|
||||
|
|
||||
| Practices:
|
||||
| 1. Automatically generate unique names for anonymous lambdas.
|
||||
| 2. Incorporate user-provided names for convenience, when provided.
|
||||
| 3. Still have to uniqueify names for user-provided nested lambdas.
|
||||
|
||||
localenv
|
||||
| Shorthand for local environment.
|
||||
| Represents an explicit runtime repsentation for a struct that
|
||||
| holds captured function arguments with the ability to be persisted
|
||||
| (for example, moved to the the heap).
|
||||
|
|
||||
| Note that library `xo-expression` also uses the term environment, but differently.
|
||||
| In that context describes all function arguments.
|
||||
|
||||
lvtype
|
||||
| Shorthand for `llvm::Type`:
|
||||
| llvm-owned representation for a datatype
|
||||
|
||||
xsession
|
||||
| Shorthand for `llvm::orc::ExecutionSession`.
|
||||
| Manages running JIT-generated machine code in the host process
|
||||
|
|
|
|||
6
example/ex_cpp/README
Normal file
6
example/ex_cpp/README
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Not including this in build for now.
|
||||
Instead, use to manually generate .ll output:
|
||||
|
||||
$ clang -cc1 ex_cpp.cpp -emit-llvm
|
||||
|
||||
and inspect ex_cpp.ll
|
||||
40
example/ex_cpp/ex_cpp.cpp
Normal file
40
example/ex_cpp/ex_cpp.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
struct env_type;
|
||||
|
||||
struct closure_type {
|
||||
double (*fnptr)(env_type * env, double x);
|
||||
env_type * envptr;
|
||||
};
|
||||
|
||||
double
|
||||
sqrt(double x) {
|
||||
return x/100;
|
||||
}
|
||||
|
||||
double
|
||||
wrap_sqrt(env_type * env, double x) {
|
||||
return ::sqrt(x);
|
||||
}
|
||||
|
||||
double twice(env_type * env, closure_type fnclosure, double x) {
|
||||
double tmp1 = (*fnclosure.fnptr)(fnclosure.envptr, x);
|
||||
double tmp2 = (*fnclosure.fnptr)(fnclosure.envptr, tmp1);
|
||||
|
||||
return tmp2;
|
||||
}
|
||||
|
||||
closure_type make_some_closure()
|
||||
{
|
||||
closure_type closure;
|
||||
closure.fnptr = &wrap_sqrt;
|
||||
closure.envptr = nullptr;
|
||||
|
||||
return closure;
|
||||
}
|
||||
|
||||
int main() {
|
||||
closure_type closure = make_some_closure();
|
||||
|
||||
double y = twice(nullptr, closure, 4.0);
|
||||
|
||||
//std::cout << "y=" << y << std::endl;
|
||||
}
|
||||
108
example/ex_cpp/ex_cpp.ll
Normal file
108
example/ex_cpp/ex_cpp.ll
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
; ModuleID = 'ex_cpp.cpp'
|
||||
source_filename = "ex_cpp.cpp"
|
||||
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64-unknown-linux-gnu"
|
||||
|
||||
%struct.closure_type = type { ptr, ptr }
|
||||
|
||||
; Function Attrs: mustprogress noinline nounwind optnone
|
||||
define dso_local noundef double @_Z4sqrtd(double noundef %x) #0 {
|
||||
entry:
|
||||
%x.addr = alloca double, align 8
|
||||
store double %x, ptr %x.addr, align 8
|
||||
%0 = load double, ptr %x.addr, align 8
|
||||
%div = fdiv double %0, 1.000000e+02
|
||||
ret double %div
|
||||
}
|
||||
|
||||
; Function Attrs: mustprogress noinline nounwind optnone
|
||||
define dso_local noundef double @_Z9wrap_sqrtP8env_typed(ptr noundef %env, double noundef %x) #0 {
|
||||
entry:
|
||||
%env.addr = alloca ptr, align 8
|
||||
%x.addr = alloca double, align 8
|
||||
store ptr %env, ptr %env.addr, align 8
|
||||
store double %x, ptr %x.addr, align 8
|
||||
%0 = load double, ptr %x.addr, align 8
|
||||
%call = call noundef double @_Z4sqrtd(double noundef %0)
|
||||
ret double %call
|
||||
}
|
||||
|
||||
; Function Attrs: mustprogress noinline nounwind optnone
|
||||
define dso_local noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef %env, ptr %fnclosure.coerce0, ptr %fnclosure.coerce1, double noundef %x) #0 {
|
||||
entry:
|
||||
%fnclosure = alloca %struct.closure_type, align 8
|
||||
%env.addr = alloca ptr, align 8
|
||||
%x.addr = alloca double, align 8
|
||||
%tmp1 = alloca double, align 8
|
||||
%tmp2 = alloca double, align 8
|
||||
%0 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 0
|
||||
store ptr %fnclosure.coerce0, ptr %0, align 8
|
||||
%1 = getelementptr inbounds { ptr, ptr }, ptr %fnclosure, i32 0, i32 1
|
||||
store ptr %fnclosure.coerce1, ptr %1, align 8
|
||||
store ptr %env, ptr %env.addr, align 8
|
||||
store double %x, ptr %x.addr, align 8
|
||||
%fnptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0
|
||||
%2 = load ptr, ptr %fnptr, align 8
|
||||
%envptr = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1
|
||||
%3 = load ptr, ptr %envptr, align 8
|
||||
%4 = load double, ptr %x.addr, align 8
|
||||
%call = call noundef double %2(ptr noundef %3, double noundef %4)
|
||||
store double %call, ptr %tmp1, align 8
|
||||
%fnptr1 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 0
|
||||
%5 = load ptr, ptr %fnptr1, align 8
|
||||
%envptr2 = getelementptr inbounds %struct.closure_type, ptr %fnclosure, i32 0, i32 1
|
||||
%6 = load ptr, ptr %envptr2, align 8
|
||||
%7 = load double, ptr %tmp1, align 8
|
||||
%call3 = call noundef double %5(ptr noundef %6, double noundef %7)
|
||||
store double %call3, ptr %tmp2, align 8
|
||||
%8 = load double, ptr %tmp2, align 8
|
||||
ret double %8
|
||||
}
|
||||
|
||||
; Function Attrs: mustprogress noinline nounwind optnone
|
||||
define dso_local { ptr, ptr } @_Z17make_some_closurev() #0 {
|
||||
entry:
|
||||
%retval = alloca %struct.closure_type, align 8
|
||||
%fnptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 0
|
||||
store ptr @_Z9wrap_sqrtP8env_typed, ptr %fnptr, align 8
|
||||
%envptr = getelementptr inbounds %struct.closure_type, ptr %retval, i32 0, i32 1
|
||||
store ptr null, ptr %envptr, align 8
|
||||
%0 = load { ptr, ptr }, ptr %retval, align 8
|
||||
ret { ptr, ptr } %0
|
||||
}
|
||||
|
||||
; Function Attrs: mustprogress noinline norecurse nounwind optnone
|
||||
define dso_local noundef i32 @main() #1 {
|
||||
entry:
|
||||
%closure = alloca %struct.closure_type, align 8
|
||||
%y = alloca double, align 8
|
||||
%agg.tmp = alloca %struct.closure_type, align 8
|
||||
%call = call { ptr, ptr } @_Z17make_some_closurev()
|
||||
%0 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 0
|
||||
%1 = extractvalue { ptr, ptr } %call, 0
|
||||
store ptr %1, ptr %0, align 8
|
||||
%2 = getelementptr inbounds { ptr, ptr }, ptr %closure, i32 0, i32 1
|
||||
%3 = extractvalue { ptr, ptr } %call, 1
|
||||
store ptr %3, ptr %2, align 8
|
||||
call void @llvm.memcpy.p0.p0.i64(ptr align 8 %agg.tmp, ptr align 8 %closure, i64 16, i1 false)
|
||||
%4 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 0
|
||||
%5 = load ptr, ptr %4, align 8
|
||||
%6 = getelementptr inbounds { ptr, ptr }, ptr %agg.tmp, i32 0, i32 1
|
||||
%7 = load ptr, ptr %6, align 8
|
||||
%call1 = call noundef double @_Z5twiceP8env_type12closure_typed(ptr noundef null, ptr %5, ptr %7, double noundef 4.000000e+00)
|
||||
store double %call1, ptr %y, align 8
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2
|
||||
|
||||
attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
|
||||
attributes #1 = { mustprogress noinline norecurse nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
|
||||
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
|
||||
|
||||
!llvm.module.flags = !{!0}
|
||||
!llvm.ident = !{!1}
|
||||
|
||||
!0 = !{i32 1, !"wchar_size", i32 4}
|
||||
!1 = !{!"clang version 18.1.5"}
|
||||
58
example/ex_cpp/tmp.ll
Normal file
58
example/ex_cpp/tmp.ll
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
define double @root4(ptr %.env, double %x) {
|
||||
entry:
|
||||
%x1 = alloca double, align 8
|
||||
store double %x, ptr %x1, align 8
|
||||
%x2 = load double, ptr %x1, align 8
|
||||
%calltmp = call double @w.sqrt(ptr null, double %x2)
|
||||
%calltmp3 = call double @w.sqrt(ptr null, double %calltmp)
|
||||
ret double %calltmp3
|
||||
}
|
||||
|
||||
; ----------------------------------------------------------------
|
||||
|
||||
define double @twice(ptr %.env, { ptr, ptr } %f, double %x) {
|
||||
entry:
|
||||
%f1 = alloca { ptr, ptr }, align 8
|
||||
store { ptr, ptr } %f, ptr %f1, align 8
|
||||
%x2 = alloca double, align 8
|
||||
store double %x, ptr %x2, align 8
|
||||
%f3 = load { ptr, ptr }, ptr %f1, align 8
|
||||
%fnptr = extractvalue { ptr, ptr } %f3, 0
|
||||
%envptr = extractvalue { ptr, ptr } %f3, 1
|
||||
%f4 = load { ptr, ptr }, ptr %f1, align 8
|
||||
%fnptr5 = extractvalue { ptr, ptr } %f4, 0
|
||||
%envptr6 = extractvalue { ptr, ptr } %f4, 1
|
||||
%x7 = load double, ptr %x2, align 8
|
||||
%calltmp = call double %fnptr5(ptr %envptr6, double %x7)
|
||||
%calltmp8 = call double %fnptr(ptr %envptr, double %calltmp)
|
||||
ret double %calltmp8
|
||||
}
|
||||
|
||||
|
||||
define double @twice(ptr %.env, { ptr, ptr } %f, double %x) {
|
||||
entry:
|
||||
%f1 = alloca { ptr, ptr }, align 8
|
||||
%f.elt = extractvalue { ptr, ptr } %f, 0
|
||||
store ptr %f.elt, ptr %f1, align 8
|
||||
%f1.repack9 = getelementptr inbounds { ptr, ptr }, ptr %f1, i64 0, i32 1
|
||||
%f.elt10 = extractvalue { ptr, ptr } %f, 1
|
||||
store ptr %f.elt10, ptr %f1.repack9, align 8
|
||||
%calltmp = call double %f.elt(ptr %f.elt10, double %x)
|
||||
%calltmp8 = call double %f.elt(ptr %f.elt10, double %calltmp)
|
||||
ret double %calltmp8
|
||||
}
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------
|
||||
|
||||
define double @root_2x(ptr %.env, double %x2) {
|
||||
entry:
|
||||
%x21 = alloca double, align 8
|
||||
store double %x2, ptr %x21, align 8
|
||||
%envptr = insertvalue { ptr, ptr } { ptr @twice, ptr undef }, ptr %.env, 1
|
||||
%fnptr = extractvalue { ptr, ptr } %envptr, 0
|
||||
%envptr2 = extractvalue { ptr, ptr } %envptr, 1
|
||||
%x23 = load double, ptr %x21, align 8
|
||||
%calltmp = call double %fnptr(ptr %envptr2, { ptr, ptr } { ptr @w.sqrt, ptr null }, double %x23)
|
||||
ret double %calltmp
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/** ex_kaleidoscop4.cpp **/
|
||||
|
||||
#include "xo/jit/KaleidoscopeJit.hpp"
|
||||
#include "xo/jit/Jit.hpp"
|
||||
#include <iostream>
|
||||
|
||||
int
|
||||
|
|
@ -8,9 +8,9 @@ main() {
|
|||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
auto jit = xo::jit::KaleidoscopeJIT::Create();
|
||||
//auto jit = xo::jit::KaleidoscopeJIT::Create();
|
||||
|
||||
cerr << "created kaleidoscope jit successfully" << endl;
|
||||
//cerr << "created kaleidoscope jit successfully" << endl;
|
||||
}
|
||||
|
||||
/** end ex_kaleidoscope4.cpp **/
|
||||
|
|
|
|||
|
|
@ -111,16 +111,55 @@ namespace xo {
|
|||
llvm::Type * codegen_type(TypeDescr td);
|
||||
llvm::Value * codegen_constant(ref::brw<xo::ast::ConstantInterface> expr);
|
||||
llvm::Function * codegen_primitive(ref::brw<xo::ast::PrimitiveInterface> expr);
|
||||
llvm::Value * codegen_apply(ref::brw<xo::ast::Apply> expr, llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
/** like @ref codegen_primitive , but create wrapper function that accepts (and discards)
|
||||
* environment pointer as first argument.
|
||||
*
|
||||
* Implementation consists of tail call to natural primitive, that skips the unused
|
||||
* environment pointer
|
||||
**/
|
||||
llvm::Function * codegen_primitive_wrapper(ref::brw<xo::ast::PrimitiveInterface> expr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
/** Generate closure for invoking a primitive function.
|
||||
* Primitives don't benefit from a closure, but we need a consistent ABI
|
||||
* to support function-pointer-like behavior for a target function
|
||||
* that may resolve to primitive-or-lambda at runtime
|
||||
**/
|
||||
llvm::Value * codegen_primitive_closure(ref::brw<xo::ast::PrimitiveInterface> expr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
llvm::Value * codegen_apply(ref::brw<xo::ast::Apply> expr,
|
||||
llvm::Value * envptr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
/* NOTE: codegen_lambda() needs to be reentrant too.
|
||||
* for example can have a lambda in apply position.
|
||||
*/
|
||||
llvm::Function * codegen_lambda_decl(ref::brw<xo::ast::Lambda> expr);
|
||||
llvm::Function * codegen_lambda_defn(ref::brw<xo::ast::Lambda> expr, llvm::IRBuilder<> & ir_builder);
|
||||
llvm::Value * codegen_variable(ref::brw<xo::ast::Variable> var, llvm::IRBuilder<> & ir_builder);
|
||||
llvm::Value * codegen_ifexpr(ref::brw<xo::ast::IfExpr> ifexpr, llvm::IRBuilder<> & ir_builder);
|
||||
/** Generate closure for invoking a lambda (user-defined function).
|
||||
* See @ref MachPipeline::codegen_apply for invocation
|
||||
* Same ABI as @ref MachPipeline::codegen_primitive_closure
|
||||
*
|
||||
* @param envptr. Environment from surrounding lexical scope.
|
||||
* This will be captured as envptr member by
|
||||
* the IR code for creating a closure.
|
||||
* @ref MachPipeline::codegen_toplevel and friends are responsible for
|
||||
* assembling and propagating this.
|
||||
**/
|
||||
llvm::Value * codegen_lambda_closure(ref::brw<xo::ast::Lambda> lambda,
|
||||
llvm::Value * envptr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
llvm::Value * codegen_variable(ref::brw<xo::ast::Variable> var,
|
||||
llvm::Value * envptr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
llvm::Value * codegen_ifexpr(ref::brw<xo::ast::IfExpr> ifexpr,
|
||||
llvm::Value * envptr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
llvm::Value * codegen(ref::brw<Expression> expr, llvm::IRBuilder<> & ir_builder);
|
||||
llvm::Value * codegen(ref::brw<Expression> expr,
|
||||
llvm::Value * envptr,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
llvm::Value * codegen_toplevel(ref::brw<Expression> expr);
|
||||
|
||||
|
|
@ -161,6 +200,7 @@ namespace xo {
|
|||
llvm::AllocaInst * create_entry_frame_alloca(llvm::Function * llvm_fn,
|
||||
llvm::StructType * frame_llvm_type);
|
||||
|
||||
#ifdef OBSOLETE // see activation_record::create_entry_block_alloca()
|
||||
/** codegen helper for a user-defined function (codegen_lambda()):
|
||||
* create stack slot on behalf of some formal parameter to a function,
|
||||
* so we can avoid SSA restriction on function body
|
||||
|
|
@ -170,6 +210,7 @@ namespace xo {
|
|||
llvm::AllocaInst * create_entry_block_alloca(llvm::Function * llvm_fn,
|
||||
const std::string & var_name,
|
||||
TypeDescr var_type);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/** (re)create pipeline to turn expressions into llvm IR code **/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "LlvmContext.hpp"
|
||||
#include "xo/expression/Lambda.hpp"
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
# include <llvm/IR/IRBuilder.h>
|
||||
|
|
@ -16,22 +17,201 @@
|
|||
|
||||
namespace xo {
|
||||
namespace jit {
|
||||
/** scope for a stack frame associated with a user-defined function
|
||||
/** analagous to xo::ast::binding_path,
|
||||
* but with locations renumbered to include only vars that belong to an explict runtime
|
||||
* environment object; in other words we exclude vars with stack-only storage
|
||||
**/
|
||||
struct runtime_binding_path {
|
||||
public:
|
||||
runtime_binding_path() = default;
|
||||
runtime_binding_path(int i_rt_link,
|
||||
int j_rt_slot)
|
||||
: i_rt_link_{i_rt_link}, j_rt_slot_{j_rt_slot} {}
|
||||
|
||||
static runtime_binding_path stackonly() {
|
||||
return runtime_binding_path(0, -1);
|
||||
}
|
||||
static runtime_binding_path local(int j_rt_slot) {
|
||||
return runtime_binding_path(0, j_rt_slot);
|
||||
}
|
||||
|
||||
bool is_stackonly() const { return (i_rt_link_ == 0) && (j_rt_slot_ == -1); }
|
||||
bool is_captured() const { return !is_stackonly(); }
|
||||
|
||||
public:
|
||||
/** nnumber of parent runtime env links to traverse. -1 if global. -2 if sentinel **/
|
||||
int i_rt_link_ = -2;
|
||||
/** >= 0: slot# within explicit runtime environment where this variable bound.
|
||||
* (local vars only -- ignored for global vars)
|
||||
* -1: stack-only parameter
|
||||
**/
|
||||
int j_rt_slot_ = 0;
|
||||
};
|
||||
|
||||
struct runtime_binding_detail {
|
||||
/** Formal index position for this formal parameter.
|
||||
* Index into @ref activation_record::binding_v_,
|
||||
* also for @ref Lambda::fn_arg
|
||||
**/
|
||||
int i_argno_ = -1;
|
||||
|
||||
/** instructions for establishing stack address of this variable
|
||||
* In practice will be either an AllocaInst (for non-captured variables),
|
||||
* or result of IRBuilder<>::CreateInBoundsGEP (for captured variables).
|
||||
**/
|
||||
llvm::Value * llvm_addr_ = nullptr;
|
||||
|
||||
/** llvm type associated with stack-allocated variable.
|
||||
* Determines (when combined with llvm::DataLayout) how much space
|
||||
* will be required for this particular variable
|
||||
**/
|
||||
llvm::Type * llvm_type_ = nullptr;
|
||||
};
|
||||
|
||||
inline std::ostream &
|
||||
operator<<(std::ostream & os, const runtime_binding_detail & x) {
|
||||
os << "<runtime_binding_detail"
|
||||
<< xtag("i_argno", x.i_argno_)
|
||||
<< xtag("llvm_addr", (void*)x.llvm_addr_)
|
||||
<< xtag("llvm_type", (void*)x.llvm_type_)
|
||||
<< ">";
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. pattern for a stack frame associated with a user-defined function (some Lambda lm)
|
||||
*
|
||||
* each function needs its own IR builder, to keep track of things like insert point
|
||||
* 2. each function needs its own IR builder, to keep track of things like insert point
|
||||
*
|
||||
* 3. simple case first.
|
||||
* if lm->needs_closure_flag() is false, then:
|
||||
*
|
||||
* a. still need a closure-shaped object, because when we invoke function, we may
|
||||
* not know until runtime whether it relies on closure.
|
||||
* For such function we will generate a closure with empty environment pointer.
|
||||
* b. all formal parameters of lm
|
||||
* are used only in the layer associated with that lambda's body; in particular
|
||||
* they aren't free in any nested lambda
|
||||
* c. conversely, the top layer of lm's body has no free variables.
|
||||
* The only variables that *do* appear are lm's formal parameters.
|
||||
*
|
||||
* In this case, all of lm's formals will be allocated on the stack using regular
|
||||
* allocInst, and we don't need a closure for lm.
|
||||
*
|
||||
* 4. complex case second
|
||||
* If lm->needs_closure_flag() is true, then either:
|
||||
*
|
||||
* a. at least one formal parameter of lm appears free in some nested lambda.
|
||||
* b. lambda's top layer itself contains one or more free variables.
|
||||
*
|
||||
* In either case we will create an explicit environment for lm,
|
||||
* containing all the variables needed by some nested lambda
|
||||
**/
|
||||
class activation_record {
|
||||
public:
|
||||
activation_record() = default;
|
||||
using Lambda = xo::ast::Lambda;
|
||||
using TypeDescr = xo::reflect::TypeDescr;
|
||||
|
||||
llvm::AllocaInst * lookup_var(const std::string & var_name) const;
|
||||
public:
|
||||
activation_record(const ref::rp<Lambda> & lm);
|
||||
|
||||
llvm::AllocaInst * alloc_var(const std::string & var_name,
|
||||
llvm::AllocaInst * alloca);
|
||||
const ref::rp<Lambda> lambda() const { return lambda_; }
|
||||
|
||||
/** retrieve @c llvm::Value* representing the primary stack location
|
||||
* for formal parameter @p var_name
|
||||
**/
|
||||
const runtime_binding_detail * lookup_var(const std::string & var_name) const;
|
||||
|
||||
/** Remember allocation of a function variable on the stack
|
||||
*
|
||||
* @param var_name. formal parameter name
|
||||
* @param binding. address + supporting details for
|
||||
* primary (stack-allocated) storage for this variable
|
||||
**/
|
||||
const runtime_binding_detail * alloc_var(const std::string & var_name,
|
||||
const runtime_binding_detail & binding);
|
||||
|
||||
#ifdef NOT_USING
|
||||
llvm::AllocaInst * create_runtime_localenv_alloca(ref::brw<LlvmContext> llvm_cx,
|
||||
//const llvm::DataLayout & data_layout,
|
||||
llvm::Function * llvm_fn,
|
||||
llvm::IRBuilder<> & fn_ir_builder);
|
||||
#endif
|
||||
|
||||
runtime_binding_detail 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_td);
|
||||
|
||||
/** generate instructions that establish stacck location for a local-environment slot
|
||||
*
|
||||
* @param llvm_cx. handle for context -- manages storage for llvm::Types + related
|
||||
* @param localenv_llvm_type. describes contents of local environment
|
||||
* for a particular function. Same as @c localenv_alloca->getAllocatedType()
|
||||
* @param localenv_alloca. stack location for local environment
|
||||
* @param i_slot. 0-based slot number within local environment,
|
||||
* for which address is required
|
||||
* @param fn_ir_builder. insertion point for generated instructions
|
||||
* that compute target slot address (will be at/near top of function,
|
||||
* since we will copy captured function arguments to localenv,
|
||||
* then use the localenv copy exclusively.
|
||||
* @return value representing localenv slot address
|
||||
**/
|
||||
llvm::Value * runtime_localenv_slot_addr(ref::brw<LlvmContext> llvm_cx,
|
||||
llvm::StructType * localenv_llvm_type,
|
||||
llvm::AllocaInst * localenv_alloca,
|
||||
int i_slot,
|
||||
llvm::IRBuilder<> & fn_ir_builder);
|
||||
|
||||
/** establish storage for formal parameters on behalf of a new-but-empty
|
||||
* llvm function @p llvm_fn. Creates llvm IR instructions on function
|
||||
* entry that
|
||||
* 1. allocates stack space for function parameters.
|
||||
* 2. stores incoming parameters in that stack space.
|
||||
*
|
||||
* Strategy:
|
||||
* - for stackonly parameters, use individual @c llvm::AllocaInst instances
|
||||
* - create custom @c llvm::StructType for captured parameters, also initially stack-allocated
|
||||
**/
|
||||
bool bind_locals(ref::brw<LlvmContext> llvm_cx,
|
||||
//const llvm::DataLayout & data_layout,
|
||||
llvm::Function * llvm_fn,
|
||||
llvm::IRBuilder<> & ir_builder);
|
||||
|
||||
private:
|
||||
/** maps named slots in a stack frame to logical addresses **/
|
||||
std::map<std::string, llvm::AllocaInst*> frame_; /* <-> kaleidoscope NamedValues */
|
||||
/** this activation record created on behalf of a call to @ref lambda_.
|
||||
* @ref Variable::path_ specifies a logical path to a variable,
|
||||
* but does not distinguish stack-native variables from variables in explicit
|
||||
* runtime environment records.
|
||||
*
|
||||
**/
|
||||
ref::rp<Lambda> lambda_;
|
||||
|
||||
/** @c binding_v_[i] specifies how/where we mean to navigate to
|
||||
* location for formal parameter number *i* of @ref lambda_.
|
||||
**/
|
||||
std::vector<runtime_binding_path> binding_v_;
|
||||
|
||||
/** if this function requires an explicit environment,
|
||||
* gives stack location for that environment.
|
||||
**/
|
||||
llvm::AllocaInst * localenv_alloca_ = nullptr;
|
||||
|
||||
/** maps named slots in a stack frame to logical addresses.
|
||||
*
|
||||
* - For captured arguments: will refer to slot within stack-allocated local environment
|
||||
* (an llvm::StructType, created by type2llvm::create_localenv_llvm_type())
|
||||
*
|
||||
* - For non-captured arguments: will refer to stack-allocated argument copy
|
||||
*
|
||||
* In either case using copy-to-stack to evade directly confronting
|
||||
* so we don't have to comply with llvm IR's SSA requirement.
|
||||
**/
|
||||
std::map<std::string, runtime_binding_detail> frame_; /* <-> kaleidoscope NamedValues */
|
||||
}; /*activation_record*/
|
||||
|
||||
} /*namespace jit*/
|
||||
|
|
|
|||
225
include/xo/jit/type2llvm.hpp
Normal file
225
include/xo/jit/type2llvm.hpp
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/** @file type2llvm.hpp
|
||||
*
|
||||
* Author: Roland Conybeare
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "LlvmContext.hpp"
|
||||
#include "xo/expression/Lambda.hpp"
|
||||
#include "xo/reflect/TypeDescr.hpp"
|
||||
#include <llvm/IR/DerivedTypes.h>
|
||||
//#include <cstdint>
|
||||
|
||||
namespace xo {
|
||||
namespace jit {
|
||||
/**
|
||||
**/
|
||||
struct type2llvm {
|
||||
public:
|
||||
using FunctionInterface = xo::ast::FunctionInterface;
|
||||
using Lambda = xo::ast::Lambda;
|
||||
using TypeDescr = xo::reflect::TypeDescr;
|
||||
|
||||
public:
|
||||
/** establish suitable llvm representation for a c++ type (described by @p td)
|
||||
* llvm types are unique'd, at least within @p llvm_cx
|
||||
**/
|
||||
static llvm::Type * td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr td);
|
||||
|
||||
/** establish llvm representation for a function type
|
||||
* described by @p fn_td
|
||||
*
|
||||
* @param wrapper_flag If true, create function type for a wrapper
|
||||
* to be associated with a closure.
|
||||
* The wrapper accepts (and ignores) an envapi pointer as first argument.
|
||||
* Necessary to (for example) support function pointers that may refer
|
||||
* to either {primitive functions, functions-requiring-closures},
|
||||
* with choice deferred until runtime
|
||||
**/
|
||||
static llvm::FunctionType * function_td_to_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr fn_td,
|
||||
bool wrapper_flag = false);
|
||||
|
||||
/** establish llvm representation for a function-pointer type
|
||||
* described by @p fn_td
|
||||
*
|
||||
* @param wrapper_flag If true, create function type for a wrapper
|
||||
* to be associated with a closure.
|
||||
* The wrapper accepts (and ignores) an envapi pointer as first argument.
|
||||
* Necessary to (for example) support function pointers that may refer
|
||||
* to either {primitive functions, functions-requiring-closures},
|
||||
* with choice deferred until runtime
|
||||
**/
|
||||
static llvm::PointerType * function_td_to_llvm_fnptr_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr fn_td,
|
||||
bool wrapper_flag);
|
||||
|
||||
/** establish llvm concrete representation for a closure.
|
||||
*
|
||||
* +-------+
|
||||
* [0] | o-------> fnptr T (*)(envptr, ...)
|
||||
* +-------+
|
||||
* [1] | o-------\
|
||||
* +-------+ |
|
||||
* |
|
||||
* |
|
||||
* v
|
||||
* +-------+
|
||||
* parent_env [0] | o-------> _env_api*
|
||||
* +-------+
|
||||
* unwind_fn [1] | o-------> env * (*)(env*, ctl)
|
||||
* +-------+
|
||||
*
|
||||
* @return struct type. typename will be @c c.foo for a function
|
||||
* (primitive or lambda) with name @c foo
|
||||
**/
|
||||
static llvm::StructType *
|
||||
create_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
xo::ref::brw<FunctionInterface> fn);
|
||||
|
||||
/** establish llvm abstract representation for a closure:
|
||||
* struct with
|
||||
* - [0] function pointer
|
||||
* - [1] runtime localenv pointer
|
||||
*
|
||||
* +-------+
|
||||
* | o---------> native function
|
||||
* +-------+
|
||||
* | o---------> runtime localenv
|
||||
* +-------+ (possibly nullptr)
|
||||
*
|
||||
* 1. for primitives, localenv will be null pointer
|
||||
* 2. for lambdas L with L->requires_closure_flag() = false,
|
||||
* localenv will also be null pointer
|
||||
* 3. for lambdas with L->requires_closure_flag() = true,
|
||||
*
|
||||
* localenv will (for lambdas requiring closures)
|
||||
* in practice be struct:
|
||||
*
|
||||
* ^
|
||||
* | 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
|
||||
*
|
||||
* Implementation here will just use generic pointer for runtime
|
||||
* localenv.
|
||||
**/
|
||||
static llvm::StructType *
|
||||
function_td_to_closureapi_lvtype(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr fn_td,
|
||||
const std::string & hint_name);
|
||||
|
||||
/** establish llvm concrete representation for a particular lambda's
|
||||
* runtime local environment:
|
||||
*
|
||||
* ^
|
||||
* | 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
|
||||
*
|
||||
* @return struct type. typename will be @c e.foo for lambda with name @c foo
|
||||
**/
|
||||
static llvm::StructType *
|
||||
create_localenv_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
xo::ref::brw<Lambda> lambda);
|
||||
|
||||
/** establish llvm rep'n for a pointer to an abstract local environment:
|
||||
*
|
||||
* +-------+
|
||||
* | o-------------\
|
||||
* +-------+ |
|
||||
* |
|
||||
* |
|
||||
* |
|
||||
* v
|
||||
* +-------+
|
||||
* parent_env [0] | o-------> _env_api*
|
||||
* +-------+
|
||||
* unwind_fn [1] | o-------> env * (*)(env*, ctl)
|
||||
* +-------+
|
||||
**/
|
||||
static llvm::PointerType *
|
||||
env_api_llvm_ptr_type(xo::ref::brw<LlvmContext> llvm_cx);
|
||||
|
||||
/** function type:
|
||||
* @code
|
||||
* env_api_* (env_api* env, int ctl);
|
||||
* @endcode
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* returns function-pointer type
|
||||
**/
|
||||
static llvm::PointerType *
|
||||
require_localenv_unwind_llvm_fnptr_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
llvm::PointerType * hint_envptr_llvm_type = nullptr);
|
||||
|
||||
private:
|
||||
|
||||
/** establish llvm representation for a struct type described by @p struct_td
|
||||
**/
|
||||
static llvm::StructType * struct_td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr struct_td);
|
||||
|
||||
/** establish llvm representation for a pointer type described by @p pointer_td **/
|
||||
static llvm::PointerType * pointer_td_to_llvm_type(xo::ref::brw<LlvmContext> llvm_cx,
|
||||
TypeDescr pointer_td);
|
||||
|
||||
/** establish llvm abstract representation for a local environment:
|
||||
*
|
||||
* ^
|
||||
* | parent
|
||||
* +-------+ |
|
||||
* parent_env [0] | o-------/
|
||||
* +-------+
|
||||
* unwind_fn [1] | o-------> env * (*)(env*, ctl)
|
||||
* +-------+
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* Concrete implementation will probably occupy additional memory,
|
||||
* to store captured lambda variables.
|
||||
*
|
||||
* @see type2llvm::function_td_to_llvm_closure_type
|
||||
**/
|
||||
static llvm::StructType *
|
||||
env_api_llvm_type(xo::ref::brw<LlvmContext> llvm_cx);
|
||||
|
||||
}; /*type2llvm*/
|
||||
} /*namespace jit*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/** end type2llvm.hpp **/
|
||||
|
|
@ -7,6 +7,7 @@ set(SELF_SRCS
|
|||
MachPipeline.cpp
|
||||
intrinsics.cpp
|
||||
activation_record.cpp
|
||||
type2llvm.cpp
|
||||
)
|
||||
|
||||
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,7 @@
|
|||
/* @file activation_record.cpp */
|
||||
|
||||
#include "activation_record.hpp"
|
||||
#include "type2llvm.hpp"
|
||||
#include "xo/indentlog/print/tag.hpp"
|
||||
#include <iostream>
|
||||
|
||||
|
|
@ -9,8 +10,35 @@ namespace xo {
|
|||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
llvm::AllocaInst *
|
||||
activation_record::lookup_var(const std::string & x) const {
|
||||
activation_record::activation_record(const ref::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);
|
||||
|
||||
|
|
@ -18,16 +46,28 @@ namespace xo {
|
|||
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;
|
||||
return &(ix->second);
|
||||
} /*lookup_var*/
|
||||
|
||||
llvm::AllocaInst *
|
||||
const runtime_binding_detail *
|
||||
activation_record::alloc_var(const std::string & x,
|
||||
llvm::AllocaInst * alloca)
|
||||
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)
|
||||
|
|
@ -35,9 +75,359 @@ namespace xo {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
frame_[x] = alloca;
|
||||
return alloca;
|
||||
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*/
|
||||
|
||||
|
|
|
|||
406
src/jit/type2llvm.cpp
Normal file
406
src/jit/type2llvm.cpp
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
/* @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)
|
||||
{
|
||||
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 */
|
||||
|
|
@ -116,12 +116,19 @@ namespace xo {
|
|||
|
||||
//auto rng = xo::rng::xoshiro256ss(seed);
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline"));
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipeline.fptr"));
|
||||
//log && log("(A)", xtag("foo", foo));
|
||||
|
||||
auto jit = MachPipeline::make();
|
||||
|
||||
for (std::size_t i_tc = 0, n_tc = s_testcase_v.size(); i_tc < n_tc; ++i_tc) {
|
||||
/** can't share jit across examples,
|
||||
* until we fix treatment of primitives:
|
||||
* now that we build a wrapper for each primitive,
|
||||
* need some bookkeeping to avoid trying to build
|
||||
* the same wrapper twice.
|
||||
**/
|
||||
auto jit = MachPipeline::make();
|
||||
|
||||
TestCase const & testcase = s_testcase_v[i_tc];
|
||||
|
||||
INFO(tostr(xtag("i_tc", i_tc)));
|
||||
|
|
@ -183,7 +190,66 @@ namespace xo {
|
|||
REQUIRE(actual == expected);
|
||||
}
|
||||
}
|
||||
} /*TEST_CASE(machpipeline)*/
|
||||
} /*TEST_CASE(machpipeline.fptr)*/
|
||||
|
||||
TEST_CASE("machpipeline.wrap", "[llvm][llvm_closure]") {
|
||||
constexpr bool c_debug_flag = true;
|
||||
|
||||
scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.machpipelin.wrap"));
|
||||
|
||||
auto jit = MachPipeline::make();
|
||||
|
||||
auto root = make_primitive("sqrt",
|
||||
::sqrt,
|
||||
false /*!explicit_symbol_def*/,
|
||||
llvmintrinsic::fp_sqrt);
|
||||
|
||||
llvm::Value * llvm_ircode
|
||||
= jit->codegen_primitive_wrapper(root, *(jit->llvm_current_ir_builder()));
|
||||
|
||||
/* TODO: printer for llvm::Value* */
|
||||
if (llvm_ircode) {
|
||||
/* note: llvm:errs() is 'raw stderr stream' */
|
||||
cerr << "llvm_ircode for primitive wrapper:" << endl;
|
||||
llvm_ircode->print(llvm::errs());
|
||||
cerr << endl;
|
||||
} else {
|
||||
cerr << "code generation failed"
|
||||
<< xtag("root", root)
|
||||
<< endl;
|
||||
}
|
||||
|
||||
REQUIRE(llvm_ircode);
|
||||
|
||||
std::string wrapper_name = std::string("w.") + root->name();
|
||||
|
||||
jit->machgen_current_module();
|
||||
|
||||
auto llvm_addr = jit->lookup_symbol(wrapper_name);
|
||||
|
||||
bool llvm_addr_flag = static_cast<bool>(llvm_addr);
|
||||
|
||||
if (!llvm_addr_flag) {
|
||||
cerr << "ex2: lookup: symbol not found"
|
||||
<< xtag("symbol", wrapper_name)
|
||||
<< endl;
|
||||
} else {
|
||||
cerr << "ex2: lookup: symbol found"
|
||||
<< xtag("llvm_addr", llvm_addr.get().getValue())
|
||||
<< xtag("symbol", wrapper_name)
|
||||
<< endl;
|
||||
}
|
||||
|
||||
REQUIRE(llvm_addr_flag);
|
||||
|
||||
auto fn_ptr = llvm_addr.get().toPtr<double(*)(void*, double)>();
|
||||
|
||||
REQUIRE(fn_ptr);
|
||||
|
||||
auto actual = (*fn_ptr)(nullptr, 4.0);
|
||||
|
||||
REQUIRE(actual == 2.0);
|
||||
}
|
||||
|
||||
rp<Lambda>
|
||||
make_ratio() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue