Skip to content
Fetching contributors…
Cannot retrieve contributors at this time
7157 lines (5999 sloc) 185 KB
/*
* MacRuby compiler.
*
* This file is covered by the Ruby license. See COPYING for more details.
*
* Copyright (C) 2008-2009, Apple Inc. All rights reserved.
*/
#define ROXOR_COMPILER_DEBUG 0
#include "llvm.h"
#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "ruby/node.h"
#include "ruby/re.h"
#include "id.h"
#include "vm.h"
#include "compiler.h"
#include "objc.h"
extern "C" const char *ruby_node_name(int node);
// Will be set later, in vm.cpp.
llvm::Module *RoxorCompiler::module = NULL;
RoxorCompiler *RoxorCompiler::shared = NULL;
RoxorCompiler::RoxorCompiler(void)
{
fname = "";
inside_eval = false;
bb = NULL;
entry_bb = NULL;
begin_bb = NULL;
rescue_invoke_bb = NULL;
rescue_rethrow_bb = NULL;
ensure_bb = NULL;
current_mid = 0;
current_arity = rb_vm_arity(-1);
current_instance_method = false;
self_id = rb_intern("self");
current_self = NULL;
current_var_uses = NULL;
running_block = NULL;
current_block_arg = NULL;
current_block = false;
current_block_chain = false;
current_block_node = NULL;
current_block_func = NULL;
current_opened_class = NULL;
dynamic_class = false;
current_module = false;
current_loop_begin_bb = NULL;
current_loop_body_bb = NULL;
current_loop_end_bb = NULL;
current_loop_exit_val = NULL;
current_rescue = false;
return_from_block = -1;
return_from_block_ids = 0;
ensure_pn = NULL;
current_scope = NULL;
dispatcherFunc = NULL;
fastPlusFunc = NULL;
fastMinusFunc = NULL;
fastMultFunc = NULL;
fastDivFunc = NULL;
fastLtFunc = NULL;
fastLeFunc = NULL;
fastGtFunc = NULL;
fastGeFunc = NULL;
fastEqFunc = NULL;
fastNeqFunc = NULL;
fastEqqFunc = NULL;
whenSplatFunc = NULL;
prepareBlockFunc = NULL;
pushBindingFunc = NULL;
getBlockFunc = NULL;
currentBlockObjectFunc = NULL;
getConstFunc = NULL;
setConstFunc = NULL;
prepareMethodFunc = NULL;
singletonClassFunc = NULL;
defineClassFunc = NULL;
prepareIvarSlotFunc = NULL;
getIvarFunc = NULL;
setIvarFunc = NULL;
definedFunc = NULL;
undefFunc = NULL;
aliasFunc = NULL;
valiasFunc = NULL;
newHashFunc = NULL;
toAFunc = NULL;
toAryFunc = NULL;
catArrayFunc = NULL;
dupArrayFunc = NULL;
newArrayFunc = NULL;
newStructFunc = NULL;
newOpaqueFunc = NULL;
newPointerFunc = NULL;
getStructFieldsFunc = NULL;
getOpaqueDataFunc = NULL;
getPointerPtrFunc = NULL;
checkArityFunc = NULL;
setStructFunc = NULL;
newRangeFunc = NULL;
newRegexpFunc = NULL;
strInternFunc = NULL;
keepVarsFunc = NULL;
masgnGetElemBeforeSplatFunc = NULL;
masgnGetElemAfterSplatFunc = NULL;
masgnGetSplatFunc = NULL;
newStringFunc = NULL;
newString2Func = NULL;
newString3Func = NULL;
yieldFunc = NULL;
getBrokenFunc = NULL;
blockEvalFunc = NULL;
gvarSetFunc = NULL;
gvarGetFunc = NULL;
cvarSetFunc = NULL;
cvarGetFunc = NULL;
currentExceptionFunc = NULL;
popExceptionFunc = NULL;
getSpecialFunc = NULL;
breakFunc = NULL;
returnFromBlockFunc = NULL;
returnedFromBlockFunc = NULL;
checkReturnFromBlockFunc = NULL;
setHasEnsureFunc = NULL;
longjmpFunc = NULL;
setjmpFunc = NULL;
setScopeFunc = NULL;
setCurrentClassFunc = NULL;
getCacheFunc = NULL;
VoidTy = Type::getVoidTy(context);
Int1Ty = Type::getInt1Ty(context);
Int8Ty = Type::getInt8Ty(context);
Int16Ty = Type::getInt16Ty(context);
Int32Ty = Type::getInt32Ty(context);
Int64Ty = Type::getInt64Ty(context);
FloatTy = Type::getFloatTy(context);
DoubleTy = Type::getDoubleTy(context);
#if __LP64__
RubyObjTy = IntTy = Int64Ty;
#else
RubyObjTy = IntTy = Int32Ty;
#endif
zeroVal = ConstantInt::get(IntTy, 0);
oneVal = ConstantInt::get(IntTy, 1);
twoVal = ConstantInt::get(IntTy, 2);
threeVal = ConstantInt::get(IntTy, 3);
defaultScope = ConstantInt::get(Int32Ty, SCOPE_DEFAULT);
publicScope = ConstantInt::get(Int32Ty, SCOPE_PUBLIC);
RubyObjPtrTy = PointerType::getUnqual(RubyObjTy);
RubyObjPtrPtrTy = PointerType::getUnqual(RubyObjPtrTy);
nilVal = ConstantInt::get(RubyObjTy, Qnil);
trueVal = ConstantInt::get(RubyObjTy, Qtrue);
falseVal = ConstantInt::get(RubyObjTy, Qfalse);
undefVal = ConstantInt::get(RubyObjTy, Qundef);
splatArgFollowsVal = ConstantInt::get(RubyObjTy, SPLAT_ARG_FOLLOWS);
PtrTy = PointerType::getUnqual(Int8Ty);
PtrPtrTy = PointerType::getUnqual(PtrTy);
Int32PtrTy = PointerType::getUnqual(Int32Ty);
#if ROXOR_COMPILER_DEBUG
level = 0;
#endif
}
RoxorAOTCompiler::RoxorAOTCompiler(void)
: RoxorCompiler()
{
cObject_gvar = NULL;
cStandardError_gvar = NULL;
}
inline SEL
RoxorCompiler::mid_to_sel(ID mid, int arity)
{
SEL sel;
const char *mid_str = rb_id2name(mid);
if (mid_str[strlen(mid_str) - 1] != ':' && arity > 0) {
char buf[100];
snprintf(buf, sizeof buf, "%s:", mid_str);
sel = sel_registerName(buf);
}
else {
sel = sel_registerName(mid_str);
}
return sel;
}
inline bool
RoxorCompiler::unbox_ruby_constant(Value *val, VALUE *rval)
{
if (ConstantInt::classof(val)) {
long tmp = cast<ConstantInt>(val)->getZExtValue();
*rval = tmp;
return true;
}
return false;
}
inline ICmpInst *
RoxorCompiler::is_value_a_fixnum(Value *val)
{
Value *andOp = BinaryOperator::CreateAnd(val, oneVal, "", bb);
return new ICmpInst(*bb, ICmpInst::ICMP_EQ, andOp, oneVal);
}
Instruction *
RoxorCompiler::compile_protected_call(Value *imp, std::vector<Value *> &params)
{
if (rescue_invoke_bb == NULL) {
CallInst *dispatch = CallInst::Create(imp,
params.begin(),
params.end(),
"",
bb);
return dispatch;
}
else {
BasicBlock *normal_bb = BasicBlock::Create(context, "normal",
bb->getParent());
InvokeInst *dispatch = InvokeInst::Create(imp,
normal_bb,
rescue_invoke_bb,
params.begin(),
params.end(),
"",
bb);
bb = normal_bb;
return dispatch;
}
}
void
RoxorCompiler::compile_single_when_argument(NODE *arg, Value *comparedToVal,
BasicBlock *thenBB)
{
Value *subnodeVal = compile_node(arg);
Value *condVal;
if (comparedToVal != NULL) {
std::vector<Value *> params;
params.push_back(compile_mcache(selEqq, false));
params.push_back(current_self);
params.push_back(subnodeVal);
params.push_back(compile_sel(selEqq));
params.push_back(compile_const_pointer(NULL));
params.push_back(ConstantInt::get(Int8Ty, 0));
params.push_back(ConstantInt::get(Int32Ty, 1));
params.push_back(comparedToVal);
condVal = compile_optimized_dispatch_call(selEqq, 1, params);
if (condVal == NULL) {
condVal = compile_dispatch_call(params);
}
}
else {
condVal = subnodeVal;
}
Function *f = bb->getParent();
BasicBlock *nextTestBB = BasicBlock::Create(context, "next_test", f);
compile_boolean_test(condVal, thenBB, nextTestBB);
bb = nextTestBB;
}
void
RoxorCompiler::compile_boolean_test(Value *condVal, BasicBlock *ifTrueBB,
BasicBlock *ifFalseBB)
{
Function *f = bb->getParent();
BasicBlock *notFalseBB = BasicBlock::Create(context, "not_false", f);
Value *notFalseCond = new ICmpInst(*bb, ICmpInst::ICMP_NE, condVal,
falseVal);
BranchInst::Create(notFalseBB, ifFalseBB, notFalseCond, bb);
Value *notNilCond = new ICmpInst(*notFalseBB, ICmpInst::ICMP_NE, condVal,
nilVal);
BranchInst::Create(ifTrueBB, ifFalseBB, notNilCond, notFalseBB);
}
void
RoxorCompiler::compile_when_arguments(NODE *args, Value *comparedToVal,
BasicBlock *thenBB)
{
switch (nd_type(args)) {
case NODE_ARRAY:
while (args != NULL) {
compile_single_when_argument(args->nd_head, comparedToVal,
thenBB);
args = args->nd_next;
}
break;
case NODE_SPLAT:
{
Value *condVal = compile_when_splat(comparedToVal,
compile_node(args->nd_head));
BasicBlock *nextTestBB = BasicBlock::Create(context,
"next_test", bb->getParent());
compile_boolean_test(condVal, thenBB, nextTestBB);
bb = nextTestBB;
}
break;
case NODE_ARGSPUSH:
case NODE_ARGSCAT:
compile_when_arguments(args->nd_head, comparedToVal, thenBB);
compile_single_when_argument(args->nd_body, comparedToVal, thenBB);
break;
default:
compile_node_error("unrecognized when arg node", args);
}
}
Function::ArgumentListType::iterator
RoxorCompiler::compile_optional_arguments(
Function::ArgumentListType::iterator iter, NODE *node)
{
assert(nd_type(node) == NODE_OPT_ARG);
do {
assert(node->nd_value != NULL);
Value *isUndefInst = new ICmpInst(*bb, ICmpInst::ICMP_EQ, iter,
undefVal);
Function *f = bb->getParent();
BasicBlock *arg_undef = BasicBlock::Create(context, "arg_undef", f);
BasicBlock *next_bb = BasicBlock::Create(context, "", f);
BranchInst::Create(arg_undef, next_bb, isUndefInst, bb);
bb = arg_undef;
compile_node(node->nd_value);
BranchInst::Create(next_bb, bb);
bb = next_bb;
++iter;
}
while ((node = node->nd_next) != NULL);
return iter;
}
void
RoxorCompiler::compile_dispatch_arguments(NODE *args,
std::vector<Value *> &arguments, int *pargc)
{
int argc = 0;
switch (nd_type(args)) {
case NODE_ARRAY:
for (NODE *n = args; n != NULL; n = n->nd_next) {
arguments.push_back(compile_node(n->nd_head));
argc++;
}
break;
case NODE_SPLAT:
assert(args->nd_head != NULL);
arguments.push_back(splatArgFollowsVal);
arguments.push_back(compile_node(args->nd_head));
argc++;
break;
case NODE_ARGSCAT:
assert(args->nd_head != NULL);
compile_dispatch_arguments(args->nd_head, arguments, &argc);
assert(args->nd_body != NULL);
arguments.push_back(splatArgFollowsVal);
arguments.push_back(compile_node(args->nd_body));
argc++;
break;
case NODE_ARGSPUSH:
assert(args->nd_head != NULL);
compile_dispatch_arguments(args->nd_head, arguments, &argc);
assert(args->nd_body != NULL);
arguments.push_back(compile_node(args->nd_body));
argc++;
break;
default:
compile_node_error("unrecognized dispatch arg node", args);
}
*pargc = argc;
}
Value *
RoxorCompiler::compile_fast_op_call(SEL sel, Value *selfVal, Value *otherVal)
{
Function *func = NULL;
// VALUE rb_vm_fast_op(struct mcache *cache, VALUE left, VALUE right);
#define fast_op(storage, name) \
do { \
if (storage == NULL) { \
storage = cast<Function>(module->getOrInsertFunction(name, \
RubyObjTy, PtrTy, RubyObjTy, RubyObjTy, NULL)); \
} \
func = storage; \
} \
while (0)
if (sel == selPLUS) {
fast_op(fastPlusFunc, "rb_vm_fast_plus");
}
else if (sel == selMINUS) {
fast_op(fastMinusFunc, "rb_vm_fast_minus");
}
else if (sel == selDIV) {
fast_op(fastDivFunc, "rb_vm_fast_div");
}
else if (sel == selMULT) {
fast_op(fastMultFunc, "rb_vm_fast_mult");
}
else if (sel == selLT) {
fast_op(fastLtFunc, "rb_vm_fast_lt");
}
else if (sel == selLE) {
fast_op(fastLeFunc, "rb_vm_fast_le");
}
else if (sel == selGT) {
fast_op(fastGtFunc, "rb_vm_fast_gt");
}
else if (sel == selGE) {
fast_op(fastGeFunc, "rb_vm_fast_ge");
}
else if (sel == selEq) {
fast_op(fastEqFunc, "rb_vm_fast_eq");
}
else if (sel == selNeq) {
fast_op(fastNeqFunc, "rb_vm_fast_neq");
}
else if (sel == selEqq) {
fast_op(fastEqqFunc, "rb_vm_fast_eqq");
}
else {
return NULL;
}
std::vector<Value *> params;
params.push_back(compile_mcache(sel, false));
params.push_back(selfVal);
params.push_back(otherVal);
return compile_protected_call(func, params);
}
Value *
RoxorCompiler::compile_when_splat(Value *comparedToVal, Value *splatVal)
{
if (whenSplatFunc == NULL) {
// VALUE rb_vm_when_splat(struct mcache *cache,
// unsigned char overriden,
// VALUE comparedTo, VALUE splat)
whenSplatFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_when_splat",
RubyObjTy, PtrTy, Int1Ty,
RubyObjTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(compile_mcache(selEqq, false));
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(selEqq, true);
params.push_back(new LoadInst(is_redefined, "", bb));
params.push_back(comparedToVal);
params.push_back(splatVal);
return compile_protected_call(whenSplatFunc, params);
}
GlobalVariable *
RoxorCompiler::compile_const_global_ustring(const UniChar *str,
const size_t len, CFHashCode hash)
{
assert(len > 0);
std::map<CFHashCode, GlobalVariable *>::iterator iter =
static_ustrings.find(hash);
GlobalVariable *gvar;
if (iter == static_ustrings.end()) {
const ArrayType *str_type = ArrayType::get(Int16Ty, len);
std::vector<Constant *> ary_elements;
for (unsigned int i = 0; i < len; i++) {
ary_elements.push_back(ConstantInt::get(Int16Ty, str[i]));
}
gvar = new GlobalVariable(*RoxorCompiler::module, str_type, true,
GlobalValue::InternalLinkage,
ConstantArray::get(str_type, ary_elements), "");
static_ustrings[hash] = gvar;
}
else {
gvar = iter->second;
}
return gvar;
}
GlobalVariable *
RoxorCompiler::compile_const_global_string(const char *str,
const size_t len)
{
assert(len > 0);
std::string s(str, len);
std::map<std::string, GlobalVariable *>::iterator iter =
static_strings.find(s);
GlobalVariable *gvar;
if (iter == static_strings.end()) {
const ArrayType *str_type = ArrayType::get(Int8Ty, len + 1);
std::vector<Constant *> ary_elements;
for (unsigned int i = 0; i < len; i++) {
ary_elements.push_back(ConstantInt::get(Int8Ty, str[i]));
}
ary_elements.push_back(ConstantInt::get(Int8Ty, 0));
gvar = new GlobalVariable(*RoxorCompiler::module, str_type, true,
GlobalValue::InternalLinkage,
ConstantArray::get(str_type, ary_elements), "");
static_strings[s] = gvar;
}
else {
gvar = iter->second;
}
return gvar;
}
Value *
RoxorCompiler::compile_get_mcache(Value *sel, bool super)
{
if (getCacheFunc == NULL) {
// void *rb_vm_get_call_cache2(SEL sel, unsigned char super);
getCacheFunc =
cast<Function>(module->getOrInsertFunction(
"rb_vm_get_call_cache2", PtrTy, PtrTy, Int8Ty,
NULL));
}
std::vector<Value *> params;
params.push_back(sel);
params.push_back(ConstantInt::get(Int8Ty, super ? 1 : 0));
return CallInst::Create(getCacheFunc, params.begin(), params.end(), "", bb);
}
Value *
RoxorCompiler::compile_mcache(SEL sel, bool super)
{
struct mcache *cache = GET_CORE()->method_cache_get(sel, super);
return compile_const_pointer(cache);
}
Value *
RoxorAOTCompiler::compile_mcache(SEL sel, bool super)
{
if (super) {
char buf[100];
snprintf(buf, sizeof buf, "__super__:%s", sel_getName(sel));
sel = sel_registerName(buf);
}
GlobalVariable *gvar;
std::map<SEL, GlobalVariable *>::iterator iter = mcaches.find(sel);
if (iter == mcaches.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, PtrTy, false,
GlobalValue::InternalLinkage, Constant::getNullValue(PtrTy),
"");
assert(gvar != NULL);
mcaches[sel] = gvar;
}
else {
gvar = iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorCompiler::compile_ccache(ID name)
{
struct ccache *cache = GET_CORE()->constant_cache_get(name);
return compile_const_pointer(cache);
}
Value *
RoxorAOTCompiler::compile_ccache(ID name)
{
std::map<ID, GlobalVariable *>::iterator iter =
ccaches.find(name);
GlobalVariable *gvar;
if (iter == ccaches.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, PtrTy, false,
GlobalValue::InternalLinkage, Constant::getNullValue(PtrTy),
"");
assert(gvar != NULL);
ccaches[name] = gvar;
}
else {
gvar = iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorAOTCompiler::compile_sel(SEL sel, bool add_to_bb)
{
std::map<SEL, GlobalVariable *>::iterator iter = sels.find(sel);
GlobalVariable *gvar;
if (iter == sels.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, PtrTy, false,
GlobalValue::InternalLinkage, Constant::getNullValue(PtrTy),
"");
assert(gvar != NULL);
sels[sel] = gvar;
}
else {
gvar = iter->second;
}
return add_to_bb
? new LoadInst(gvar, "", bb)
: new LoadInst(gvar, "");
}
inline Value *
RoxorCompiler::compile_arity(rb_vm_arity_t &arity)
{
uint64_t v;
assert(sizeof(uint64_t) == sizeof(rb_vm_arity_t));
memcpy(&v, &arity, sizeof(rb_vm_arity_t));
return ConstantInt::get(Int64Ty, v);
}
void
RoxorCompiler::compile_prepare_method(Value *classVal, Value *sel,
bool singleton, Function *new_function, rb_vm_arity_t &arity, NODE *body)
{
if (prepareMethodFunc == NULL) {
// void rb_vm_prepare_method(Class klass, unsigned char dynamic_class,
// SEL sel, Function *func, rb_vm_arity_t arity, int flags)
prepareMethodFunc =
cast<Function>(module->getOrInsertFunction(
"rb_vm_prepare_method",
VoidTy, RubyObjTy, Int8Ty, PtrTy, PtrTy, Int64Ty,
Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(classVal);
params.push_back(ConstantInt::get(Int8Ty,
!singleton && dynamic_class ? 1 : 0));
params.push_back(sel);
params.push_back(compile_const_pointer(new_function));
params.push_back(compile_arity(arity));
params.push_back(ConstantInt::get(Int32Ty, rb_vm_node_flags(body)));
CallInst::Create(prepareMethodFunc, params.begin(),
params.end(), "", bb);
}
void
RoxorAOTCompiler::compile_prepare_method(Value *classVal, Value *sel,
bool singleton, Function *new_function, rb_vm_arity_t &arity, NODE *body)
{
if (prepareMethodFunc == NULL) {
// void rb_vm_prepare_method2(Class klass, unsigned char dynamic_class,
// SEL sel, IMP ruby_imp, rb_vm_arity_t arity, int flags)
prepareMethodFunc =
cast<Function>(module->getOrInsertFunction(
"rb_vm_prepare_method2",
VoidTy, RubyObjTy, Int8Ty, PtrTy, PtrTy, Int64Ty,
Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(classVal);
params.push_back(ConstantInt::get(Int8Ty,
!singleton && dynamic_class ? 1 : 0));
params.push_back(sel);
// Make sure the function is compiled before use, this way LLVM won't use
// a stub.
GET_CORE()->compile(new_function);
params.push_back(new BitCastInst(new_function, PtrTy, "", bb));
params.push_back(compile_arity(arity));
params.push_back(ConstantInt::get(Int32Ty, rb_vm_node_flags(body)));
CallInst::Create(prepareMethodFunc, params.begin(),
params.end(), "", bb);
}
Value *
RoxorCompiler::compile_dispatch_call(std::vector<Value *> &params)
{
if (dispatcherFunc == NULL) {
// VALUE rb_vm_dispatch(struct mcache *cache, VALUE top, VALUE self,
// SEL sel, void *block, unsigned char opt, int argc, ...);
std::vector<const Type *> types;
types.push_back(PtrTy);
types.push_back(RubyObjTy);
types.push_back(RubyObjTy);
types.push_back(PtrTy);
types.push_back(PtrTy);
types.push_back(Int8Ty);
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(RubyObjTy, types, true);
dispatcherFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_dispatch", ft));
}
assert(current_scope != NULL);
current_scope->dispatch_lines.push_back(current_line);
return compile_protected_call(dispatcherFunc, params);
}
Value *
RoxorCompiler::compile_attribute_assign(NODE *node, Value *extra_val)
{
Value *recv = node->nd_recv == (NODE *)1
? current_self
: compile_node(node->nd_recv);
ID mid = node->nd_mid;
assert(mid > 0);
std::vector<Value *> args;
NODE *n = node->nd_args;
int argc = 0;
if (n != NULL) {
compile_dispatch_arguments(n, args, &argc);
}
if (extra_val != NULL) {
args.push_back(extra_val);
argc++;
}
if (mid != idASET) {
// A regular attribute assignment (obj.foo = 42)
assert(argc == 1);
}
std::vector<Value *> params;
const SEL sel = mid_to_sel(mid, argc);
params.push_back(compile_mcache(sel, false));
params.push_back(current_self);
params.push_back(recv);
params.push_back(compile_sel(sel));
params.push_back(compile_const_pointer(NULL));
unsigned char opt = 0;
if (recv == current_self) {
opt = DISPATCH_SELF_ATTRASGN;
}
params.push_back(ConstantInt::get(Int8Ty, opt));
params.push_back(ConstantInt::get(Int32Ty, argc));
for (std::vector<Value *>::iterator i = args.begin();
i != args.end();
++i) {
params.push_back(*i);
}
if (compile_optimized_dispatch_call(sel, argc, params) == NULL) {
compile_dispatch_call(params);
}
// The return value of these assignments is always the new value.
return params.back();
}
void
RoxorCompiler::compile_multiple_assignment_element(NODE *node, Value *val)
{
switch (nd_type(node)) {
case NODE_LASGN:
case NODE_DASGN:
case NODE_DASGN_CURR:
{
Value *slot = compile_lvar_slot(node->nd_vid);
new StoreInst(val, slot, bb);
}
break;
case NODE_IASGN:
case NODE_IASGN2:
compile_ivar_assignment(node->nd_vid, val);
break;
case NODE_CVASGN:
compile_cvar_assignment(node->nd_vid, val);
break;
case NODE_GASGN:
compile_gvar_assignment(node, val);
break;
case NODE_ATTRASGN:
compile_attribute_assign(node, val);
break;
case NODE_MASGN:
compile_multiple_assignment(node, val);
break;
case NODE_CDECL:
compile_constant_declaration(node, val);
break;
default:
compile_node_error("unimplemented MASGN subnode",
node);
}
}
Value *
RoxorCompiler::compile_multiple_assignment(NODE *node, Value *val)
{
assert(nd_type(node) == NODE_MASGN);
if (toAryFunc == NULL) {
// VALUE rb_vm_to_ary(VALUE ary);
toAryFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_to_ary",
RubyObjTy, RubyObjTy, NULL));
}
if (masgnGetElemBeforeSplatFunc == NULL) {
// VALUE rb_vm_masgn_get_elem_before_splat(VALUE ary, int offset);
masgnGetElemBeforeSplatFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_masgn_get_elem_before_splat",
RubyObjTy, RubyObjTy, Int32Ty, NULL));
}
if (masgnGetElemAfterSplatFunc == NULL) {
// VALUE rb_vm_masgn_get_elem_after_splat(VALUE ary, int before_splat_count, int after_splat_count, int offset);
masgnGetElemAfterSplatFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_masgn_get_elem_after_splat",
RubyObjTy, RubyObjTy, Int32Ty, Int32Ty, Int32Ty, NULL));
}
if (masgnGetSplatFunc == NULL) {
// VALUE rb_vm_masgn_get_splat(VALUE ary, int before_splat_count, int after_splat_count);
masgnGetSplatFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_masgn_get_splat",
RubyObjTy, RubyObjTy, Int32Ty, Int32Ty, NULL));
}
NODE *before_splat = node->nd_head, *after_splat = NULL, *splat = NULL;
assert((before_splat == NULL) || (nd_type(before_splat) == NODE_ARRAY));
// if the splat has no name (a, *, b = 1, 2, 3), its node value is -1
if ((node->nd_next == (NODE *)-1) || (node->nd_next == NULL) || (nd_type(node->nd_next) != NODE_POSTARG)) {
splat = node->nd_next;
}
else {
NODE *post_arg = node->nd_next;
splat = post_arg->nd_1st;
after_splat = post_arg->nd_2nd;
}
assert((after_splat == NULL) || (nd_type(after_splat) == NODE_ARRAY));
int before_splat_count = 0, after_splat_count = 0;
for (NODE *l = before_splat; l != NULL; l = l->nd_next) {
++before_splat_count;
}
for (NODE *l = after_splat; l != NULL; l = l->nd_next) {
++after_splat_count;
}
{
std::vector<Value *> params;
params.push_back(val);
val = CallInst::Create(toAryFunc, params.begin(),
params.end(), "", bb);
}
NODE *l = before_splat;
for (int i = 0; l != NULL; ++i) {
std::vector<Value *> params;
params.push_back(val);
params.push_back(ConstantInt::get(Int32Ty, i));
Value *elt = CallInst::Create(masgnGetElemBeforeSplatFunc, params.begin(),
params.end(), "", bb);
compile_multiple_assignment_element(l->nd_head, elt);
l = l->nd_next;
}
if (splat != NULL && splat != (NODE *)-1) {
std::vector<Value *> params;
params.push_back(val);
params.push_back(ConstantInt::get(Int32Ty, before_splat_count));
params.push_back(ConstantInt::get(Int32Ty, after_splat_count));
Value *elt = CallInst::Create(masgnGetSplatFunc, params.begin(),
params.end(), "", bb);
compile_multiple_assignment_element(splat, elt);
}
l = after_splat;
for (int i = 0; l != NULL; ++i) {
std::vector<Value *> params;
params.push_back(val);
params.push_back(ConstantInt::get(Int32Ty, before_splat_count));
params.push_back(ConstantInt::get(Int32Ty, after_splat_count));
params.push_back(ConstantInt::get(Int32Ty, i));
Value *elt = CallInst::Create(masgnGetElemAfterSplatFunc, params.begin(),
params.end(), "", bb);
compile_multiple_assignment_element(l->nd_head, elt);
l = l->nd_next;
}
return val;
}
Value *
RoxorCompiler::compile_prepare_block_args(Function *func, int *flags)
{
return compile_const_pointer(func);
}
Value *
RoxorAOTCompiler::compile_prepare_block_args(Function *func, int *flags)
{
*flags |= VM_BLOCK_AOT;
// Force compilation (no stub).
GET_CORE()->compile(func);
return new BitCastInst(func, PtrTy, "", bb);
}
Value *
RoxorCompiler::compile_block_get(Value *block_object)
{
if (getBlockFunc == NULL) {
// void *rb_vm_get_block(VALUE obj);
getBlockFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_get_block",
PtrTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(block_object);
return compile_protected_call(getBlockFunc, params);
}
Value *
RoxorCompiler::compile_block_create(void)
{
assert(current_block_func != NULL && current_block_node != NULL);
if (prepareBlockFunc == NULL) {
// void *rb_vm_prepare_block(Function *func, int flags, VALUE self,
// rb_vm_arity_t arity,
// rb_vm_var_uses **parent_var_uses,
// rb_vm_block_t *parent_block,
// int dvars_size, ...);
std::vector<const Type *> types;
types.push_back(PtrTy);
types.push_back(Int32Ty);
types.push_back(RubyObjTy);
types.push_back(Int64Ty);
types.push_back(PtrPtrTy);
types.push_back(PtrTy);
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(PtrTy, types, true);
prepareBlockFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_prepare_block", ft));
}
std::vector<Value *> params;
int flags = 0;
params.push_back(compile_prepare_block_args(current_block_func, &flags));
if (nd_type(current_block_node) == NODE_SCOPE
&& current_block_node->nd_body == NULL) {
flags |= VM_BLOCK_EMPTY;
}
params.push_back(ConstantInt::get(Int32Ty, flags));
params.push_back(current_self);
rb_vm_arity_t arity = rb_vm_node_arity(current_block_node);
params.push_back(compile_arity(arity));
params.push_back(current_var_uses == NULL
? compile_const_pointer_to_pointer(NULL) : current_var_uses);
params.push_back(running_block == NULL
? compile_const_pointer(NULL) : running_block);
// Dvars.
params.push_back(ConstantInt::get(Int32Ty, (int)dvars.size()));
for (std::vector<ID>::iterator iter = dvars.begin();
iter != dvars.end(); ++iter) {
params.push_back(compile_lvar_slot(*iter));
}
// Lvars.
params.push_back(ConstantInt::get(Int32Ty, (int)lvars.size()));
for (std::map<ID, Value *>::iterator iter = lvars.begin();
iter != lvars.end(); ++iter) {
ID name = iter->first;
Value *slot = iter->second;
params.push_back(compile_id((long)name));
params.push_back(slot);
}
return CallInst::Create(prepareBlockFunc, params.begin(), params.end(),
"", bb);
}
Value *
RoxorCompiler::compile_binding(void)
{
if (pushBindingFunc == NULL) {
// void rb_vm_push_binding(VALUE self, rb_vm_block_t *current_block,
// rb_vm_var_uses **parent_var_uses,
// int lvars_size, ...);
std::vector<const Type *> types;
types.push_back(RubyObjTy);
types.push_back(PtrTy);
types.push_back(PtrPtrTy);
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(VoidTy, types, true);
pushBindingFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_push_binding", ft));
}
std::vector<Value *> params;
params.push_back(current_self);
params.push_back(running_block == NULL
? compile_const_pointer(NULL) : running_block);
if (current_var_uses == NULL) {
// there is no local variables in this scope
params.push_back(compile_const_pointer_to_pointer(NULL));
}
else {
params.push_back(current_var_uses);
}
// Lvars.
params.push_back(ConstantInt::get(Int32Ty, (int)lvars.size()));
for (std::map<ID, Value *>::iterator iter = lvars.begin();
iter != lvars.end(); ++iter) {
ID lvar = iter->first;
params.push_back(compile_id(lvar));
params.push_back(iter->second);
}
return CallInst::Create(pushBindingFunc, params.begin(), params.end(),
"", bb);
}
Value *
RoxorCompiler::gen_slot_cache(ID id)
{
int *slot = (int *)malloc(sizeof(int));
*slot = -1;
return compile_const_pointer(slot, Int32PtrTy);
}
Value *
RoxorAOTCompiler::gen_slot_cache(ID id)
{
GlobalVariable *gvar = new GlobalVariable(*RoxorCompiler::module,
Int32PtrTy, false, GlobalValue::InternalLinkage,
Constant::getNullValue(Int32PtrTy), "");
ivar_slots.push_back(gvar);
return new LoadInst(gvar, "");
}
Value *
RoxorCompiler::compile_slot_cache(ID id)
{
if (inside_eval || current_block || !current_instance_method
|| current_module) {
return compile_const_pointer(NULL, Int32PtrTy);
}
std::map<ID, Value *>::iterator iter = ivar_slots_cache.find(id);
Value *slot;
if (iter == ivar_slots_cache.end()) {
#if ROXOR_COMPILER_DEBUG
printf("allocating a new slot for ivar %s\n", rb_id2name(id));
#endif
slot = gen_slot_cache(id);
ivar_slots_cache[id] = slot;
}
else {
slot = iter->second;
}
Instruction *slot_insn = dyn_cast<Instruction>(slot);
if (slot_insn != NULL) {
Instruction *insn = slot_insn->clone(context);
BasicBlock::InstListType &list = bb->getInstList();
list.insert(list.end(), insn);
return insn;
}
else {
return slot;
}
}
Value *
RoxorCompiler::compile_ivar_read(ID vid)
{
if (getIvarFunc == NULL) {
// VALUE rb_vm_ivar_get(VALUE obj, ID name, int *slot_cache);
getIvarFunc = cast<Function>(module->getOrInsertFunction("rb_vm_ivar_get",
RubyObjTy, RubyObjTy, IntTy, Int32PtrTy, NULL));
}
std::vector<Value *> params;
params.push_back(current_self);
params.push_back(compile_id(vid));
params.push_back(compile_slot_cache(vid));
return CallInst::Create(getIvarFunc, params.begin(), params.end(), "", bb);
}
Value *
RoxorCompiler::compile_ivar_assignment(ID vid, Value *val)
{
if (setIvarFunc == NULL) {
// void rb_vm_ivar_set(VALUE obj, ID name, VALUE val, int *slot_cache);
setIvarFunc =
cast<Function>(module->getOrInsertFunction("rb_vm_ivar_set",
VoidTy, RubyObjTy, IntTy, RubyObjTy, Int32PtrTy,
NULL));
}
std::vector<Value *> params;
params.push_back(current_self);
params.push_back(compile_id(vid));
params.push_back(val);
params.push_back(compile_slot_cache(vid));
CallInst::Create(setIvarFunc, params.begin(), params.end(), "", bb);
return val;
}
Value *
RoxorCompiler::compile_cvar_get(ID id, bool check)
{
if (cvarGetFunc == NULL) {
// VALUE rb_vm_cvar_get(VALUE klass, ID id, unsigned char check,
// unsigned char dynamic_class);
cvarGetFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_cvar_get",
RubyObjTy, RubyObjTy, IntTy, Int8Ty, Int8Ty, NULL));
}
std::vector<Value *> params;
params.push_back(compile_current_class());
params.push_back(compile_id(id));
params.push_back(ConstantInt::get(Int8Ty, check ? 1 : 0));
params.push_back(ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0));
return compile_protected_call(cvarGetFunc, params);
}
Value *
RoxorCompiler::compile_cvar_assignment(ID name, Value *val)
{
if (cvarSetFunc == NULL) {
// VALUE rb_vm_cvar_set(VALUE klass, ID id, VALUE val,
// unsigned char dynamic_class);
cvarSetFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_cvar_set",
RubyObjTy, RubyObjTy, IntTy, RubyObjTy, Int8Ty, NULL));
}
std::vector<Value *> params;
params.push_back(compile_current_class());
params.push_back(compile_id(name));
params.push_back(val);
params.push_back(ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0));
return CallInst::Create(cvarSetFunc, params.begin(),
params.end(), "", bb);
}
Value *
RoxorCompiler::compile_gvar_assignment(NODE *node, Value *val)
{
if (gvarSetFunc == NULL) {
// VALUE rb_gvar_set(struct global_entry *entry, VALUE val);
gvarSetFunc = cast<Function>(module->getOrInsertFunction(
"rb_gvar_set",
RubyObjTy, PtrTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(compile_global_entry(node));
params.push_back(val);
return compile_protected_call(gvarSetFunc, params);
}
Value *
RoxorCompiler::compile_constant_declaration(NODE *node, Value *val)
{
if (setConstFunc == NULL) {
// VALUE rb_vm_set_const(VALUE mod, ID id, VALUE obj,
// unsigned char dynamic_class);
setConstFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_set_const",
VoidTy, RubyObjTy, IntTy, RubyObjTy, Int8Ty,
NULL));
}
std::vector<Value *> params;
bool outer = true;
if (node->nd_vid > 0) {
params.push_back(compile_current_class());
params.push_back(compile_id(node->nd_vid));
}
else {
assert(node->nd_else != NULL);
params.push_back(compile_class_path(node->nd_else, &outer));
assert(node->nd_else->nd_mid > 0);
params.push_back(compile_id(node->nd_else->nd_mid));
}
params.push_back(val);
params.push_back(ConstantInt::get(Int8Ty,
dynamic_class && outer ? 1 : 0));
CallInst::Create(setConstFunc, params.begin(), params.end(), "", bb);
return val;
}
Value *
RoxorCompiler::compile_current_class(void)
{
if (current_opened_class == NULL) {
VALUE current_class = (VALUE)GET_VM()->get_current_class();
return current_class == 0
? compile_nsobject() : compile_literal(current_class);
}
return new LoadInst(current_opened_class, "", bb);
}
inline Value *
RoxorCompiler::compile_nsobject(void)
{
return ConstantInt::get(RubyObjTy, rb_cObject);
}
inline Value *
RoxorAOTCompiler::compile_nsobject(void)
{
if (cObject_gvar == NULL) {
cObject_gvar = new GlobalVariable(*RoxorCompiler::module, RubyObjTy,
false, GlobalValue::InternalLinkage, zeroVal, "NSObject");
class_gvars.push_back(cObject_gvar);
}
return new LoadInst(cObject_gvar, "", bb);
}
inline Value *
RoxorCompiler::compile_standarderror(void)
{
return ConstantInt::get(RubyObjTy, rb_eStandardError);
}
inline Value *
RoxorAOTCompiler::compile_standarderror(void)
{
if (cStandardError_gvar == NULL) {
cStandardError_gvar = new GlobalVariable(*RoxorCompiler::module,
RubyObjTy, false, GlobalValue::InternalLinkage, zeroVal,
"StandardError");
class_gvars.push_back(cStandardError_gvar);
}
return new LoadInst(cStandardError_gvar, "", bb);
}
inline Value *
RoxorCompiler::compile_id(ID id)
{
return ConstantInt::get(IntTy, (long)id);
}
Value *
RoxorAOTCompiler::compile_id(ID id)
{
std::map<ID, GlobalVariable *>::iterator iter = ids.find(id);
GlobalVariable *gvar;
if (iter == ids.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, IntTy, false,
GlobalValue::InternalLinkage, ConstantInt::get(IntTy, 0), "");
ids[id] = gvar;
}
else {
gvar = iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorCompiler::compile_const(ID id, Value *outer)
{
bool outer_given = true;
if (outer == NULL) {
outer = compile_current_class();
outer_given = false;
}
if (getConstFunc == NULL) {
// VALUE rb_vm_get_const(VALUE mod, unsigned char lexical_lookup,
// struct ccache *cache, ID id, unsigned char dynamic_class);
getConstFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_get_const",
RubyObjTy, RubyObjTy, Int8Ty, PtrTy, IntTy, Int8Ty,
NULL));
}
std::vector<Value *> params;
params.push_back(outer);
params.push_back(ConstantInt::get(Int8Ty, outer_given ? 0 : 1));
params.push_back(compile_ccache(id));
params.push_back(compile_id(id));
params.push_back(ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0));
return compile_protected_call(getConstFunc, params);
}
Value *
RoxorCompiler::compile_singleton_class(Value *obj)
{
if (singletonClassFunc == NULL) {
// VALUE rb_singleton_class(VALUE klass);
singletonClassFunc = cast<Function>(module->getOrInsertFunction(
"rb_singleton_class",
RubyObjTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(obj);
return compile_protected_call(singletonClassFunc, params);
}
Value *
RoxorCompiler::compile_defined_expression(NODE *node)
{
// Easy cases first.
VALUE str = 0;
switch (nd_type(node)) {
case NODE_NIL:
str = (VALUE)CFSTR("nil");
break;
case NODE_SELF:
str = (VALUE)CFSTR("self");
break;
case NODE_TRUE:
str = (VALUE)CFSTR("true");
break;
case NODE_FALSE:
str = (VALUE)CFSTR("false");
break;
case NODE_ARRAY:
case NODE_ZARRAY:
case NODE_STR:
case NODE_LIT:
str = (VALUE)CFSTR("expression");
break;
case NODE_LVAR:
case NODE_DVAR:
str = (VALUE)CFSTR("local-variable");
break;
case NODE_OP_ASGN1:
case NODE_OP_ASGN2:
case NODE_OP_ASGN_OR:
case NODE_OP_ASGN_AND:
case NODE_MASGN:
case NODE_LASGN:
case NODE_DASGN:
case NODE_DASGN_CURR:
case NODE_GASGN:
case NODE_IASGN:
case NODE_CDECL:
case NODE_CVDECL:
case NODE_CVASGN:
str = (VALUE)CFSTR("assignment");
break;
}
if (str != 0) {
return ConstantInt::get(RubyObjTy, (long)str);
}
// Now the less easy ones... let's set up an exception handler first
// because we might need to evalute things that will result in an
// exception.
Function *f = bb->getParent();
BasicBlock *old_rescue_invoke_bb = rescue_invoke_bb;
BasicBlock *new_rescue_invoke_bb = BasicBlock::Create(context, "rescue", f);
BasicBlock *merge_bb = BasicBlock::Create(context, "merge", f);
rescue_invoke_bb = new_rescue_invoke_bb;
// Prepare arguments for the runtime.
Value *self = current_self;
Value *what1 = NULL;
Value *what2 = NULL;
int type = 0;
bool expression = false;
switch (nd_type(node)) {
case NODE_IVAR:
type = DEFINED_IVAR;
what1 = compile_id(node->nd_vid);
break;
case NODE_GVAR:
type = DEFINED_GVAR;
// TODO AOT compiler
what1 = ConstantInt::get(RubyObjTy, (VALUE)node->nd_entry);
break;
case NODE_CVAR:
type = DEFINED_CVAR;
what1 = compile_id(node->nd_vid);
break;
case NODE_CONST:
type = DEFINED_LCONST;
what1 = compile_id(node->nd_vid);
what2 = compile_current_class();
break;
case NODE_SUPER:
case NODE_ZSUPER:
type = DEFINED_SUPER;
what1 = compile_id(current_mid);
break;
case NODE_COLON2:
case NODE_COLON3:
what2 = nd_type(node) == NODE_COLON2
? compile_node(node->nd_head)
: compile_nsobject();
if (rb_is_const_id(node->nd_mid)) {
type = DEFINED_CONST;
what1 = compile_id(node->nd_mid);
}
else {
type = DEFINED_METHOD;
what1 = compile_id(node->nd_mid);
}
break;
case NODE_CALL:
case NODE_VCALL:
case NODE_FCALL:
case NODE_ATTRASGN:
if (nd_type(node) == NODE_CALL
|| (nd_type(node) == NODE_ATTRASGN
&& node->nd_recv != (NODE *)1)) {
self = compile_node(node->nd_recv);
}
type = DEFINED_METHOD;
what1 = compile_id(node->nd_mid);
break;
default:
// Unhandled node type, probably an expression. Let's compile
// it and it case everything goes okay we just return 'expression'.
compile_node(node);
expression = true;
break;
}
if (definedFunc == NULL) {
// VALUE rb_vm_defined(VALUE self, int type, VALUE what, VALUE what2);
definedFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_defined",
RubyObjTy, RubyObjTy, Int32Ty, RubyObjTy, RubyObjTy,
NULL));
}
Value *val = NULL;
if (!expression) {
std::vector<Value *> params;
params.push_back(self);
params.push_back(ConstantInt::get(Int32Ty, type));
params.push_back(what1 == NULL ? nilVal : what1);
params.push_back(what2 == NULL ? nilVal : what2);
// Call the runtime.
val = compile_protected_call(definedFunc, params);
}
else {
val = ConstantInt::get(RubyObjTy, (long)CFSTR("expression"));
}
BasicBlock *normal_bb = bb;
BranchInst::Create(merge_bb, bb);
// The rescue block - here we simply do nothing.
bb = new_rescue_invoke_bb;
compile_landing_pad_header();
compile_landing_pad_footer();
BranchInst::Create(merge_bb, bb);
// Now merging.
bb = merge_bb;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(val, normal_bb);
pn->addIncoming(nilVal, new_rescue_invoke_bb);
rescue_invoke_bb = old_rescue_invoke_bb;
return pn;
}
Value *
RoxorCompiler::compile_dstr(NODE *node)
{
std::vector<Value *> params;
if (node->nd_lit != 0) {
params.push_back(compile_literal(node->nd_lit));
}
NODE *n = node->nd_next;
assert(n != NULL);
while (n != NULL) {
params.push_back(compile_node(n->nd_head));
n = n->nd_next;
}
const int count = params.size();
params.insert(params.begin(), ConstantInt::get(Int32Ty, count));
if (newStringFunc == NULL) {
// VALUE rb_str_new_fast(int argc, ...)
std::vector<const Type *> types;
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(RubyObjTy, types, true);
newStringFunc = cast<Function>(module->getOrInsertFunction(
"rb_str_new_fast", ft));
}
return CallInst::Create(newStringFunc, params.begin(), params.end(), "",
bb);
}
Value *
RoxorCompiler::compile_dvar_slot(ID name)
{
// TODO we should cache this
int i = 0, idx = -1;
for (std::vector<ID>::iterator iter = dvars.begin();
iter != dvars.end(); ++iter) {
if (*iter == name) {
idx = i;
break;
}
i++;
}
if (idx == -1) {
return NULL;
}
Function::ArgumentListType::iterator fargs_i =
bb->getParent()->getArgumentList().begin();
++fargs_i; // skip self
++fargs_i; // skip sel
Value *dvars_ary = fargs_i;
Value *index = ConstantInt::get(Int32Ty, idx);
Value *slot = GetElementPtrInst::Create(dvars_ary, index, rb_id2name(name),
bb);
return new LoadInst(slot, "", bb);
}
void
RoxorCompiler::compile_break_val(Value *val)
{
if (breakFunc == NULL) {
// void rb_vm_break(VALUE val);
breakFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_break",
VoidTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(val);
CallInst::Create(breakFunc, params.begin(), params.end(), "", bb);
}
void
RoxorCompiler::compile_return_from_block(Value *val, int id)
{
if (returnFromBlockFunc == NULL) {
// void rb_vm_return_from_block(VALUE val, int id,
// rb_vm_block_t *current_block);
returnFromBlockFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_return_from_block",
VoidTy, RubyObjTy, Int32Ty, PtrTy, NULL));
}
std::vector<Value *> params;
params.push_back(val);
params.push_back(ConstantInt::get(Int32Ty, id));
params.push_back(running_block);
compile_protected_call(returnFromBlockFunc, params);
}
void
RoxorCompiler::compile_return_from_block_handler(int id)
{
//const std::type_info &eh_type = typeid(RoxorReturnFromBlockException *);
//Value *exception = compile_landing_pad_header(eh_type);
Value *exception = compile_landing_pad_header();
if (checkReturnFromBlockFunc == NULL) {
// VALUE rb_vm_check_return_from_block_exc(void *exc, int id);
checkReturnFromBlockFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_check_return_from_block_exc",
RubyObjTy, PtrTy, Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(exception);
params.push_back(ConstantInt::get(Int32Ty, id));
Value *val = CallInst::Create(checkReturnFromBlockFunc, params.begin(),
params.end(), "", bb);
Function *f = bb->getParent();
BasicBlock *ret_bb = BasicBlock::Create(context, "ret", f);
BasicBlock *rethrow_bb = BasicBlock::Create(context, "rethrow", f);
Value *need_ret = new ICmpInst(*bb, ICmpInst::ICMP_NE, val,
ConstantInt::get(RubyObjTy, Qundef));
BranchInst::Create(ret_bb, rethrow_bb, need_ret, bb);
bb = ret_bb;
compile_landing_pad_footer(false);
ReturnInst::Create(context, val, bb);
bb = rethrow_bb;
compile_rethrow_exception();
}
Value *
RoxorCompiler::compile_jump(NODE *node)
{
const bool within_loop = current_loop_begin_bb != NULL
&& current_loop_body_bb != NULL
&& current_loop_end_bb != NULL;
const bool within_block = current_block && current_mid == 0;
Value *val = nd_type(node) == NODE_RETRY
? nilVal
: node->nd_head != NULL
? compile_node(node->nd_head) : nilVal;
switch (nd_type(node)) {
case NODE_RETRY:
// Simply jump to the nearest begin label, after poping the
// current exception.
compile_pop_exception();
if (begin_bb == NULL) {
rb_raise(rb_eSyntaxError, "unexpected retry");
}
// TODO raise a SyntaxError if called outside of a "rescue"
// block.
BranchInst::Create(begin_bb, bb);
break;
case NODE_BREAK:
if (current_rescue) {
compile_landing_pad_footer();
}
if (within_loop) {
BranchInst::Create(current_loop_end_bb, bb);
current_loop_exit_val->addIncoming(val, bb);
}
else if (within_block) {
compile_break_val(val);
ReturnInst::Create(context, val, bb);
}
else {
rb_raise(rb_eLocalJumpError, "unexpected break");
}
break;
case NODE_NEXT:
if (current_rescue) {
compile_landing_pad_footer();
}
if (within_loop) {
BranchInst::Create(current_loop_begin_bb, bb);
}
else if (within_block) {
ReturnInst::Create(context, val, bb);
}
else {
rb_raise(rb_eLocalJumpError, "unexpected next");
}
break;
case NODE_REDO:
if (current_rescue) {
compile_landing_pad_footer();
}
if (within_loop) {
BranchInst::Create(current_loop_body_bb, bb);
}
else if (within_block) {
assert(entry_bb != NULL);
BranchInst::Create(entry_bb, bb);
}
else {
rb_raise(rb_eLocalJumpError, "unexpected redo");
}
break;
case NODE_RETURN:
if (current_rescue) {
compile_pop_exception();
}
if (within_block) {
if (return_from_block == -1) {
return_from_block = return_from_block_ids++;
}
compile_return_from_block(val, return_from_block);
ReturnInst::Create(context, val, bb);
}
else {
compile_simple_return(val);
}
break;
}
// To not complicate the compiler even more, let's be very lazy here and
// continue on a dead branch. Hopefully LLVM is smart enough to eliminate
// it at compilation time.
bb = BasicBlock::Create(context, "DEAD", bb->getParent());
return val;
}
void
RoxorCompiler::compile_simple_return(Value *val)
{
if (ensure_bb != NULL) {
BranchInst::Create(ensure_bb, bb);
ensure_pn->addIncoming(val, bb);
}
else {
ReturnInst::Create(context, val, bb);
}
}
Value *
RoxorCompiler::compile_set_has_ensure(Value *val)
{
if (setHasEnsureFunc == NULL) {
setHasEnsureFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_set_has_ensure", Int8Ty, Int8Ty, NULL));
}
std::vector<Value *> params;
params.push_back(val);
return CallInst::Create(setHasEnsureFunc, params.begin(), params.end(),
"", bb);
}
Value *
RoxorCompiler::compile_class_path(NODE *node, bool *outer)
{
if (nd_type(node) == NODE_COLON3) {
// ::Foo
if (outer != NULL) {
*outer = false;
}
return compile_nsobject();
}
else if (node->nd_head != NULL) {
// Bar::Foo
if (outer != NULL) {
*outer = false;
}
return compile_node(node->nd_head);
}
if (outer != NULL) {
*outer = true;
}
return compile_current_class();
}
Value *
RoxorCompiler::compile_landing_pad_header(void)
{
return compile_landing_pad_header(typeid(void));
}
Value *
RoxorCompiler::compile_landing_pad_header(const std::type_info &eh_type)
{
Function *eh_exception_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_exception);
Value *eh_ptr = CallInst::Create(eh_exception_f, "", bb);
#if __LP64__
Function *eh_selector_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_selector_i64);
#else
Function *eh_selector_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_selector_i32);
#endif
std::vector<Value *> params;
params.push_back(eh_ptr);
Function *__gxx_personality_v0_func = NULL;
if (__gxx_personality_v0_func == NULL) {
__gxx_personality_v0_func = cast<Function>(
module->getOrInsertFunction("__gxx_personality_v0",
PtrTy, NULL));
}
params.push_back(ConstantExpr::getBitCast(__gxx_personality_v0_func, PtrTy));
if (eh_type == typeid(void)) {
// catch (...)
params.push_back(compile_const_pointer(NULL));
}
else {
// catch (eh_type &exc)
params.push_back(compile_const_pointer((void *)&eh_type));
params.push_back(compile_const_pointer(NULL));
}
Value *eh_sel = CallInst::Create(eh_selector_f, params.begin(),
params.end(), "", bb);
if (eh_type != typeid(void)) {
// TODO: this doesn't work yet, the type id must be a GlobalVariable...
#if __LP64__
Function *eh_typeid_for_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_typeid_for_i64);
#else
Function *eh_typeid_for_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_typeid_for_i32);
#endif
std::vector<Value *> params;
params.push_back(compile_const_pointer((void *)&eh_type));
Value *eh_typeid = CallInst::Create(eh_typeid_for_f, params.begin(),
params.end(), "", bb);
Function *f = bb->getParent();
BasicBlock *typeok_bb = BasicBlock::Create(context, "typeok", f);
BasicBlock *nocatch_bb = BasicBlock::Create(context, "nocatch", f);
Value *need_ret = new ICmpInst(*bb, ICmpInst::ICMP_EQ, eh_sel,
eh_typeid);
BranchInst::Create(typeok_bb, nocatch_bb, need_ret, bb);
bb = nocatch_bb;
compile_rethrow_exception();
bb = typeok_bb;
}
Function *beginCatchFunc = NULL;
if (beginCatchFunc == NULL) {
// void *__cxa_begin_catch(void *);
beginCatchFunc = cast<Function>(
module->getOrInsertFunction("__cxa_begin_catch",
PtrTy, PtrTy, NULL));
}
params.clear();
params.push_back(eh_ptr);
return CallInst::Create(beginCatchFunc, params.begin(), params.end(),
"", bb);
}
void
RoxorCompiler::compile_pop_exception(void)
{
if (popExceptionFunc == NULL) {
// void rb_vm_pop_exception(void);
popExceptionFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_pop_exception",
VoidTy, NULL));
}
CallInst::Create(popExceptionFunc, "", bb);
}
void
RoxorCompiler::compile_landing_pad_footer(bool pop_exception)
{
if (pop_exception) {
compile_pop_exception();
}
Function *endCatchFunc = NULL;
if (endCatchFunc == NULL) {
// void __cxa_end_catch(void);
endCatchFunc = cast<Function>(
module->getOrInsertFunction("__cxa_end_catch",
VoidTy, NULL));
}
CallInst::Create(endCatchFunc, "", bb);
}
void
RoxorCompiler::compile_rethrow_exception(void)
{
if (rescue_rethrow_bb == NULL) {
Function *rethrowFunc = NULL;
if (rethrowFunc == NULL) {
// void __cxa_rethrow(void);
rethrowFunc = cast<Function>(
module->getOrInsertFunction("__cxa_rethrow", VoidTy, NULL));
}
CallInst::Create(rethrowFunc, "", bb);
new UnreachableInst(context, bb);
}
else {
BranchInst::Create(rescue_rethrow_bb, bb);
}
}
Value *
RoxorCompiler::compile_current_exception(void)
{
if (currentExceptionFunc == NULL) {
// VALUE rb_vm_current_exception(void);
currentExceptionFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_current_exception",
RubyObjTy, NULL));
}
return CallInst::Create(currentExceptionFunc, "", bb);
}
typedef struct rb_vm_immediate_val {
int type;
union {
long l;
double d;
} v;
rb_vm_immediate_val(void) { type = 0; }
bool is_fixnum(void) { return type == T_FIXNUM; }
bool is_float(void) { return type == T_FLOAT; }
long long_val(void) { return is_fixnum() ? v.l : (long)v.d; }
double double_val(void) { return is_float() ? v.d : (double)v.l; }
} rb_vm_immediate_val_t;
static bool
unbox_immediate_val(VALUE rval, rb_vm_immediate_val_t *val)
{
if (rval != Qundef) {
if (FIXNUM_P(rval)) {
val->type = T_FIXNUM;
val->v.l = FIX2LONG(rval);
return true;
}
else if (FIXFLOAT_P(rval)) {
val->type = T_FLOAT;
val->v.d = FIXFLOAT2DBL(rval);
return true;
}
}
return false;
}
template <class T> static bool
optimized_const_immediate_op(SEL sel, T leftVal, T rightVal,
bool *is_predicate, T *res_p)
{
T res;
if (sel == selPLUS) {
res = leftVal + rightVal;
}
else if (sel == selMINUS) {
res = leftVal - rightVal;
}
else if (sel == selDIV) {
if (rightVal == 0) {
return false;
}
res = leftVal / rightVal;
}
else if (sel == selMULT) {
res = leftVal * rightVal;
}
else {
*is_predicate = true;
if (sel == selLT) {
res = leftVal < rightVal;
}
else if (sel == selLE) {
res = leftVal <= rightVal;
}
else if (sel == selGT) {
res = leftVal > rightVal;
}
else if (sel == selGE) {
res = leftVal >= rightVal;
}
else if (sel == selEq || sel == selEqq) {
res = leftVal == rightVal;
}
else if (sel == selNeq) {
res = leftVal != rightVal;
}
else {
abort();
}
}
*res_p = res;
return true;
}
Value *
RoxorCompiler::optimized_immediate_op(SEL sel, Value *leftVal, Value *rightVal,
bool float_op, bool *is_predicate)
{
Value *res;
if (sel == selPLUS) {
res = BinaryOperator::CreateAdd(leftVal, rightVal, "", bb);
}
else if (sel == selMINUS) {
res = BinaryOperator::CreateSub(leftVal, rightVal, "", bb);
}
else if (sel == selDIV) {
res = float_op
? BinaryOperator::CreateFDiv(leftVal, rightVal, "", bb)
: BinaryOperator::CreateSDiv(leftVal, rightVal, "", bb);
}
else if (sel == selMULT) {
res = BinaryOperator::CreateMul(leftVal, rightVal, "", bb);
}
else {
*is_predicate = true;
CmpInst::Predicate predicate;
if (sel == selLT) {
predicate = float_op ? FCmpInst::FCMP_OLT : ICmpInst::ICMP_SLT;
}
else if (sel == selLE) {
predicate = float_op ? FCmpInst::FCMP_OLE : ICmpInst::ICMP_SLE;
}
else if (sel == selGT) {
predicate = float_op ? FCmpInst::FCMP_OGT : ICmpInst::ICMP_SGT;
}
else if (sel == selGE) {
predicate = float_op ? FCmpInst::FCMP_OGE : ICmpInst::ICMP_SGE;
}
else if (sel == selEq || sel == selEqq) {
predicate = float_op ? FCmpInst::FCMP_OEQ : ICmpInst::ICMP_EQ;
}
else if (sel == selNeq) {
predicate = float_op ? FCmpInst::FCMP_ONE : ICmpInst::ICMP_NE;
}
else {
abort();
}
if (float_op) {
res = new FCmpInst(*bb, predicate, leftVal, rightVal);
}
else {
res = new ICmpInst(*bb, predicate, leftVal, rightVal);
}
res = SelectInst::Create(res, trueVal, falseVal, "", bb);
}
return res;
}
Value *
RoxorCompiler::compile_double_coercion(Value *val, Value *mask,
BasicBlock *fallback_bb, Function *f)
{
Value *is_float = new ICmpInst(*bb, ICmpInst::ICMP_EQ, mask, threeVal);
BasicBlock *is_float_bb = BasicBlock::Create(context, "is_float", f);
BasicBlock *isnt_float_bb = BasicBlock::Create(context, "isnt_float", f);
BasicBlock *merge_bb = BasicBlock::Create(context, "merge", f);
BranchInst::Create(is_float_bb, isnt_float_bb, is_float, bb);
bb = is_float_bb;
Value *is_float_val = BinaryOperator::CreateXor(val, threeVal, "", bb);
#if __LP64__
is_float_val = new BitCastInst(is_float_val, DoubleTy, "", bb);
#else
is_float_val = new BitCastInst(is_float_val, FloatTy, "", bb);
is_float_val = new FPExtInst(is_float_val, DoubleTy, "", bb);
#endif
BranchInst::Create(merge_bb, bb);
bb = isnt_float_bb;
Value *is_fixnum = new ICmpInst(*bb, ICmpInst::ICMP_EQ, mask, oneVal);
BasicBlock *is_fixnum_bb = BasicBlock::Create(context, "is_fixnum", f);
BranchInst::Create(is_fixnum_bb, fallback_bb, is_fixnum, bb);
bb = is_fixnum_bb;
Value *is_fixnum_val = BinaryOperator::CreateAShr(val, twoVal, "", bb);
is_fixnum_val = new SIToFPInst(is_fixnum_val, DoubleTy, "", bb);
BranchInst::Create(merge_bb, bb);
bb = merge_bb;
PHINode *pn = PHINode::Create(DoubleTy, "op_tmp", bb);
pn->addIncoming(is_float_val, is_float_bb);
pn->addIncoming(is_fixnum_val, is_fixnum_bb);
return pn;
}
Value *
RoxorCompiler::compile_optimized_dispatch_call(SEL sel, int argc,
std::vector<Value *> &params)
{
// The not operator (!).
if (sel == selNot) {
if (current_block_func != NULL || argc != 0) {
return NULL;
}
Value *val = params[2]; // self
Function *f = bb->getParent();
BasicBlock *falseBB = BasicBlock::Create(context, "", f);
BasicBlock *trueBB = BasicBlock::Create(context, "", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "", f);
compile_boolean_test(val, trueBB, falseBB);
BranchInst::Create(mergeBB, falseBB);
BranchInst::Create(mergeBB, trueBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(trueVal, falseBB);
pn->addIncoming(falseVal, trueBB);
return pn;
}
// Pure arithmetic operations.
else if (sel == selPLUS || sel == selMINUS || sel == selDIV
|| sel == selMULT || sel == selLT || sel == selLE
|| sel == selGT || sel == selGE || sel == selEq
|| sel == selNeq || sel == selEqq) {
if (current_block_func != NULL || argc != 1) {
return NULL;
}
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(sel, true);
Value *leftVal = params[2]; // self
Value *rightVal = params.back();
VALUE leftRVal = Qundef, rightRVal = Qundef;
const bool leftIsConstant = unbox_ruby_constant(leftVal, &leftRVal);
const bool rightIsConst = unbox_ruby_constant(rightVal, &rightRVal);
if (leftIsConstant && rightIsConst
&& TYPE(leftRVal) == T_SYMBOL && TYPE(rightRVal) == T_SYMBOL) {
// Both operands are symbol constants.
if (sel == selEq || sel == selEqq || sel == selNeq) {
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
is_redefined_val, ConstantInt::getFalse(context));
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create(context, "op_not_redefined", f);
BasicBlock *elseBB = BasicBlock::Create(context, "op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "op_merge", f);
BranchInst::Create(thenBB, elseBB, isOpRedefined, bb);
Value *thenVal = NULL;
if (sel == selEq || sel == selEqq) {
thenVal = leftRVal == rightRVal ? trueVal : falseVal;
}
else if (sel == selNeq) {
thenVal = leftRVal != rightRVal ? trueVal : falseVal;
}
else {
abort();
}
BranchInst::Create(mergeBB, thenBB);
bb = elseBB;
Value *elseVal = compile_dispatch_call(params);
elseBB = bb;
BranchInst::Create(mergeBB, elseBB);
PHINode *pn = PHINode::Create(RubyObjTy, "op_tmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
bb = mergeBB;
return pn;
}
else {
return NULL;
}
}
rb_vm_immediate_val_t leftImm, rightImm;
const bool leftIsImmediateConst = unbox_immediate_val(leftRVal,
&leftImm);
const bool rightIsImmediateConst = unbox_immediate_val(rightRVal,
&rightImm);
if (leftIsImmediateConst && rightIsImmediateConst) {
Value *res_val = NULL;
if (leftImm.is_fixnum() && rightImm.is_fixnum()) {
bool result_is_predicate = false;
long res;
if (optimized_const_immediate_op<long>(
sel,
leftImm.long_val(),
rightImm.long_val(),
&result_is_predicate,
&res)) {
if (result_is_predicate) {
res_val = res == 1 ? trueVal : falseVal;
}
else if (FIXABLE(res)) {
res_val = ConstantInt::get(RubyObjTy, LONG2FIX(res));
}
}
}
else {
bool result_is_predicate = false;
double res;
if (optimized_const_immediate_op<double>(
sel,
leftImm.double_val(),
rightImm.double_val(),
&result_is_predicate,
&res)) {
if (result_is_predicate) {
res_val = res == 1 ? trueVal : falseVal;
}
else {
res_val = ConstantInt::get(RubyObjTy,
DBL2FIXFLOAT(res));
}
}
}
if (res_val != NULL) {
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
is_redefined_val, ConstantInt::getFalse(context));
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create(context, "op_not_redefined", f);
BasicBlock *elseBB = BasicBlock::Create(context, "op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "op_merge", f);
BranchInst::Create(thenBB, elseBB, isOpRedefined, bb);
Value *thenVal = res_val;
BranchInst::Create(mergeBB, thenBB);
bb = elseBB;
Value *elseVal = compile_dispatch_call(params);
elseBB = bb;
BranchInst::Create(mergeBB, elseBB);
PHINode *pn = PHINode::Create(RubyObjTy, "op_tmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
bb = mergeBB;
return pn;
}
// Can't optimize, call the dispatcher.
return NULL;
}
else {
// Either one or both is not a constant immediate.
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
is_redefined_val, ConstantInt::getFalse(context));
Function *f = bb->getParent();
BasicBlock *not_redefined_bb =
BasicBlock::Create(context, "op_not_redefined", f);
BasicBlock *optimize_fixnum_bb =
BasicBlock::Create(context, "op_optimize_fixnum", f);
BasicBlock *optimize_float_bb =
BasicBlock::Create(context, "op_optimize_float", f);
BasicBlock *dispatch_bb =
BasicBlock::Create( context, "op_dispatch", f);
BasicBlock *merge_bb = BasicBlock::Create(context, "op_merge", f);
BranchInst::Create(not_redefined_bb, dispatch_bb, isOpRedefined,
bb);
bb = not_redefined_bb;
Value *leftAndOp = NULL;
if (!leftIsImmediateConst) {
leftAndOp = BinaryOperator::CreateAnd(leftVal, threeVal, "",
bb);
}
Value *rightAndOp = NULL;
if (!rightIsImmediateConst) {
rightAndOp = BinaryOperator::CreateAnd(rightVal, threeVal, "",
bb);
}
if (leftAndOp != NULL && rightAndOp != NULL) {
Value *leftIsFixnum = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
leftAndOp, oneVal);
BasicBlock *left_is_fixnum_bb =
BasicBlock::Create(context, "left_fixnum", f);
BranchInst::Create(left_is_fixnum_bb, optimize_float_bb,
leftIsFixnum, bb);
bb = left_is_fixnum_bb;
Value *rightIsFixnum = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
rightAndOp, oneVal);
BranchInst::Create(optimize_fixnum_bb, optimize_float_bb,
rightIsFixnum, bb);
}
else if (leftAndOp != NULL) {
if (rightImm.is_fixnum()) {
Value *leftIsFixnum = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
leftAndOp, oneVal);
BranchInst::Create(optimize_fixnum_bb, optimize_float_bb,
leftIsFixnum, bb);
}
else {
BranchInst::Create(optimize_float_bb, bb);
}
}
else if (rightAndOp != NULL) {
if (leftImm.is_fixnum()) {
Value *rightIsFixnum = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
rightAndOp, oneVal);
BranchInst::Create(optimize_fixnum_bb, optimize_float_bb,
rightIsFixnum, bb);
}
else {
BranchInst::Create(optimize_float_bb, bb);
}
}
bb = optimize_fixnum_bb;
Value *unboxedLeft;
if (leftIsImmediateConst) {
unboxedLeft = ConstantInt::get(IntTy, leftImm.long_val());
}
else {
unboxedLeft = BinaryOperator::CreateAShr(leftVal, twoVal, "",
bb);
}
Value *unboxedRight;
if (rightIsImmediateConst) {
unboxedRight = ConstantInt::get(IntTy, rightImm.long_val());
}
else {
unboxedRight = BinaryOperator::CreateAShr(rightVal, twoVal, "",
bb);
}
bool result_is_predicate = false;
Value *fix_op_res = optimized_immediate_op(sel, unboxedLeft,
unboxedRight, false, &result_is_predicate);
if (!result_is_predicate) {
// Box the fixnum.
Value *shift = BinaryOperator::CreateShl(fix_op_res, twoVal, "",
bb);
Value *boxed_op_res = BinaryOperator::CreateOr(shift, oneVal,
"", bb);
// Is result fixable?
Value *fixnumMax = ConstantInt::get(IntTy, FIXNUM_MAX + 1);
Value *isFixnumMaxOk = new ICmpInst(*bb, ICmpInst::ICMP_SLT,
fix_op_res, fixnumMax);
BasicBlock *fixable_max_bb =
BasicBlock::Create(context, "op_fixable_max", f);
BranchInst::Create(fixable_max_bb, dispatch_bb, isFixnumMaxOk,
bb);
bb = fixable_max_bb;
Value *fixnumMin = ConstantInt::get(IntTy, FIXNUM_MIN);
Value *isFixnumMinOk = new ICmpInst(*bb, ICmpInst::ICMP_SGE,
fix_op_res, fixnumMin);
BranchInst::Create(merge_bb, dispatch_bb, isFixnumMinOk, bb);
fix_op_res = boxed_op_res;
optimize_fixnum_bb = fixable_max_bb;
}
else {
BranchInst::Create(merge_bb, bb);
}
bb = optimize_float_bb;
if (leftIsImmediateConst) {
unboxedLeft = ConstantFP::get(DoubleTy, leftImm.double_val());
}
else {
unboxedLeft = compile_double_coercion(leftVal, leftAndOp,
dispatch_bb, f);
}
if (rightIsImmediateConst) {
unboxedRight = ConstantFP::get(DoubleTy, rightImm.double_val());
}
else {
unboxedRight = compile_double_coercion(rightVal, rightAndOp,
dispatch_bb, f);
}
result_is_predicate = false;
Value *flp_op_res = optimized_immediate_op(sel, unboxedLeft,
unboxedRight, true, &result_is_predicate);
if (!result_is_predicate) {
// Box the float.
#if !__LP64__
flp_op_res = new FPTruncInst(flp_op_res, FloatTy, "", bb);
#endif
flp_op_res = new BitCastInst(flp_op_res, RubyObjTy, "", bb);
flp_op_res = BinaryOperator::CreateOr(flp_op_res, threeVal,
"", bb);
}
optimize_float_bb = bb;
BranchInst::Create(merge_bb, bb);
bb = dispatch_bb;
Value *dispatch_val = compile_fast_op_call(sel, leftVal, rightVal);
if (dispatch_val == NULL) {
dispatch_val = compile_dispatch_call(params);
}
dispatch_bb = bb;
BranchInst::Create(merge_bb, bb);
bb = merge_bb;
PHINode *pn = PHINode::Create(RubyObjTy, "op_tmp", bb);
pn->addIncoming(fix_op_res, optimize_fixnum_bb);
pn->addIncoming(flp_op_res, optimize_float_bb);
pn->addIncoming(dispatch_val, dispatch_bb);
// if (sel == selEqq) {
// pn->addIncoming(fastEqqVal, fastEqqBB);
// }
return pn;
}
}
// Other operators (#<< or #[] or #[]=)
else if (sel == selLTLT || sel == selAREF || sel == selASET) {
const int expected_argc = sel == selASET ? 2 : 1;
if (current_block_func != NULL || argc != expected_argc) {
return NULL;
}
if (params.size() - argc > 7) {
// Looks like there is a splat argument there, we can't handle this
// in the primitives.
return NULL;
}
Function *opt_func = NULL;
if (sel == selLTLT) {
opt_func = cast<Function>(module->getOrInsertFunction("rb_vm_fast_shift",
RubyObjTy, RubyObjTy, RubyObjTy, PtrTy, Int1Ty, NULL));
}
else if (sel == selAREF) {
opt_func = cast<Function>(module->getOrInsertFunction("rb_vm_fast_aref",
RubyObjTy, RubyObjTy, RubyObjTy, PtrTy, Int1Ty, NULL));
}
else if (sel == selASET) {
opt_func = cast<Function>(module->getOrInsertFunction("rb_vm_fast_aset",
RubyObjTy, RubyObjTy, RubyObjTy, RubyObjTy, PtrTy,
Int1Ty, NULL));
}
else {
abort();
}
std::vector<Value *> new_params;
new_params.push_back(params[2]); // self
if (argc == 1) {
new_params.push_back(params.back()); // other
}
else {
new_params.insert(new_params.end(), params.end() - 2, params.end());
}
new_params.push_back(params[0]); // cache
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(sel, true);
new_params.push_back(new LoadInst(is_redefined, "", bb));
return compile_protected_call(opt_func, new_params);
}
// #send or #__send__
else if (sel == selSend || sel == sel__send__) {
if (current_block_func != NULL || argc == 0) {
return NULL;
}
Value *symVal = params[params.size() - argc];
if (!ConstantInt::classof(symVal)) {
return NULL;
}
VALUE sym = cast<ConstantInt>(symVal)->getZExtValue();
if (!SYMBOL_P(sym)) {
return NULL;
}
SEL new_sel = mid_to_sel(SYM2ID(sym), argc - 1);
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(sel, true);
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
is_redefined_val, ConstantInt::getFalse(context));
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create(context, "op_not_redefined", f);
BasicBlock *elseBB = BasicBlock::Create(context, "op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "op_merge", f);
BranchInst::Create(thenBB, elseBB, isOpRedefined, bb);
bb = thenBB;
std::vector<Value *> new_params;
new_params.push_back(compile_mcache(new_sel, false));
new_params.push_back(params[1]);
new_params.push_back(params[2]);
new_params.push_back(compile_sel(new_sel));
new_params.push_back(params[4]);
new_params.push_back(ConstantInt::get(Int8Ty, DISPATCH_FCALL));
new_params.push_back(ConstantInt::get(Int32Ty, argc - 1));
for (int i = 0; i < argc - 1; i++) {
new_params.push_back(params[8 + i]);
}
Value *thenVal = compile_dispatch_call(new_params);
thenBB = bb;
BranchInst::Create(mergeBB, thenBB);
bb = elseBB;
Value *elseVal = compile_dispatch_call(params);
elseBB = bb;
BranchInst::Create(mergeBB, elseBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "op_tmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
return pn;
}
#if 0
// XXX this optimization is disabled because it's buggy and not really
// interesting
// #eval
else if (sel == selEval) {
if (current_block_func != NULL || argc != 1) {
return NULL;
}
Value *strVal = params.back();
if (!ConstantInt::classof(strVal)) {
return NULL;
}
VALUE str = cast<ConstantInt>(strVal)->getZExtValue();
if (TYPE(str) != T_STRING) {
return NULL;
}
// FIXME:
// - pass the real file/line arguments
// - catch potential parsing exceptions
NODE *new_node = rb_compile_string("", str, 0);
if (new_node == NULL) {
return NULL;
}
if (nd_type(new_node) != NODE_SCOPE || new_node->nd_body == NULL) {
return NULL;
}
GlobalVariable *is_redefined = GET_VM()->redefined_op_gvar(sel, true);
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(ICmpInst::ICMP_EQ,
is_redefined_val, ConstantInt::getFalse(), "", bb);
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create("op_not_redefined", f);
BasicBlock *elseBB = BasicBlock::Create("op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create("op_merge", f);
BranchInst::Create(thenBB, elseBB, isOpRedefined, bb);
bb = thenBB;
Value *thenVal = compile_node(new_node->nd_body);
thenBB = bb;
BranchInst::Create(mergeBB, thenBB);
bb = elseBB;
Value *elseVal = compile_dispatch_call(params);
BranchInst::Create(mergeBB, elseBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "op_tmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
return pn;
}
#endif
#if 0
// TODO: block inlining optimization
else if (current_block_func != NULL) {
static SEL selTimes = 0;
if (selTimes == 0) {
selTimes = rb_intern("times");
}
if (sel == selTimes && argc == 0) {
Value *val = params[1]; // self
long valLong;
if (unbox_fixnum_constant(val, &valLong)) {
GlobalVariable *is_redefined = redefined_op_gvar(sel, true);
Value *is_redefined_val = new LoadInst(is_redefined, "", bb);
Value *isOpRedefined = new ICmpInst(ICmpInst::ICMP_EQ, is_redefined_val, ConstantInt::getFalse(), "", bb);
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create("op_not_redefined", f);
BasicBlock *elseBB = BasicBlock::Create("op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create("op_merge", f);
BranchInst::Create(thenBB, elseBB, isOpRedefined, bb);
bb = thenBB;
// Val *mem = new AllocaInst(RubyObjTy, "", bb);
// new StoreInst(zeroVal, mem, "", bb);
// Val *i = LoadInst(mem, "", bb);
Value *thenVal = val;
BranchInst::Create(mergeBB, thenBB);
Value *elseVal = dispatchCall;
elseBB->getInstList().push_back(dispatchCall);
BranchInst::Create(mergeBB, elseBB);
PHINode *pn = PHINode::Create(Type::Int32Ty, "op_tmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
bb = mergeBB;
return pn;
}
}
}
#endif
return NULL;
}
Instruction *
RoxorCompiler::compile_range(Value *beg, Value *end, bool exclude_end,
bool retain, bool add_to_bb)
{
if (newRangeFunc == NULL) {
// VALUE rb_range_new2(VALUE beg, VALUE end, int exclude_end,
// int retain);
newRangeFunc = cast<Function>(module->getOrInsertFunction(
"rb_range_new2",
RubyObjTy, RubyObjTy, RubyObjTy, Int32Ty, Int32Ty,
NULL));
}
std::vector<Value *> params;
params.push_back(beg);
params.push_back(end);
params.push_back(ConstantInt::get(Int32Ty, exclude_end ? 1 : 0));
params.push_back(ConstantInt::get(Int32Ty, retain ? 1 : 0));
if (add_to_bb) {
return compile_protected_call(newRangeFunc, params);
}
return CallInst::Create(newRangeFunc, params.begin(), params.end(), "");
}
Value *
RoxorCompiler::compile_literal(VALUE val)
{
if (TYPE(val) == T_STRING) {
// We must compile a new string creation because strings are
// mutable, we can't simply compile a reference to a master
// copy.
//
// 10.times { s = 'foo'; s << 'bar' }
//
const size_t str_len = RSTRING_LEN(val);
if (str_len == 0) {
if (newString3Func == NULL) {
newString3Func = cast<Function>(
module->getOrInsertFunction(
"rb_str_new_empty", RubyObjTy, NULL));
}
return CallInst::Create(newString3Func, "", bb);
}
else {
UniChar *buf = (UniChar *)CFStringGetCharactersPtr(
(CFStringRef)val);
if (buf == NULL) {
buf = (UniChar *)alloca(sizeof(UniChar) * str_len);
CFStringGetCharacters((CFStringRef)val,
CFRangeMake(0, str_len), buf);
}
GlobalVariable *str_gvar = compile_const_global_ustring(buf,
str_len, CFHash((CFTypeRef)val));
std::vector<Value *> idxs;
idxs.push_back(ConstantInt::get(Int32Ty, 0));
idxs.push_back(ConstantInt::get(Int32Ty, 0));
Instruction *load = GetElementPtrInst::Create(str_gvar,
idxs.begin(), idxs.end(), "", bb);
if (newString2Func == NULL) {
newString2Func = cast<Function>(
module->getOrInsertFunction(
"rb_unicode_str_new",
RubyObjTy, PointerType::getUnqual(Int16Ty),
Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(load);
params.push_back(ConstantInt::get(Int32Ty, str_len));
return CallInst::Create(newString2Func, params.begin(),
params.end(), "", bb);
}
}
return compile_immutable_literal(val);
}
Value *
RoxorCompiler::compile_immutable_literal(VALUE val)
{
return ConstantInt::get(RubyObjTy, (long)val);
}
Value *
RoxorAOTCompiler::compile_immutable_literal(VALUE val)
{
if (SPECIAL_CONST_P(val)) {
return RoxorCompiler::compile_immutable_literal(val);
}
if (rb_obj_is_kind_of(val, rb_cEncoding)) {
// This is the __ENCODING__ keyword.
// TODO: compile the real encoding...
return nilVal;
}
std::map<VALUE, GlobalVariable *>::iterator iter = literals.find(val);
GlobalVariable *gvar = NULL;
if (iter == literals.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, RubyObjTy, false,
GlobalValue::InternalLinkage, nilVal, "");
literals[val] = gvar;
}
else {
gvar = iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorCompiler::compile_global_entry(NODE *node)
{
return compile_const_pointer(node->nd_entry);
}
Value *
RoxorAOTCompiler::compile_global_entry(NODE *node)
{
const ID name = node->nd_vid;
assert(name > 0);
std::map<ID, GlobalVariable *>::iterator iter = global_entries.find(name);
GlobalVariable *gvar = NULL;
if (iter == global_entries.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module, PtrTy, false,
GlobalValue::InternalLinkage, Constant::getNullValue(PtrTy),
"");
global_entries[name] = gvar;
}
else {
gvar = iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorCompiler::compile_set_current_class(Value *klass)
{
if (setCurrentClassFunc == NULL) {
// Class rb_vm_set_current_class(Class klass)
setCurrentClassFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_set_current_class",
RubyObjTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(klass);
return CallInst::Create(setCurrentClassFunc, params.begin(), params.end(),
"", bb);
}
void
RoxorCompiler::compile_set_current_scope(Value *klass, Value *scope)
{
if (setScopeFunc == NULL) {
// void rb_vm_set_current_scope(VALUE mod, int scope)
setScopeFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_set_current_scope",
VoidTy, RubyObjTy, Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(klass);
params.push_back(scope);
CallInst::Create(setScopeFunc, params.begin(), params.end(), "", bb);
}
void
RoxorCompiler::compile_ivar_slots(Value *klass,
BasicBlock::InstListType &list,
BasicBlock::InstListType::iterator list_iter)
{
if (ivar_slots_cache.size() > 0) {
if (prepareIvarSlotFunc == NULL) {
// void rb_vm_prepare_class_ivar_slot(VALUE klass, ID name,
// int *slot_cache);
prepareIvarSlotFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_prepare_class_ivar_slot",
VoidTy, RubyObjTy, IntTy, Int32PtrTy, NULL));
}
for (std::map<ID, Value *>::iterator iter
= ivar_slots_cache.begin();
iter != ivar_slots_cache.end();
++iter) {
ID ivar_name = iter->first;
Value *ivar_slot = iter->second;
std::vector<Value *> params;
params.push_back(klass);
Value *id_val = compile_id(ivar_name);
if (Instruction::classof(id_val)) {
Instruction *insn = cast<Instruction>(id_val);
insn->removeFromParent();
list.insert(list_iter, insn);
}
params.push_back(id_val);
Instruction *slot_insn = dyn_cast<Instruction>(ivar_slot);
if (slot_insn != NULL) {
Instruction *insn = slot_insn->clone(context);
list.insert(list_iter, insn);
params.push_back(insn);
}
else {
params.push_back(ivar_slot);
}
CallInst *call = CallInst::Create(prepareIvarSlotFunc,
params.begin(), params.end(), "");
list.insert(list_iter, call);
}
}
}
void
RoxorCompiler::compile_node_error(const char *msg, NODE *node)
{
int t = nd_type(node);
printf("%s: %d (%s)", msg, t, ruby_node_name(t));
abort();
}
void
RoxorCompiler::compile_keep_vars(BasicBlock *startBB, BasicBlock *mergeBB)
{
if (keepVarsFunc == NULL) {
// void rb_vm_keep_vars(rb_vm_var_uses *uses, int lvars_size, ...)
std::vector<const Type *> types;
types.push_back(PtrTy);
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(VoidTy, types, true);
keepVarsFunc = cast<Function>
(module->getOrInsertFunction("rb_vm_keep_vars", ft));
}
BasicBlock *notNullBB = BasicBlock::Create(context, "not_null",
startBB->getParent());
bb = startBB;
Value *usesVal = new LoadInst(current_var_uses, "", bb);
Value *notNullCond = new ICmpInst(*bb, ICmpInst::ICMP_NE, usesVal,
compile_const_pointer(NULL));
// we only need to call keepVarsFunc if current_var_uses is not NULL
BranchInst::Create(notNullBB, mergeBB, notNullCond, bb);
bb = notNullBB;
// params must be filled each time because in AOT mode it contains instructions
std::vector<Value *> params;
params.push_back(new LoadInst(current_var_uses, "", bb));
params.push_back(NULL);
int vars_count = 0;
for (std::map<ID, Value *>::iterator iter = lvars.begin();
iter != lvars.end(); ++iter) {
ID name = iter->first;
Value *slot = iter->second;
if (std::find(dvars.begin(), dvars.end(), name) == dvars.end()) {
Value *id_val = compile_id(name);
params.push_back(id_val);
params.push_back(slot);
vars_count++;
}
}
params[1] = ConstantInt::get(Int32Ty, vars_count);
CallInst::Create(keepVarsFunc, params.begin(), params.end(), "", bb);
BranchInst::Create(mergeBB, bb);
}
Value *
RoxorCompiler::compile_node(NODE *node)
{
#if ROXOR_COMPILER_DEBUG
printf("%s:%ld ", fname, nd_line(node));
for (int i = 0; i < level; i++) {
printf("...");
}
printf("... %s\n", ruby_node_name(nd_type(node)));
#endif
current_line = nd_line(node);
switch (nd_type(node)) {
case NODE_SCOPE:
{
rb_vm_arity_t arity = rb_vm_node_arity(node);
const int nargs = bb == NULL ? 0 : arity.real;
const bool has_dvars = current_block && current_mid == 0;
// Get dynamic vars.
if (has_dvars && node->nd_tbl != NULL) {
const int args_count = (int)node->nd_tbl[0];
const int lvar_count = (int)node->nd_tbl[args_count + 1];
for (int i = 0; i < lvar_count; i++) {
ID id = node->nd_tbl[i + args_count + 2];
if (lvars.find(id) != lvars.end()) {
std::vector<ID>::iterator iter = std::find(dvars.begin(), dvars.end(), id);
if (iter == dvars.end()) {
#if ROXOR_COMPILER_DEBUG
printf("dvar %s\n", rb_id2name(id));
#endif
dvars.push_back(id);
}
}
}
}
// Create function type.
std::vector<const Type *> types;
types.push_back(RubyObjTy); // self
types.push_back(PtrTy); // sel
if (has_dvars) {
types.push_back(RubyObjPtrPtrTy); // dvars array
types.push_back(PtrTy); // rb_vm_block_t of the currently running block
}
for (int i = 0; i < nargs; ++i) {
types.push_back(RubyObjTy);
}
FunctionType *ft = FunctionType::get(RubyObjTy, types, false);
std::string function_name;
if (ruby_aot_compile) {
function_name.append(RSTRING_PTR(ruby_aot_init_func));
function_name.append("_ruby_scope");
}
else {
function_name.append("__ruby_scope");
}
Function *f = Function::Create(ft, GlobalValue::ExternalLinkage,
function_name, module);
RoxorScope *old_current_scope = current_scope;
current_scope = new RoxorScope(fname);
scopes[f] = current_scope;
BasicBlock *old_rescue_invoke_bb = rescue_invoke_bb;
BasicBlock *old_rescue_rethrow_bb = rescue_rethrow_bb;
BasicBlock *old_entry_bb = entry_bb;
BasicBlock *old_bb = bb;
BasicBlock *new_rescue_invoke_bb = NULL;
BasicBlock *new_rescue_rethrow_bb = NULL;
rescue_invoke_bb = NULL;
rescue_rethrow_bb = NULL;
bb = BasicBlock::Create(context, "MainBlock", f);
std::map<ID, Value *> old_lvars = lvars;
lvars.clear();
Value *old_self = current_self;
Function::arg_iterator arg;
arg = f->arg_begin();
Value *self = arg++;
self->setName("self");
current_self = self;
Value *sel = arg++;
sel->setName("sel");
Value *old_running_block = running_block;
Value *old_current_var_uses = current_var_uses;
current_var_uses = NULL;
if (has_dvars) {
Value *dvars_arg = arg++;
dvars_arg->setName("dvars");
running_block = arg++;
running_block->setName("running_block");
}
else {
running_block = NULL;
}
current_block_arg = NULL;
if (node->nd_tbl != NULL) {
bool has_vars_to_save = false;
int i, args_count = (int)node->nd_tbl[0];
assert(args_count == nargs
|| args_count == nargs + 1 /* optional block */
|| args_count == nargs - 1 /* unnamed param (|x,|) */);
for (i = 0; i < args_count; i++) {
ID id = node->nd_tbl[i + 1];
#if ROXOR_COMPILER_DEBUG
printf("arg %s\n", rb_id2name(id));
#endif
Value *val = NULL;
if (i < nargs) {
val = arg++;
val->setName(rb_id2name(id));
}
else {
// Optional block.
if (currentBlockObjectFunc == NULL) {
// VALUE rb_vm_current_block_object(void);
currentBlockObjectFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_current_block_object",
RubyObjTy, NULL));
}
val = CallInst::Create(currentBlockObjectFunc, "", bb);
current_block_arg = val;
}
Value *slot = new AllocaInst(RubyObjTy, "", bb);
new StoreInst(val, slot, bb);
lvars[id] = slot;
has_vars_to_save = true;
}
// local vars must be created before the optional arguments
// because they can be used in them, for instance with def f(a=b=c=1)
if (compile_lvars(&node->nd_tbl[args_count + 1])) {
has_vars_to_save = true;
}
if (has_vars_to_save) {
current_var_uses = new AllocaInst(PtrTy, "", bb);
new StoreInst(compile_const_pointer(NULL),
current_var_uses, bb);
new_rescue_invoke_bb = BasicBlock::Create(context,
"rescue_save_vars", f);
new_rescue_rethrow_bb = BasicBlock::Create(context,
"rescue_save_vars.rethrow", f);
rescue_invoke_bb = new_rescue_invoke_bb;
rescue_rethrow_bb = new_rescue_rethrow_bb;
}
NODE *args_node = node->nd_args;
if (args_node != NULL) {
// compile multiple assignment arguments (def f((a, b, v)))
// (this must also be done after the creation of local variables)
NODE *rest_node = args_node->nd_next;
if (rest_node != NULL) {
NODE *right_req_node = rest_node->nd_next;
if (right_req_node != NULL) {
NODE *last_node = right_req_node->nd_next;
if (last_node != NULL) {
assert(nd_type(last_node) == NODE_AND);
// multiple assignment for the left-side required arguments
if (last_node->nd_1st != NULL) {
compile_node(last_node->nd_1st);
}
// multiple assignment for the right-side required arguments
if (last_node->nd_2nd != NULL) {
compile_node(last_node->nd_2nd);
}
}
}
}
// Compile optional arguments.
Function::ArgumentListType::iterator iter = f->arg_begin();
++iter; // skip self
++iter; // skip sel
NODE *opt_node = args_node->nd_opt;
if (opt_node != NULL) {
int to_skip = args_node->nd_frml;
if (has_dvars) {
to_skip += 2; // dvars array and currently running block
}
for (i = 0; i < to_skip; i++) {
++iter; // skip dvars and args required on the left-side
}
iter = compile_optional_arguments(iter, opt_node);
}
}
}
Value *val = NULL;
if (node->nd_body != NULL) {
entry_bb = BasicBlock::Create(context, "entry_point", f);
BranchInst::Create(entry_bb, bb);
bb = entry_bb;
rb_vm_arity_t old_current_arity = current_arity;
current_arity = arity;
DEBUG_LEVEL_INC();
val = compile_node(node->nd_body);
DEBUG_LEVEL_DEC();
current_arity = old_current_arity;
}
if (val == NULL) {
val = nilVal;
}
ReturnInst::Create(context, val, bb);
// the rethrows after the save of variables must be real rethrows
rescue_rethrow_bb = NULL;
rescue_invoke_bb = NULL;
// current_lvar_uses has 2 uses or more if it is really used
// (there is always a StoreInst in which we assign it NULL)
if (current_var_uses != NULL && current_var_uses->hasNUsesOrMore(2)) {
// searches all ReturnInst in the function we just created and add before
// a call to the function to save the local variables if necessary
// (we can't do this before finishing compiling the whole function
// because we can't be sure if the function contains a block or not before)
std::vector<ReturnInst *> to_fix;
for (Function::iterator block_it = f->begin();
block_it != f->end();
++block_it) {
for (BasicBlock::iterator inst_it = block_it->begin();
inst_it != block_it->end();
++inst_it) {
ReturnInst *inst = dyn_cast<ReturnInst>(inst_it);
if (inst != NULL) {
to_fix.push_back(inst);
}
}
}
// we have to process the blocks in a second loop because
// we can't modify the blocks while iterating on them
for (std::vector<ReturnInst *>::iterator inst_it = to_fix.begin();
inst_it != to_fix.end();
++inst_it) {
ReturnInst *inst = *inst_it;
BasicBlock *startBB = inst->getParent();
BasicBlock *mergeBB = startBB->splitBasicBlock(inst, "merge");
// we do not want the BranchInst added by splitBasicBlock
startBB->getInstList().pop_back();
compile_keep_vars(startBB, mergeBB);
}
if (new_rescue_invoke_bb->use_empty() && new_rescue_rethrow_bb->use_empty()) {
new_rescue_invoke_bb->eraseFromParent();
new_rescue_rethrow_bb->eraseFromParent();
}
else {
if (new_rescue_invoke_bb->use_empty()) {
new_rescue_invoke_bb->eraseFromParent();
}
else {
bb = new_rescue_invoke_bb;
compile_landing_pad_header();
BranchInst::Create(new_rescue_rethrow_bb, bb);
}
bb = new_rescue_rethrow_bb;
BasicBlock *mergeBB = BasicBlock::Create(context,
"merge", f);
compile_keep_vars(bb, mergeBB);
bb = mergeBB;
compile_rethrow_exception();
}
}
else if (current_var_uses != NULL) {
for (BasicBlock::use_iterator rescue_use_it = new_rescue_invoke_bb->use_begin();
rescue_use_it != new_rescue_invoke_bb->use_end();
rescue_use_it = new_rescue_invoke_bb->use_begin()) {
InvokeInst* invoke = dyn_cast<InvokeInst>(rescue_use_it);
assert(invoke != NULL);
// transform the InvokeInst in CallInst
std::vector<Value *> params;
for (InvokeInst::op_iterator op_it = invoke->op_begin()+3;
op_it != invoke->op_end(); ++op_it) {
params.push_back(op_it->get());
}
CallInst *call_inst = CallInst::Create(
invoke->getOperand(0),
params.begin(), params.end(),
invoke->getNameStr(),
invoke);
invoke->replaceAllUsesWith(call_inst);
BasicBlock *normal_bb = dyn_cast<BasicBlock>(invoke->getOperand(1));
assert(normal_bb != NULL);
BranchInst::Create(normal_bb, invoke);
invoke->eraseFromParent();
}
new_rescue_invoke_bb->eraseFromParent();
if (new_rescue_rethrow_bb->use_empty()) {
new_rescue_rethrow_bb->eraseFromParent();
}
else {
bb = new_rescue_rethrow_bb;
compile_rethrow_exception();
}
}
rescue_rethrow_bb = old_rescue_rethrow_bb;
rescue_invoke_bb = old_rescue_invoke_bb;
bb = old_bb;
entry_bb = old_entry_bb;
lvars = old_lvars;
current_self = old_self;
current_var_uses = old_current_var_uses;
running_block = old_running_block;
current_scope = old_current_scope;
return cast<Value>(f);
}
break;
case NODE_DVAR:
case NODE_LVAR:
{
assert(node->nd_vid > 0);
return new LoadInst(compile_lvar_slot(node->nd_vid), "", bb);
}
break;
case NODE_GVAR:
{
assert(node->nd_vid > 0);
assert(node->nd_entry != NULL);
if (gvarGetFunc == NULL) {
// VALUE rb_gvar_get(struct global_entry *entry);
gvarGetFunc = cast<Function>(module->getOrInsertFunction("rb_gvar_get", RubyObjTy, PtrTy, NULL));
}
std::vector<Value *> params;
params.push_back(compile_global_entry(node));
return CallInst::Create(gvarGetFunc, params.begin(), params.end(), "", bb);
}
break;
case NODE_GASGN:
{
assert(node->nd_vid > 0);
assert(node->nd_value != NULL);
assert(node->nd_entry != NULL);
return compile_gvar_assignment(node,
compile_node(node->nd_value));
}
break;
case NODE_CVAR:
assert(node->nd_vid > 0);
return compile_cvar_get(node->nd_vid, true);
case NODE_CVASGN:
assert(node->nd_vid > 0);
assert(node->nd_value != NULL);
return compile_cvar_assignment(node->nd_vid,
compile_node(node->nd_value));
case NODE_MASGN:
{
NODE *rhsn = node->nd_value;
assert(rhsn != NULL);
Value *ary = compile_node(rhsn);
return compile_multiple_assignment(node, ary);
}
break;
case NODE_DASGN:
case NODE_DASGN_CURR:
case NODE_LASGN:
{
assert(node->nd_vid > 0);
assert(node->nd_value != NULL);
Value *new_val = compile_node(node->nd_value);
new StoreInst(new_val, compile_lvar_slot(node->nd_vid), bb);
return new_val;
}
break;
case NODE_OP_ASGN_OR:
{
assert(node->nd_recv != NULL);
assert(node->nd_value != NULL);
Value *recvVal;
if (nd_type(node->nd_recv) == NODE_CVAR) {
// @@foo ||= 42
// We need to compile the class variable retrieve to not
// raise an exception in case the variable has never been
// defined yet.
assert(node->nd_recv->nd_vid > 0);
recvVal = compile_cvar_get(node->nd_recv->nd_vid, false);
}
else {
recvVal = compile_node(node->nd_recv);
}
Value *falseCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
recvVal, falseVal);
Function *f = bb->getParent();
BasicBlock *falseBB = BasicBlock::Create(context, "", f);
BasicBlock *elseBB = BasicBlock::Create(context, "", f);
BasicBlock *trueBB = BasicBlock::Create(context, "", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "", f);
BranchInst::Create(falseBB, trueBB, falseCond, bb);
bb = trueBB;
Value *nilCond = new ICmpInst(*bb, ICmpInst::ICMP_EQ, recvVal,
nilVal);
BranchInst::Create(falseBB, elseBB, nilCond, bb);
bb = falseBB;
Value *newRecvVal = compile_node(node->nd_value);
falseBB = bb;
BranchInst::Create(mergeBB, bb);
BranchInst::Create(mergeBB, elseBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(newRecvVal, falseBB);
pn->addIncoming(recvVal, elseBB);
return pn;
}
break;
case NODE_OP_ASGN_AND:
{
assert(node->nd_recv != NULL);
assert(node->nd_value != NULL);
Value *recvVal = compile_node(node->nd_recv);
Function *f = bb->getParent();
BasicBlock *notNilBB = BasicBlock::Create(context, "", f);
BasicBlock *elseBB = BasicBlock::Create(context, "", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "", f);
compile_boolean_test(recvVal, notNilBB, elseBB);
bb = notNilBB;
Value *newRecvVal = compile_node(node->nd_value);
notNilBB = bb;
BranchInst::Create(mergeBB, bb);
BranchInst::Create(mergeBB, elseBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(newRecvVal, notNilBB);
pn->addIncoming(recvVal, elseBB);
return pn;
}
break;
case NODE_OP_ASGN1:
case NODE_OP_ASGN2:
{
assert(node->nd_recv != NULL);
Value *recv = compile_node(node->nd_recv);
long type = nd_type(node) == NODE_OP_ASGN1
? node->nd_mid : node->nd_next->nd_mid;
// a=[0] += 42
//
// tmp = a.send(:[], 0)
// tmp = tmp + 42
// a.send(:[]=, 0, tmp)
assert(node->nd_args != NULL);
assert(node->nd_args->nd_head != NULL);
// tmp = a.send(:[], 0)
std::vector<Value *> params;
SEL sel;
if (nd_type(node) == NODE_OP_ASGN1) {
sel = selAREF;
}
else {
assert(node->nd_next->nd_vid > 0);
sel = mid_to_sel(node->nd_next->nd_vid, 0);
}
params.push_back(compile_mcache(sel, false));
params.push_back(current_self);
params.push_back(recv);
params.push_back(compile_sel(sel));
params.push_back(compile_const_pointer(NULL));
params.push_back(ConstantInt::get(Int8Ty, 0));
int argc = 0;
std::vector<Value *> arguments;
if (nd_type(node) == NODE_OP_ASGN1) {
assert(node->nd_args->nd_body != NULL);
compile_dispatch_arguments(node->nd_args->nd_body,
arguments,
&argc);
}
params.push_back(ConstantInt::get(Int32Ty, argc));
for (std::vector<Value *>::iterator i = arguments.begin();
i != arguments.end(); ++i) {
params.push_back(*i);
}
Value *tmp = compile_optimized_dispatch_call(sel, argc, params);
if (tmp == NULL) {
tmp = compile_dispatch_call(params);
}
// tmp = tmp + 42
BasicBlock *mergeBB = NULL;
BasicBlock *touchedBB = NULL;
BasicBlock *untouchedBB = NULL;
Value *tmp2;
NODE *value = nd_type(node) == NODE_OP_ASGN1
? node->nd_args->nd_head : node->nd_value;
assert(value != NULL);
if (type == 0 || type == 1) {
// 0 means OR, 1 means AND
Function *f = bb->getParent();
touchedBB = BasicBlock::Create(context, "", f);
untouchedBB = BasicBlock::Create(context, "", f);
mergeBB = BasicBlock::Create(context, "merge", f);
if (type == 0) {
compile_boolean_test(tmp, untouchedBB, touchedBB);
}
else {
compile_boolean_test(tmp, touchedBB, untouchedBB);
}
BranchInst::Create(mergeBB, untouchedBB);
bb = touchedBB;
tmp2 = compile_node(value);
}
else {
ID mid = nd_type(node) == NODE_OP_ASGN1
? node->nd_mid : node->nd_next->nd_mid;
sel = mid_to_sel(mid, 1);
params.clear();
params.push_back(compile_mcache(sel, false));
params.push_back(current_self);
params.push_back(tmp);
params.push_back(compile_sel(sel));
params.push_back(compile_const_pointer(NULL));
params.push_back(ConstantInt::get(Int8Ty, 0));
params.push_back(ConstantInt::get(Int32Ty, 1));
params.push_back(compile_node(value));
tmp2 = compile_optimized_dispatch_call(sel, 1, params);
if (tmp2 == NULL) {
tmp2 = compile_dispatch_call(params);
}
}
// a.send(:[]=, 0, tmp)
if (nd_type(node) == NODE_OP_ASGN1) {
sel = selASET;
}
else {
assert(node->nd_next->nd_aid > 0);
sel = mid_to_sel(node->nd_next->nd_aid, 1);
}
params.clear();
params.push_back(compile_mcache(sel, false));
params.push_back(current_self);
params.push_back(recv);
params.push_back(compile_sel(sel));
params.push_back(compile_const_pointer(NULL));
params.push_back(ConstantInt::get(Int8Ty, 0));
argc++;
params.push_back(ConstantInt::get(Int32Ty, argc));
for (std::vector<Value *>::iterator i = arguments.begin();
i != arguments.end(); ++i) {
params.push_back(*i);
}
params.push_back(tmp2);
Value *ret = compile_optimized_dispatch_call(sel, argc, params);
if (ret == NULL) {
ret = compile_dispatch_call(params);
}
if (mergeBB == NULL) {
return ret;
}
// compile_dispatch_call can create a new BasicBlock
// so we have to get bb just after
touchedBB = bb;
BranchInst::Create(mergeBB, touchedBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(tmp, untouchedBB);
pn->addIncoming(ret, touchedBB);
return pn;
}
break;
case NODE_XSTR:
case NODE_DXSTR:
{
Value *str;
if (nd_type(node) == NODE_DXSTR) {
str = compile_dstr(node);
}
else {
assert(node->nd_lit != 0);
str = compile_literal(node->nd_lit);
}
std::vector<Value *> params;
params.push_back(compile_mcache(selBackquote, false));
params.push_back(current_self);
params.push_back(current_self);
params.push_back(compile_sel(selBackquote));
params.push_back(compile_const_pointer(NULL));
params.push_back(ConstantInt::get(Int8Ty, DISPATCH_FCALL));
params.push_back(ConstantInt::get(Int32Ty, 1));
params.push_back(str);
return compile_dispatch_call(params);
}
break;
case NODE_DSTR:
return compile_dstr(node);
case NODE_DREGX:
case NODE_DREGX_ONCE: // TODO optimize NODE_DREGX_ONCE
{
Value *val = compile_dstr(node);
const int flag = node->nd_cflag;
if (newRegexpFunc == NULL) {
newRegexpFunc = cast<Function>(module->getOrInsertFunction(
"rb_reg_new_str",
RubyObjTy, RubyObjTy, Int32Ty, NULL));
}
std::vector<Value *> params;
params.push_back(val);
params.push_back(ConstantInt::get(Int32Ty, flag));
return compile_protected_call(newRegexpFunc, params);
}
break;
case NODE_DSYM:
{
Value *val = compile_dstr(node);
if (strInternFunc == NULL) {
strInternFunc = cast<Function>(module->getOrInsertFunction("rb_str_intern_fast",
RubyObjTy, RubyObjTy, NULL));
}
std::vector<Value *> params;
params.push_back(val);
return compile_protected_call(strInternFunc, params);
}
break;
case NODE_EVSTR:
{
assert(node->nd_body != NULL);
return compile_node(node->nd_body);
}
break;
case NODE_OR:
{
NODE *left = node->nd_1st;
assert(left != NULL);
NODE *right = node->nd_2nd;
assert(right != NULL);
Function *f = bb->getParent();
BasicBlock *leftNotFalseBB = BasicBlock::Create(context,
"left_not_false", f);
BasicBlock *leftNotTrueBB = BasicBlock::Create(context,
"left_not_true", f);
BasicBlock *leftTrueBB = BasicBlock::Create(context,
"left_is_true", f);
BasicBlock *rightNotFalseBB = BasicBlock::Create(context,
"right_not_false", f);
BasicBlock *rightTrueBB = BasicBlock::Create(context,
"right_is_true", f);
BasicBlock *failBB = BasicBlock::Create(context, "fail", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "merge", f);
Value *leftVal = compile_node(left);
Value *leftNotFalseCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
leftVal, falseVal);
BranchInst::Create(leftNotFalseBB, leftNotTrueBB,
leftNotFalseCond, bb);
bb = leftNotFalseBB;
Value *leftNotNilCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
leftVal, nilVal);
BranchInst::Create(leftTrueBB, leftNotTrueBB, leftNotNilCond,
bb);
bb = leftNotTrueBB;
Value *rightVal = compile_node(right);
Value *rightNotFalseCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
rightVal, falseVal);
BranchInst::Create(rightNotFalseBB, failBB, rightNotFalseCond,
bb);
bb = rightNotFalseBB;
Value *rightNotNilCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
rightVal, nilVal);
BranchInst::Create(rightTrueBB, failBB, rightNotNilCond, bb);
BranchInst::Create(mergeBB, leftTrueBB);
BranchInst::Create(mergeBB, rightTrueBB);
BranchInst::Create(mergeBB, failBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", mergeBB);
pn->addIncoming(leftVal, leftTrueBB);
pn->addIncoming(rightVal, rightTrueBB);
pn->addIncoming(rightVal, failBB);
return pn;
}
break;
case NODE_AND:
{
NODE *left = node->nd_1st;
assert(left != NULL);
NODE *right = node->nd_2nd;
assert(right != NULL);
Function *f = bb->getParent();
BasicBlock *leftNotFalseBB = BasicBlock::Create(context,
"left_not_false", f);
BasicBlock *leftTrueBB = BasicBlock::Create(context,
"left_is_true", f);
BasicBlock *rightNotFalseBB = BasicBlock::Create(context,
"right_not_false", f);
BasicBlock *leftFailBB = BasicBlock::Create(context,
"left_fail", f);
BasicBlock *rightFailBB = BasicBlock::Create(context,
"right_fail", f);
BasicBlock *successBB = BasicBlock::Create(context, "success",
f);
BasicBlock *mergeBB = BasicBlock::Create(context, "merge", f);
Value *leftVal = compile_node(left);
Value *leftNotFalseCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
leftVal, falseVal);
BranchInst::Create(leftNotFalseBB, leftFailBB,
leftNotFalseCond, bb);
bb = leftNotFalseBB;
Value *leftNotNilCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
leftVal, nilVal);
BranchInst::Create(leftTrueBB, leftFailBB, leftNotNilCond, bb);
bb = leftTrueBB;
Value *rightVal = compile_node(right);
Value *rightNotFalseCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
rightVal, falseVal);
BranchInst::Create(rightNotFalseBB, rightFailBB, rightNotFalseCond, bb);
bb = rightNotFalseBB;
Value *rightNotNilCond = new ICmpInst(*bb, ICmpInst::ICMP_NE,
rightVal, nilVal);
BranchInst::Create(successBB, rightFailBB, rightNotNilCond, bb);
BranchInst::Create(mergeBB, successBB);
BranchInst::Create(mergeBB, leftFailBB);
BranchInst::Create(mergeBB, rightFailBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", mergeBB);
pn->addIncoming(leftVal, leftFailBB);
pn->addIncoming(rightVal, rightFailBB);
pn->addIncoming(rightVal, successBB);
return pn;
}
break;
case NODE_IF:
{
Value *condVal = compile_node(node->nd_cond);
Function *f = bb->getParent();
BasicBlock *thenBB = BasicBlock::Create(context, "then", f);
BasicBlock *elseBB = BasicBlock::Create(context, "else", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "merge", f);
compile_boolean_test(condVal, thenBB, elseBB);
bb = thenBB;
DEBUG_LEVEL_INC();
Value *thenVal = node->nd_body != NULL ? compile_node(node->nd_body) : nilVal;
DEBUG_LEVEL_DEC();
thenBB = bb;
BranchInst::Create(mergeBB, thenBB);
bb = elseBB;
DEBUG_LEVEL_INC();
Value *elseVal = node->nd_else != NULL ? compile_node(node->nd_else) : nilVal;
DEBUG_LEVEL_DEC();
elseBB = bb;
BranchInst::Create(mergeBB, elseBB);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "iftmp", mergeBB);
pn->addIncoming(thenVal, thenBB);
pn->addIncoming(elseVal, elseBB);
return pn;
}
break;
case NODE_CLASS:
case NODE_SCLASS:
case NODE_MODULE:
{
assert(node->nd_cpath != NULL);
Value *classVal;
if (nd_type(node) == NODE_SCLASS) {
classVal =
compile_singleton_class(compile_node(node->nd_recv));
}
else {
assert(node->nd_cpath->nd_mid > 0);
ID path = node->nd_cpath->nd_mid;
NODE *super = node->nd_super;
if (defineClassFunc == NULL) {
// VALUE rb_vm_define_class(ID path, VALUE outer,
// VALUE super, int flags,
// unsigned char dynamic_class);
defineClassFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_define_class",
RubyObjTy, IntTy, RubyObjTy, RubyObjTy,
Int32Ty, Int8Ty, NULL));
}
std::vector<Value *> params;
bool outer = false;
params.push_back(compile_id(path));
params.push_back(compile_class_path(node->nd_cpath, &outer));
params.push_back(super == NULL ? zeroVal : compile_node(super));
int flags = 0;
if (nd_type(node) == NODE_MODULE) {
flags |= DEFINE_MODULE;
}
if (outer) {
flags |= DEFINE_OUTER;
}
params.push_back(ConstantInt::get(Int32Ty, flags));
params.push_back(ConstantInt::get(Int8Ty,
outer && dynamic_class ? 1 : 0));
classVal = compile_protected_call(defineClassFunc, params);
}
NODE *body = node->nd_body;
if (body != NULL) {
assert(nd_type(body) == NODE_SCOPE);
ID *tbl = body->nd_tbl;
if (tbl != NULL) {
const int args_count = (int)tbl[0];
compile_lvars(&tbl[args_count + 1]);
}
if (body->nd_body != NULL) {
Value *old_self = current_self;
current_self = classVal;
GlobalVariable *old_class = current_opened_class;
current_opened_class = new GlobalVariable(
*RoxorCompiler::module, RubyObjTy, false,
GlobalValue::InternalLinkage, nilVal, "");
bool old_current_module = current_module;
std::map<ID, Value *> old_ivar_slots_cache
= ivar_slots_cache;
ivar_slots_cache.clear();
new StoreInst(classVal, current_opened_class, bb);
current_module = nd_type(node) == NODE_MODULE;
compile_set_current_scope(classVal, publicScope);
bool old_dynamic_class = dynamic_class;
dynamic_class = false;
Value *val = compile_node(body->nd_body);
dynamic_class = old_dynamic_class;
compile_set_current_scope(classVal, defaultScope);
BasicBlock::InstListType &list = bb->getInstList();
compile_ivar_slots(classVal, list, list.end());
current_self = old_self;
current_opened_class = old_class;
current_module = old_current_module;
ivar_slots_cache = old_ivar_slots_cache;
return val;
}
}
return nilVal;
}
break;
case NODE_SUPER:
case NODE_ZSUPER:
case NODE_CALL:
case NODE_FCALL:
case NODE_VCALL:
{
NODE *recv;
NODE *args;
ID mid;
recv = node->nd_recv;
args = node->nd_args;
mid = node->nd_mid;
if (nd_type(node) == NODE_CALL) {
assert(recv != NULL);
}
else {
assert(recv == NULL);
}
const bool block_given = current_block_func != NULL
&& current_block_node != NULL;
const bool super_call = nd_type(node) == NODE_SUPER
|| nd_type(node) == NODE_ZSUPER;
if (super_call) {
mid = current_mid;
}
else {
assert(mid > 0);
}
Function::ArgumentListType &fargs =
bb->getParent()->getArgumentList();
const int fargs_arity = fargs.size() - 2;
bool splat_args = false;
bool positive_arity = false;
if (nd_type(node) == NODE_ZSUPER) {
assert(args == NULL);
positive_arity = fargs_arity > 0;
}
else {
NODE *n = args;
rescan_args:
if (n != NULL) {
switch (nd_type(n)) {
case NODE_ARRAY:
positive_arity = n->nd_alen > 0;
break;
case NODE_SPLAT:
case NODE_ARGSPUSH:
case NODE_ARGSCAT:
splat_args = true;
positive_arity = true;
break;
case NODE_BLOCK_PASS:
n = n->nd_head;
if (n != NULL) {
goto rescan_args;
}
positive_arity = false;
break;
default:
compile_node_error("invalid call args", n);
}
}
}
// Recursive method call optimization.
if (!block_given && !super_call && !splat_args
&& positive_arity && mid == current_mid && recv == NULL) {
Function *f = bb->getParent();
const unsigned long argc =
args == NULL ? 0 : args->nd_alen;
if (f->arg_size() - 2 == argc) {
std::vector<Value *> params;
Function::arg_iterator arg = f->arg_begin();
params.push_back(arg++); // self
params.push_back(arg++); // sel
for (NODE *n = args; n != NULL; n = n->nd_next) {
params.push_back(compile_node(n->nd_head));
}
CallInst *inst = CallInst::Create(f, params.begin(),
params.end(), "", bb);
inst->setTailCall(true);
return cast<Value>(inst);
}
}
// Let's set the block state as NULL temporarily, when we
// compile the receiver and the arguments.
Function *old_current_block_func = current_block_func;
NODE *old_current_block_node = current_block_node;
current_block_func = NULL;
current_block_node = NULL;
// Prepare the dispatcher parameters.
std::vector<Value *> params;
// Method cache (and prepare the selector).
Value *sel_val;
SEL sel;
if (mid != 0) {
sel = mid_to_sel(mid, positive_arity ? 1 : 0);
params.push_back(compile_mcache(sel, super_call));
sel_val = compile_sel(sel);
}
else {
assert(super_call);
sel = 0;
// A super call outside a method definition (probably
// in a block). Retrieve the SEL as the second parameter
// of the current function.
Function *f = bb->getParent();
Function::arg_iterator arg = f->arg_begin();
arg++; // skip self
sel_val = arg;
params.push_back(compile_get_mcache(sel_val, true));
}
// Top.
params.push_back(current_self);
// Self.
params.push_back(recv == NULL ? current_self
: compile_node(recv));
// Selector.
params.push_back(sel_val);
// RubySpec requires that we compile the block *after* the
// arguments, so we do pass NULL as the block for the moment.
params.push_back(compile_const_pointer(NULL));