Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

7306 lines (6247 sloc) 196.424 kb
/*
* MacRuby Compiler.
*
* This file is covered by the Ruby license. See COPYING for more details.
*
* Copyright (C) 2008-2011, Apple Inc. All rights reserved.
*/
#if !defined(MACRUBY_STATIC)
#define ROXOR_COMPILER_DEBUG 0
#if !defined(DW_LANG_Ruby)
# define DW_LANG_Ruby 0x15 // TODO: Python is 0x14, request a real number
#endif
#include <llvm/LLVMContext.h>
#include <llvm/Transforms/Utils/Cloning.h>
#include "llvm.h"
#include "macruby_internal.h"
#include "ruby/encoding.h"
#include "ruby/node.h"
#include "id.h"
#include "vm.h"
#include "compiler.h"
#include "objc.h"
#include "version.h"
#include "encoding.h"
#include "re.h"
#include "bs.h"
#include "class.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;
#define __save_state(type, var) \
type __old__##var = var
#define __restore_state(var) \
var = __old__##var
#define save_compiler_state() \
__save_state(unsigned int, current_line);\
__save_state(BasicBlock *, bb);\
__save_state(BasicBlock *, entry_bb);\
__save_state(ID, current_mid);\
__save_state(rb_vm_arity_t, current_arity);\
__save_state(ID, self_id);\
__save_state(Value *, current_self);\
__save_state(bool, current_block);\
__save_state(bool, current_block_chain);\
__save_state(Value *, current_var_uses);\
__save_state(Value *, running_block);\
__save_state(Value *, current_block_arg);\
__save_state(BasicBlock *, begin_bb);\
__save_state(BasicBlock *, rescue_invoke_bb);\
__save_state(BasicBlock *, rescue_rethrow_bb);\
__save_state(BasicBlock *, ensure_bb);\
__save_state(bool, current_rescue);\
__save_state(NODE *, current_block_node);\
__save_state(Function *, current_block_func);\
__save_state(Function *, current_non_block_func);\
__save_state(GlobalVariable *, current_opened_class);\
__save_state(bool, dynamic_class);\
__save_state(BasicBlock *, current_loop_begin_bb);\
__save_state(BasicBlock *, current_loop_body_bb);\
__save_state(BasicBlock *, current_loop_end_bb);\
__save_state(PHINode *, current_loop_exit_val);\
__save_state(int, return_from_block);\
__save_state(int, return_from_block_ids);\
__save_state(PHINode *, ensure_pn);\
__save_state(NODE *, ensure_node);\
__save_state(bool, block_declaration);\
__save_state(AllocaInst *, argv_buffer);\
__save_state(GlobalVariable *, outer_stack);
#define restore_compiler_state() \
__restore_state(current_line);\
__restore_state(bb);\
__restore_state(entry_bb);\
__restore_state(current_mid);\
__restore_state(current_arity);\
__restore_state(self_id);\
__restore_state(current_self);\
__restore_state(current_block);\
__restore_state(current_block_chain);\
__restore_state(current_var_uses);\
__restore_state(running_block);\
__restore_state(current_block_arg);\
__restore_state(begin_bb);\
__restore_state(rescue_invoke_bb);\
__restore_state(rescue_rethrow_bb);\
__restore_state(ensure_bb);\
__restore_state(current_rescue);\
__restore_state(current_block_node);\
__restore_state(current_block_func);\
__restore_state(current_non_block_func);\
__restore_state(current_opened_class);\
__restore_state(dynamic_class);\
__restore_state(current_loop_begin_bb);\
__restore_state(current_loop_body_bb);\
__restore_state(current_loop_end_bb);\
__restore_state(current_loop_exit_val);\
__restore_state(return_from_block);\
__restore_state(return_from_block_ids);\
__restore_state(ensure_pn);\
__restore_state(ensure_node);\
__restore_state(block_declaration);\
__restore_state(argv_buffer);\
__restore_state(outer_stack);
#define reset_compiler_state() \
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);\
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_non_block_func = NULL;\
current_opened_class = NULL;\
dynamic_class = 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;\
ensure_node = NULL;\
block_declaration = false;\
argv_buffer = NULL;\
outer_stack = NULL;
RoxorCompiler::RoxorCompiler(bool _debug_mode)
{
assert(RoxorCompiler::module != NULL);
#if !defined(LLVM_TOT)
debug_info = new DIBuilder(*RoxorCompiler::module);
#else
debug_info = new DIFactory(*RoxorCompiler::module);
#endif
can_interpret = false;
debug_mode = _debug_mode;
fname = "";
inside_eval = false;
current_line = 0;
outer_stack_uses = false;
reset_compiler_state();
writeBarrierFunc = get_function("vm_gc_wb");
dispatchFunc = get_function("vm_dispatch");
fastPlusFunc = get_function("vm_fast_plus");
fastMinusFunc = get_function("vm_fast_minus");
fastMultFunc = get_function("vm_fast_mult");
fastDivFunc = get_function("vm_fast_div");
fastLtFunc = get_function("vm_fast_lt");
fastLeFunc = get_function("vm_fast_le");
fastGtFunc = get_function("vm_fast_gt");
fastGeFunc = get_function("vm_fast_ge");
fastEqFunc = get_function("vm_fast_eq");
fastNeqFunc = get_function("vm_fast_neq");
fastEqqFunc = get_function("vm_fast_eqq");
fastArefFunc = get_function("vm_fast_aref");
fastAsetFunc = get_function("vm_fast_aset");
fastShiftFunc = get_function("vm_fast_shift");
whenSplatFunc = get_function("vm_when_splat");
prepareBlockFunc = NULL;
pushBindingFunc = NULL;
getBlockFunc = get_function("vm_get_block");
currentBlockObjectFunc = NULL;
currentBlockFunc = NULL;
getConstFunc = get_function("vm_get_const");
setConstFunc = get_function("vm_set_const");
prepareMethodFunc = NULL;
singletonClassFunc = NULL;
defineClassFunc = NULL;
getIvarFunc = get_function("vm_ivar_get");
setIvarFunc = get_function("vm_ivar_set");
willChangeValueFunc = NULL;
didChangeValueFunc = NULL;
definedFunc = NULL;
undefFunc = NULL;
aliasFunc = NULL;
valiasFunc = NULL;
newHashFunc = get_function("vm_rhash_new");
storeHashFunc = get_function("vm_rhash_store");
toAFunc = get_function("vm_to_a");
toAryFunc = get_function("vm_to_ary");
catArrayFunc = get_function("vm_ary_cat");
dupArrayFunc = get_function("vm_ary_dup");
newArrayFunc = get_function("vm_rary_new");
entryArrayFunc = get_function("vm_ary_entry");
checkArrayFunc = get_function("vm_ary_check");
lengthArrayFunc = get_function("vm_ary_length");
ptrArrayFunc = get_function("vm_ary_ptr");
newStructFunc = NULL;
newOpaqueFunc = NULL;
newPointerFunc = NULL;
getStructFieldsFunc = NULL;
getOpaqueDataFunc = NULL;
getPointerPtrFunc = get_function("vm_rval_to_cptr");
xmallocFunc = NULL;
checkArityFunc = NULL;
setStructFunc = NULL;
newRangeFunc = NULL;
newRegexpFunc = NULL;
strInternFunc = NULL;
selToSymFunc = NULL;
keepVarsFunc = NULL;
masgnGetElemBeforeSplatFunc =
get_function("vm_masgn_get_elem_before_splat");
masgnGetElemAfterSplatFunc = get_function("vm_masgn_get_elem_after_splat");
masgnGetSplatFunc = get_function("vm_masgn_get_splat");
newStringFunc = NULL;
newString2Func = NULL;
newString3Func = NULL;
yieldFunc = get_function("vm_yield_args");
getBrokenFunc = get_function("vm_get_broken_value");
blockEvalFunc = NULL;
gvarSetFunc = NULL;
gvarGetFunc = NULL;
cvarSetFunc = get_function("vm_cvar_set");
cvarGetFunc = get_function("vm_cvar_get");
currentExceptionFunc = NULL;
popExceptionFunc = NULL;
getBackrefNth = NULL;
getBackrefSpecial = NULL;
breakFunc = NULL;
returnFromBlockFunc = NULL;
returnedFromBlockFunc = get_function("vm_returned_from_block");
checkReturnFromBlockFunc = NULL;
setHasEnsureFunc = NULL;
setScopeFunc = get_function("vm_set_current_scope");
setCurrentClassFunc = NULL;
pushOuterFunc = NULL;
popOuterFunc = NULL;
setCurrentOuterFunc = NULL;
debugTrapFunc = NULL;
getFFStateFunc = NULL;
setFFStateFunc = NULL;
releaseOwnershipFunc = get_function("vm_release_ownership");
ocvalToRvalFunc = get_function("vm_ocval_to_rval");
charToRvalFunc = get_function("vm_char_to_rval");
ucharToRvalFunc = get_function("vm_uchar_to_rval");
shortToRvalFunc = get_function("vm_short_to_rval");
ushortToRvalFunc = get_function("vm_ushort_to_rval");
intToRvalFunc = get_function("vm_int_to_rval");
uintToRvalFunc = get_function("vm_uint_to_rval");
longToRvalFunc = get_function("vm_long_to_rval");
ulongToRvalFunc = get_function("vm_ulong_to_rval");
longLongToRvalFunc = get_function("vm_long_long_to_rval");
ulongLongToRvalFunc = get_function("vm_ulong_long_to_rval");
floatToRvalFunc = get_function("vm_float_to_rval");
doubleToRvalFunc = get_function("vm_double_to_rval");
selToRvalFunc = get_function("vm_sel_to_rval");
charPtrToRvalFunc = get_function("vm_charptr_to_rval");
rvalToOcvalFunc = get_function("vm_rval_to_ocval");
rvalToBoolFunc = get_function("vm_rval_to_bool");
rvalToCharFunc = get_function("vm_rval_to_char");
rvalToUcharFunc = get_function("vm_rval_to_uchar");
rvalToShortFunc = get_function("vm_rval_to_short");
rvalToUshortFunc = get_function("vm_rval_to_ushort");
rvalToIntFunc = get_function("vm_rval_to_int");
rvalToUintFunc = get_function("vm_rval_to_uint");
rvalToLongFunc = get_function("vm_rval_to_long");
rvalToUlongFunc = get_function("vm_rval_to_ulong");
rvalToLongLongFunc = get_function("vm_rval_to_long_long");
rvalToUlongLongFunc = get_function("vm_rval_to_ulong_long");
rvalToFloatFunc = get_function("vm_rval_to_float");
rvalToDoubleFunc = get_function("vm_rval_to_double");
rvalToSelFunc = get_function("vm_rval_to_sel");
rvalToCharPtrFunc = get_function("vm_rval_to_charptr");
initBlockFunc = get_function("vm_init_c_block");
blockProcFunc = get_function("vm_ruby_block_literal_proc");
setCurrentMRIMethodContext = 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);
BitTy = Type::getInt1Ty(context);
BlockLiteralTy = module->getTypeByName("struct.ruby_block_literal");
assert(BlockLiteralTy != NULL);
#if ROXOR_COMPILER_DEBUG
level = 0;
#endif
#if LLVM_TOT
dbg_mdkind = context.getMDKindID("dbg");
assert(dbg_mdkind != 0);
#else
dbg_mdkind = LLVMContext::MD_dbg;
#endif
}
RoxorAOTCompiler::RoxorAOTCompiler(void)
: RoxorCompiler(false)
{
cObject_gvar = NULL;
cStandardError_gvar = NULL;
}
SEL
RoxorCompiler::mid_to_sel(ID mid, int arity)
{
SEL sel = rb_vm_id_to_sel(mid, arity);
if (rb_objc_ignored_sel(sel)) {
char buf[100];
snprintf(buf, sizeof buf, "__hidden__%s", rb_id2name(mid));
sel = sel_registerName(buf);
}
return sel;
}
Instruction *
RoxorCompiler::compile_protected_call(Value *imp, Value **args_begin,
Value **args_end)
{
if (rescue_invoke_bb == NULL) {
return CallInst::Create(imp, args_begin, args_end, "", bb);
}
else {
BasicBlock *normal_bb = BasicBlock::Create(context, "normal",
bb->getParent());
InvokeInst *dispatch = InvokeInst::Create(imp, normal_bb,
rescue_invoke_bb, args_begin, args_end, "", bb);
bb = normal_bb;
return dispatch;
}
}
Instruction *
RoxorCompiler::compile_protected_call(Value *imp, std::vector<Value *> &params)
{
if (rescue_invoke_bb == NULL) {
return CallInst::Create(imp, params.begin(), params.end(), "", bb);
}
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(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_when_splat(Value *comparedToVal, Value *splatVal)
{
if (comparedToVal == NULL) {
return splatVal;
}
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(selEqq, true);
Value *args[] = {
new LoadInst(is_redefined, "", bb),
comparedToVal,
splatVal
};
return compile_protected_call(whenSplatFunc, args, args + 3);
}
Instruction *
RoxorCompiler::compile_const_global_ustring(const UniChar *str,
const size_t len)
{
assert(len > 0);
const unsigned long hash = rb_str_hash_uchars(str, len);
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;
}
Value *idxs[] = {
ConstantInt::get(Int32Ty, 0),
ConstantInt::get(Int32Ty, 0)
};
return GetElementPtrInst::Create(gvar, idxs, idxs + 2, "", bb);
}
Instruction *
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;
}
Value *idxs[] = {
ConstantInt::get(Int32Ty, 0),
ConstantInt::get(Int32Ty, 0)
};
return GetElementPtrInst::Create(gvar, idxs, idxs + 2, "", 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);
}
static void
discover_stubs(std::map<SEL, std::vector<std::string> *> &map,
std::vector<std::string> &dest, SEL sel)
{
std::map<SEL, std::vector<std::string> *>::iterator iter;
iter = map.find(sel);
if (iter != map.end()) {
std::vector<std::string> *v = iter->second;
for (std::vector<std::string>::iterator i = v->begin();
i != v->end(); ++i) {
std::string s = *i;
if (std::find(dest.begin(), dest.end(), s) == dest.end()) {
dest.push_back(s);
}
}
}
}
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;
discover_stubs(bs_c_stubs_types, c_stubs, sel);
discover_stubs(bs_objc_stubs_types, objc_stubs, sel);
}
else {
gvar = iter->second;
}
return add_to_bb
? new LoadInst(gvar, "", bb)
: new LoadInst(gvar, "");
}
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_method_definition(NODE *node)
{
ID mid = node->nd_mid;
assert(mid > 0);
NODE *body = node->nd_defn;
assert(body != NULL);
const bool singleton_method = nd_type(node) == NODE_DEFS;
const ID old_current_mid = current_mid;
current_mid = mid;
const bool old_current_block_chain = current_block_chain;
current_block_chain = false;
const bool old_block_declaration = block_declaration;
block_declaration = false;
const bool old_should_interpret = should_interpret;
DEBUG_LEVEL_INC();
Value *val = compile_node(body);
assert(Function::classof(val));
Function *func = cast<Function>(val);
DEBUG_LEVEL_DEC();
should_interpret = old_should_interpret;
block_declaration = old_block_declaration;
current_block_chain = old_current_block_chain;
current_mid = old_current_mid;
Value *classVal;
if (singleton_method) {
assert(node->nd_recv != NULL);
classVal = compile_singleton_class(compile_node(node->nd_recv));
}
else {
classVal = compile_current_class();
}
rb_vm_arity_t arity = rb_vm_node_arity(body);
const SEL sel = mid_to_sel(mid, arity.real);
compile_prepare_method(classVal, compile_sel(sel), singleton_method,
func, arity, body);
can_interpret = true;
}
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));
}
Value *args[] = {
classVal,
ConstantInt::get(Int8Ty, !singleton && dynamic_class ? 1 : 0),
sel,
compile_const_pointer(new_function),
compile_arity(arity),
ConstantInt::get(Int32Ty, rb_vm_node_flags(body))
};
CallInst::Create(prepareMethodFunc, args, args + 6, "", bb);
}
void
RoxorAOTCompiler::compile_prepare_method(Value *classVal, Value *sel,
bool singleton, Function *func, 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, ...)
std::vector<const Type *> types;
types.push_back(RubyObjTy);
types.push_back(Int8Ty);
types.push_back(PtrTy);
types.push_back(PtrTy);
types.push_back(Int64Ty);
types.push_back(Int32Ty);
FunctionType *ft = FunctionType::get(VoidTy, types, true);
prepareMethodFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_prepare_method2", ft));
}
const unsigned char dyn_class = !singleton && dynamic_class ? 1 : 0;
std::vector<Value *> params;
params.push_back(classVal);
params.push_back(ConstantInt::get(Int8Ty, dyn_class));
params.push_back(sel);
params.push_back(new BitCastInst(func, PtrTy, "", bb));
params.push_back(compile_arity(arity));
params.push_back(ConstantInt::get(Int32Ty, rb_vm_node_flags(body)));
// Pre-compile a generic Objective-C stub, where all arguments and return
// value are Ruby types.
char types[100];
types[0] = '@';
types[1] = '@';
types[2] = ':';
assert(arity.real < (int)sizeof(types) - 3);
for (int i = 0; i < arity.real; i++) {
types[3 + i] = '@';
}
types[arity.real + 3] = '\0';
params.push_back(compile_const_global_string(types));
Function *stub = compile_objc_stub(func, NULL, arity, types);
params.push_back(new BitCastInst(stub, PtrTy, "", bb));
params.push_back(compile_const_pointer(NULL));
CallInst::Create(prepareMethodFunc, params.begin(), params.end(), "", bb);
}
void
RoxorCompiler::attach_current_line_metadata(Instruction *insn)
{
if (fname != NULL) {
#if !defined(LLVM_TOT)
Value *args[] = {
ConstantInt::get(Int32Ty, current_line),
ConstantInt::get(Int32Ty, 0),
debug_compile_unit,
DILocation(NULL)
};
DILocation loc = DILocation(MDNode::get(context, args, 4));
#else
DILocation loc = debug_info->CreateLocation(current_line, 0,
debug_compile_unit, DILocation(NULL));
#endif
insn->setMetadata(dbg_mdkind, loc);
}
}
void
RoxorCompiler::generate_location_path(std::string &path, DILocation loc)
{
if (loc.getFilename() == "-e") {
path.append(loc.getFilename());
}
else {
path.append(loc.getDirectory());
path.append("/");
path.append(loc.getFilename());
}
}
Value *
RoxorCompiler::compile_argv_buffer(const long argc)
{
if (argc == 0) {
return compile_const_pointer(NULL, RubyObjPtrTy);
}
assert(argc > 0);
Instruction *first = bb->getParent()->getEntryBlock().getFirstNonPHI();
if (argv_buffer == NULL) {
argv_buffer = new AllocaInst(RubyObjTy,
ConstantInt::get(Int32Ty, argc), "argv", first);
}
else {
Value *size = argv_buffer->getArraySize();
if (argc > cast<ConstantInt>(size)->getSExtValue()) {
AllocaInst *new_argv = new AllocaInst(RubyObjTy,
ConstantInt::get(Int32Ty, argc), "argv", first);
argv_buffer->replaceAllUsesWith(new_argv);
argv_buffer->eraseFromParent();
argv_buffer = new_argv;
}
}
return argv_buffer;
}
Value *
RoxorCompiler::recompile_dispatch_argv(std::vector<Value *> &params, int offset)
{
const long argc = params.size() - offset;
Value *argv = compile_argv_buffer(argc);
for (int i = 0; i < argc; i++) {
Value *idx = ConstantInt::get(Int32Ty, i);
Value *slot = GetElementPtrInst::Create(argv, idx, "", bb);
new StoreInst(params[offset + i], slot, bb);
}
return argv;
}
Value *
RoxorCompiler::compile_dispatch_call(std::vector<Value *> &params)
{
Value *argv = recompile_dispatch_argv(params, 6);
params.erase(params.begin() + 6, params.end());
params.push_back(argv);
Instruction *insn = compile_protected_call(dispatchFunc, params);
attach_current_line_metadata(insn);
return insn;
}
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(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_FCALL;
}
if (argc < (int)args.size()) {
opt |= DISPATCH_SPLAT;
}
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);
}
// The return value of these assignments is always the new value.
Value *retval = params.back();
if (compile_optimized_dispatch_call(sel, argc, params) == NULL) {
compile_dispatch_call(params);
}
return retval;
}
void
RoxorCompiler::compile_multiple_assignment_element(NODE *node, Value *val)
{
switch (nd_type(node)) {
case NODE_LASGN:
case NODE_DASGN:
case NODE_DASGN_CURR:
new StoreInst(val, compile_lvar_slot(node->nd_vid), 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);
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;
}
val = CallInst::Create(toAryFunc, val, "", bb);
NODE *l = before_splat;
for (int i = 0; l != NULL; ++i) {
Value *args[] = {
val,
ConstantInt::get(Int32Ty, i)
};
Value *elt = CallInst::Create(masgnGetElemBeforeSplatFunc,
args, args + 2, "", bb);
compile_multiple_assignment_element(l->nd_head, elt);
l = l->nd_next;
}
if (splat != NULL && splat != (NODE *)-1) {
Value *args[] = {
val,
ConstantInt::get(Int32Ty, before_splat_count),
ConstantInt::get(Int32Ty, after_splat_count)
};
Value *elt = CallInst::Create(masgnGetSplatFunc, args, args + 3,
"", bb);
compile_multiple_assignment_element(splat, elt);
}
l = after_splat;
for (int i = 0; l != NULL; ++i) {
Value *args[] = {
val,
ConstantInt::get(Int32Ty, before_splat_count),
ConstantInt::get(Int32Ty, after_splat_count),
ConstantInt::get(Int32Ty, i)
};
Value *elt = CallInst::Create(masgnGetElemAfterSplatFunc,
args, args + 4, "", bb);
compile_multiple_assignment_element(l->nd_head, elt);
l = l->nd_next;
}
return val;
}
Value *
RoxorCompiler::compile_multiple_assignment(NODE *node)
{
assert(node->nd_value != NULL);
if (node->nd_next == NULL
&& node->nd_head != NULL
&& nd_type(node->nd_head) == NODE_ARRAY
&& nd_type(node->nd_value) == NODE_ARRAY
&& node->nd_head->nd_alen == node->nd_value->nd_alen) {
// Symetric multiple-assignment optimization.
// Grab all left operands in separate Allocas.
Instruction *first = bb->getParent()->getEntryBlock().getFirstNonPHI();
std::vector<Value *> tmp_rights;
NODE *right = node->nd_value;
for (int i = 0; i < node->nd_head->nd_alen; i++) {
Value *slot = new AllocaInst(RubyObjTy, "", first);
assert(right->nd_head != NULL);
new StoreInst(compile_node(right->nd_head), slot, bb);
tmp_rights.push_back(new LoadInst(slot, "", bb));
right = right->nd_next;
}
// Compile assignments.
NODE *left = node->nd_head;
for (std::vector<Value *>::iterator i = tmp_rights.begin();
i != tmp_rights.end(); ++i) {
assert(left->nd_head != NULL);
compile_multiple_assignment_element(left->nd_head, *i);
left = left->nd_next;
}
// Compile return value (a new Array) which is eliminated later if
// never used.
const int argc = tmp_rights.size();
Value *argv = compile_argv_buffer(argc);
for (int i = 0; i < argc; i++) {
Value *idx = ConstantInt::get(Int32Ty, i);
Value *slot = GetElementPtrInst::Create(argv, idx, "", bb);
new StoreInst(tmp_rights[i], slot, bb);
}
Value *args[] = {
ConstantInt::get(Int32Ty, argc),
argv
};
CallInst *ary = CallInst::Create(newArrayFunc, args, args + 2, "", bb);
RoxorCompiler::MAsgnValue val;
val.ary = ary;
masgn_values.push_back(val);
return ary;
}
return compile_multiple_assignment(node, compile_node(node->nd_value));
}
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;
return new BitCastInst(func, PtrTy, "", bb);
}
Value *
RoxorCompiler::compile_block_get(Value *block_object)
{
Value *args[] = { block_object };
return compile_protected_call(getBlockFunc, args, args + 1);
}
Value *
RoxorCompiler::compile_prepare_block(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_block(NODE *node)
{
std::vector<ID> old_dvars = dvars;
BasicBlock *old_current_loop_begin_bb = current_loop_begin_bb;
BasicBlock *old_current_loop_body_bb = current_loop_body_bb;
BasicBlock *old_current_loop_end_bb = current_loop_end_bb;
current_loop_begin_bb = current_loop_end_bb = NULL;
Function *old_current_block_func = current_block_func;
NODE *old_current_block_node = current_block_node;
bool old_current_block = current_block;
bool old_current_block_chain = current_block_chain;
int old_return_from_block = return_from_block;
bool old_dynamic_class = dynamic_class;
bool old_block_declaration = block_declaration;
current_block = true;
current_block_chain = true;
dynamic_class = true;
block_declaration = true;
assert(node->nd_body != NULL);
Value *block = compile_node(node->nd_body);
assert(Function::classof(block));
block_declaration = old_block_declaration;
dynamic_class = old_dynamic_class;
BasicBlock *return_from_block_bb = NULL;
if (!old_current_block_chain && return_from_block != -1) {
// The block we just compiled contains one or more return expressions!
// We need to enclose further dispatcher calls inside an exception
// handler, since return-from-block may use a C++ exception.
Function *f = bb->getParent();
rescue_invoke_bb = return_from_block_bb =
BasicBlock::Create(context, "return-from-block", f);
}
current_loop_begin_bb = old_current_loop_begin_bb;
current_loop_body_bb = old_current_loop_body_bb;
current_loop_end_bb = old_current_loop_end_bb;
current_block = old_current_block;
current_block_chain = old_current_block_chain;
current_block_func = cast<Function>(block);
current_block_node = node->nd_body;
const int node_type = nd_type(node);
const bool is_lambda = node_type == NODE_LAMBDA;
Value *caller;
if (!is_lambda) {
assert(node->nd_iter != NULL);
}
if (node_type == NODE_ITER) {
caller = compile_node(node->nd_iter);
}
else {
// Dispatch #each on the receiver.
std::vector<Value *> params;
params.push_back(current_self);
if (!is_lambda) {
// The block must not be passed to the code that generates the
// values we loop on.
current_block_func = NULL;
current_block_node = NULL;
params.push_back(compile_node(node->nd_iter));
current_block_func = cast<Function>(block);
current_block_node = node->nd_body;
}
else {
params.push_back(current_self);
}
params.push_back(compile_sel((is_lambda ? selLambda : selEach)));
params.push_back(compile_prepare_block());
int opt = 0;
if (is_lambda) {
opt = DISPATCH_FCALL;
}
params.push_back(ConstantInt::get(Int8Ty, opt));
params.push_back(ConstantInt::get(Int32Ty, 0));
caller = compile_dispatch_call(params);
}
const int block_id = return_from_block_bb != NULL
? return_from_block : -1;
Value *retval_block = CallInst::Create(returnedFromBlockFunc,
ConstantInt::get(Int32Ty, block_id), "", bb);
Value *is_returned = new ICmpInst(*bb, ICmpInst::ICMP_NE, retval_block,
undefVal);
Function *f = bb->getParent();
BasicBlock *return_bb = BasicBlock::Create(context, "", f);
BasicBlock *next_bb = BasicBlock::Create(context, "", f);
BranchInst::Create(return_bb, next_bb, is_returned, bb);
bb = return_bb;
ReturnInst::Create(context, retval_block, bb);
bb = next_bb;
if (return_from_block_bb != NULL) {
BasicBlock *old_bb = bb;
bb = return_from_block_bb;
compile_return_from_block_handler(return_from_block);
bb = old_bb;
return_from_block = old_return_from_block;
}
current_block_func = old_current_block_func;
current_block_node = old_current_block_node;
dvars = old_dvars;
return caller;
}
Value *
RoxorCompiler::compile_binding(void)
{
if (pushBindingFunc == NULL) {
// void rb_vm_push_binding(VALUE self, rb_vm_block_t *current_block,
// rb_vm_binding_t *top_binding, unsigned char dynamic_class,
// rb_vm_outer_t *outer_stack, 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(PtrTy);
types.push_back(Int8Ty);
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);
params.push_back(compile_const_pointer(rb_vm_current_binding()));
params.push_back(ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0));
params.push_back(compile_outer_stack());
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::compile_slot_cache(ID id)
{
std::map<ID, void *>::iterator iter = ivars_slots_cache.find(id);
void *cache = NULL;
if (iter == ivars_slots_cache.end()) {
cache = rb_vm_ivar_slot_allocate();
ivars_slots_cache[id] = cache;
}
else {
cache = iter->second;
}
return compile_const_pointer(cache);
}
Value *
RoxorAOTCompiler::compile_slot_cache(ID id)
{
std::map<ID, void *>::iterator iter = ivars_slots_cache.find(id);
GlobalVariable *gvar = NULL;
if (iter == ivars_slots_cache.end()) {
gvar = new GlobalVariable(*RoxorCompiler::module,
PtrTy, false, GlobalValue::InternalLinkage,
compile_const_pointer(NULL), "");
ivar_slots.push_back(gvar);
ivars_slots_cache[id] = gvar;
}
else {
gvar = (GlobalVariable *)iter->second;
}
return new LoadInst(gvar, "", bb);
}
Value *
RoxorCompiler::compile_ivar_get(ID vid)
{
Value *args[] = {
current_self,
compile_id(vid),
compile_slot_cache(vid)
};
return CallInst::Create(getIvarFunc, args, args + 3, "", bb);
}
Value *
RoxorCompiler::compile_ivar_assignment(ID vid, Value *val)
{
Value *args[] = {
current_self,
compile_id(vid),
val,
compile_slot_cache(vid)
};
CallInst::Create(setIvarFunc, args, args + 4, "", bb);
return val;
}
Value *
RoxorCompiler::compile_cvar_get(ID id, bool check)
{
Value *args[] = {
compile_current_class(),
compile_id(id),
ConstantInt::get(Int8Ty, check ? 1 : 0),
ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0)
};
return compile_protected_call(cvarGetFunc, args, args + 4);
}
Value *
RoxorCompiler::compile_cvar_assignment(ID name, Value *val)
{
Value *args[] = {
compile_current_class(),
compile_id(name),
val,
ConstantInt::get(Int8Ty, dynamic_class ? 1 : 0)
};
return CallInst::Create(cvarSetFunc, args, args + 4, "", bb);
}
Value *
RoxorCompiler::compile_gvar_assignment(NODE *node, Value *val)
{
assert(node->nd_vid > 0);
assert(node->nd_entry != NULL);
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));
}
Value *args[] = {
compile_global_entry(node),
val
};
return compile_protected_call(gvarSetFunc, args, args + 2);
}
Value *
RoxorCompiler::compile_gvar_get(NODE *node)
{
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));
}
return CallInst::Create(gvarGetFunc, compile_global_entry(node),
"", bb);
}
Value *
RoxorCompiler::compile_constant_declaration(NODE *node, Value *val)
{
int flags = 0;
bool lexical_lookup = false;
Value *args[5];
if (node->nd_vid > 0) {
lexical_lookup = true;
args[0] = nilVal;
args[1] = compile_id(node->nd_vid);
}
else {
assert(node->nd_else != NULL);
args[0] = compile_class_path(node->nd_else, &flags);
assert(node->nd_else->nd_mid > 0);
args[1] = compile_id(node->nd_else->nd_mid);
lexical_lookup = flags & DEFINE_OUTER;
}
args[2] = val;
args[3] = ConstantInt::get(Int8Ty, lexical_lookup ? 1 : 0);
if (lexical_lookup) {
args[4] = compile_outer_stack();
}
else {
args[4] = compile_const_pointer(NULL);
}
CallInst::Create(setConstFunc, args, args + 5, "", 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);
}
Value *
RoxorCompiler::compile_nsobject(void)
{
return ConstantInt::get(RubyObjTy, rb_cObject);
}
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);
}
Value *
RoxorCompiler::compile_standarderror(void)
{
return ConstantInt::get(RubyObjTy, rb_eStandardError);
}
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);
}
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;
}
int flags = 0;
if (!outer_given) {
flags |= CONST_LOOKUP_LEXICAL;
}
if (dynamic_class) {
flags |= CONST_LOOKUP_DYNAMIC_CLASS;
}
Value *args[] = {
outer,
compile_ccache(id),
compile_id(id),
ConstantInt::get(Int32Ty, flags),
compile_outer_stack()
};
Instruction *insn = compile_protected_call(getConstFunc, args, args + 5);
attach_current_line_metadata(insn);
return insn;
}
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));
}
Value *args[] = { obj };
return compile_protected_call(singletonClassFunc, args, args + 1);
}
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, rb_vm_outer_t *outer_stack);
definedFunc = cast<Function>(module->getOrInsertFunction(
"rb_vm_defined",
RubyObjTy, RubyObjTy, Int32Ty, RubyObjTy, RubyObjTy, PtrTy,
NULL));
}
Value *val = NULL;
if (!expression) {
// Call the runtime.
Value *outer_stack_val;
if (type == DEFINED_CONST || type == DEFINED_LCONST) {
if (current_mid != 0) {
outer_stack_uses = true;
}
outer_stack_val = compile_outer_stack();
}
else {
outer_stack_val = compile_const_pointer(NULL);
}
Value *args[] = {
self,
ConstantInt::get(Int32Ty, type),
what1 == NULL ? nilVal : what1,
what2 == NULL ? nilVal : what2,
outer_stack_val
};
val = compile_protected_call(definedFunc, args, args + 5);
}
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));
}
CallInst::Create(breakFunc, val, "", bb);
}
void
RoxorCompiler::compile_break_within_loop(Value *val)
{
if (ensure_bb == NULL) {
BranchInst::Create(current_loop_end_bb, bb);
current_loop_exit_val->addIncoming(val, bb);
}
else {
BranchInst::Create(ensure_bb, bb);
ensure_pn->addIncoming(val, bb);
}
}
void
RoxorCompiler::compile_break_within_block(Value *val)
{
if (ensure_bb == NULL) {
compile_break_val(val);
ReturnInst::Create(context, val, bb);
}
else {
BranchInst::Create(ensure_bb, bb);
ensure_pn->addIncoming(val, 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));
}
Value *args[] = {
val,
ConstantInt::get(Int32Ty, id),
running_block
};
compile_protected_call(returnFromBlockFunc, args, args + 3);
}
void
RoxorCompiler::compile_return_from_block_handler(int id)
{
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));
}
Value *args[] = {
exception,
ConstantInt::get(Int32Ty, id)
};
Value *val = CallInst::Create(checkReturnFromBlockFunc, args, args + 2,
"", 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 = block_declaration;
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) {
compile_break_within_loop(val);
}
else if (within_block) {
compile_break_within_block(val);
}
else {
rb_raise(rb_eLocalJumpError, "unexpected break");
}
break;
case NODE_NEXT:
if (current_rescue) {
compile_landing_pad_footer();
}
if (within_loop) {
if (ensure_node != NULL) {
compile_node(ensure_node);
}
BranchInst::Create(current_loop_begin_bb, bb);
}
else if (within_block) {
compile_simple_return(val);
}
else {
rb_raise(rb_eLocalJumpError, "unexpected next");
}
break;
case NODE_REDO:
if (current_rescue) {
compile_landing_pad_footer();
}
if (within_loop) {
if (ensure_node != NULL) {
compile_node(ensure_node);
}
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));
}
return CallInst::Create(setHasEnsureFunc, val, "", bb);
}
Value *
RoxorCompiler::compile_class_path(NODE *node, int *flags)
{
if (nd_type(node) == NODE_COLON3) {
// ::Foo
if (flags != NULL) {
*flags = 0;
}
return compile_nsobject();
}
else if (node->nd_head != NULL) {
// Bar::Foo
if (flags != NULL) {
*flags = DEFINE_SUB_OUTER;
}
return compile_node(node->nd_head);
}
else {
if (flags != NULL) {
*flags = DEFINE_OUTER;
}
return compile_current_class();
}
}
Value *
RoxorCompiler::compile_landing_pad_header(void)
{
Function *eh_exception_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_exception);
Value *eh_ptr = CallInst::Create(eh_exception_f, "", bb);
Function *eh_selector_f = Intrinsic::getDeclaration(module,
Intrinsic::eh_selector);
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));
// catch (...)
params.push_back(compile_const_pointer(NULL));
CallInst::Create(eh_selector_f, params.begin(), params.end(), "", 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(int pos)
{
if (popExceptionFunc == NULL) {
// void rb_vm_pop_exception(int pos);
popExceptionFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_pop_exception",
VoidTy, Int32Ty, NULL));
}
CallInst::Create(popExceptionFunc, ConstantInt::get(Int32Ty, pos), "", 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);
}
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[1]; // 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;
}
Value *leftVal = params[1]; // self
Value *rightVal = params.back();
Function *func = NULL;
if (sel == selPLUS) {
func = fastPlusFunc;
}
else if (sel == selMINUS) {
func = fastMinusFunc;
}
else if (sel == selDIV) {
func = fastDivFunc;
}
else if (sel == selMULT) {
func = fastMultFunc;
}
else if (sel == selLT) {
func = fastLtFunc;
}
else if (sel == selLE) {
func = fastLeFunc;
}
else if (sel == selGT) {
func = fastGtFunc;
}
else if (sel == selGE) {
func = fastGeFunc;
}
else if (sel == selEq) {
func = fastEqFunc;
}
else if (sel == selNeq) {
func = fastNeqFunc;
}
else if (sel == selEqq) {
func = fastEqqFunc;
}
assert(func != NULL);
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(sel, true);
Value *args[] = {
leftVal,
rightVal,
new LoadInst(is_redefined, "", bb)
};
return compile_protected_call(func, args, args + 3);
}
// 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 > 6) {
// Looks like there is a splat argument there, we can't handle this
// in the primitives.
return NULL;
}
Function *func = NULL;
if (sel == selLTLT) {
func = fastShiftFunc;
}
else if (sel == selAREF) {
func = fastArefFunc;
}
else if (sel == selASET) {
func = fastAsetFunc;
}
assert(func != NULL);
std::vector<Value *> new_params;
new_params.push_back(params[1]); // self
if (argc == 1) {
new_params.push_back(params.back()); // other
}
else {
new_params.insert(new_params.end(), params.end() - 2, params.end());
}
GlobalVariable *is_redefined = GET_CORE()->redefined_op_gvar(sel, true);
new_params.push_back(new LoadInst(is_redefined, "", bb));
return compile_protected_call(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::get(Int8Ty, 0));
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;
// Compile a null top reference, to ignore protected visibility.
new_params.push_back(ConstantInt::get(RubyObjTy, 0));
new_params.push_back(params[1]);
new_params.push_back(compile_sel(new_sel));
new_params.push_back(params[3]);
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[7 + 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;
}
// __method__ or __callee__
else if (sel == sel__method__ || sel == sel__callee__) {
if (current_block_func != NULL || argc != 0) {
return NULL;
}
Function *f = bb->getParent();
Function::arg_iterator arg = f->arg_begin();
arg++; // skip self
Value *callee_sel = arg;
if (selToSymFunc == NULL) {
// VALUE rb_sel_to_sym(SEL sel);
selToSymFunc = cast<Function>(
module->getOrInsertFunction("rb_sel_to_sym",
RubyObjTy, PtrTy, NULL));
}
return CallInst::Create(selToSymFunc, callee_sel, "", bb);
}
return NULL;
}
Instruction *
RoxorCompiler::compile_range(Value *beg, Value *end, bool exclude_end,
bool retain)
{
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));
}
Value *args[] = {
beg,
end,
ConstantInt::get(Int32Ty, exclude_end ? 1 : 0),
ConstantInt::get(Int32Ty, retain ? 1 : 0)
};
return compile_protected_call(newRangeFunc, args, args + 4);
}
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' }
//
if (rb_str_chars_len(val) == 0) {
if (newString3Func == NULL) {
newString3Func = cast<Function>(
module->getOrInsertFunction(
"rb_str_new_empty", RubyObjTy, NULL));
}
return CallInst::Create(newString3Func, "", bb);
}
else {
const char *cstr = RSTRING_PTR(val);
const int cstr_len = RSTRING_LEN(val);
assert(cstr_len > 0);
if (newString2Func == NULL) {
newString2Func = cast<Function>(
module->getOrInsertFunction(
"rb_str_new",
RubyObjTy, PtrTy, Int32Ty, NULL));
}
Value *args[] = {
compile_const_global_string(cstr, cstr_len),
ConstantInt::get(Int32Ty, cstr_len)
};
return CallInst::Create(newString2Func, args, args + 2, "", bb);
}
}
return compile_immutable_literal(val);
}
Value *
RoxorCompiler::compile_immutable_literal(VALUE val)
{
GC_RETAIN(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));
}
return CallInst::Create(setCurrentClassFunc, klass, "", bb);
}
Value *
RoxorCompiler::compile_push_outer(Value *klass)
{
if (pushOuterFunc == NULL) {
// rb_vm_outer_t *rb_vm_push_outer(Class klass)
pushOuterFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_push_outer",
PtrTy, RubyObjTy, NULL));
}
Value *val = CallInst::Create(pushOuterFunc, klass, "", bb);
outer_stack = new GlobalVariable(*RoxorCompiler::module, PtrTy, false,
GlobalValue::InternalLinkage,
Constant::getNullValue(PtrTy), "");
assert(outer_stack != NULL);
new StoreInst(val, outer_stack, "", bb);
return val;
}
void
RoxorCompiler::compile_pop_outer(bool need_release)
{
if (popOuterFunc == NULL) {
// void rb_vm_pop_outer(unsigned char need_release)
popOuterFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_pop_outer",
VoidTy, Int8Ty, NULL));
}
Value *val = ConstantInt::get(Int8Ty, need_release ? 1 : 0);
CallInst::Create(popOuterFunc, val, "", bb);
}
Value *
RoxorCompiler::compile_outer_stack(void)
{
if (outer_stack == NULL) {
return compile_const_pointer(rb_vm_get_outer_stack());
}
return new LoadInst(outer_stack, "", bb);
}
Value *
RoxorCompiler::compile_set_current_outer(void)
{
if (setCurrentOuterFunc == NULL) {
// rb_vm_outer_t *rb_vm_set_current_outer(rb_vm_outer_t *outer)
setCurrentOuterFunc = cast<Function>(
module->getOrInsertFunction("rb_vm_set_current_outer",
PtrTy, PtrTy, NULL));
}
return CallInst::Create(setCurrentOuterFunc, compile_outer_stack(), "", bb);
}
void
RoxorCompiler::compile_set_current_scope(Value *klass, Value *scope)
{
Value *args[] = {
klass,
scope
};
CallInst::Create(setScopeFunc, args, args + 2, "", bb);
}
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);
}
bool
RoxorCompiler::should_inline_function(Function *f)
{
return f->getName().startswith("vm_");
}
void
RoxorCompiler::inline_function_calls(Function *f)
{
std::vector<CallInst *> insns;
for (Function::iterator fi = f->begin(); fi != f->end(); ++fi) {
for (BasicBlock::iterator bi = fi->begin(); bi != fi->end(); ++bi) {
CallInst *insn = dyn_cast<CallInst>(bi);
if (insn != NULL) {
Function *called = insn->getCalledFunction();
if (called != NULL && should_inline_function(called)) {
insns.push_back(insn);
}
}
}
}
InlineFunctionInfo IFI;
for (std::vector<CallInst *>::iterator i = insns.begin();
i != insns.end(); ++i) {
InlineFunction(*i, IFI);
}
}
Function *
RoxorCompiler::compile_scope(NODE *node)
{
rb_vm_arity_t arity = rb_vm_node_arity(node);
const int nargs = bb == NULL ? 0 : arity.real;
const bool has_dvars = block_declaration;
// 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);
Function *f = Function::Create(ft, GlobalValue::InternalLinkage,
"ruby_scope", module);
NODE *old_ensure_node = ensure_node;
BasicBlock *old_ensure_bb = ensure_bb;
ensure_node = NULL;
ensure_bb = NULL;
AllocaInst *old_argv_buffer = argv_buffer;
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;
argv_buffer = NULL;
rescue_invoke_bb = NULL;
rescue_rethrow_bb = NULL;
bb = BasicBlock::Create(context, "MainBlock", f);
DISubprogram old_debug_subprogram = debug_subprogram;
#if 0
// This is not the right way to emit subprogram DWARF entries,
// llc emits some assembly that doesn't compile because some
// symbols are duplicated.
debug_subprogram = debug_info->CreateSubprogram(
debug_compile_unit, f->getName(), f->getName(),
f->getName(), debug_compile_unit, nd_line(node),
DIType(), f->hasInternalLinkage(), true);
debug_info->InsertSubprogramStart(debug_subprogram, bb);
#endif
std::map<ID, Value *> old_lvars = lvars;
lvars.clear();
std::vector<MAsgnValue> old_masgn_values = masgn_values;
masgn_values.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;
}
Value *old_current_block_arg = current_block_arg;
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;
Function *old_current_non_block_func = current_non_block_func;
if (!block_declaration) {
current_non_block_func = f;
current_arity = arity;
}
DEBUG_LEVEL_INC();
val = compile_node(node->nd_body);
DEBUG_LEVEL_DEC();
current_non_block_func = old_current_non_block_func;
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()) {
#if LLVM_TOT
InvokeInst *invoke = dyn_cast<InvokeInst>(rescue_use_it);
#else
InvokeInst *invoke = dyn_cast<InvokeInst>(*rescue_use_it);
#endif
assert(invoke != NULL);
// Transform the InvokeInst in CallInst.
std::vector<Value *> params;
for (unsigned i = 0; i < invoke->getNumOperands() - 3; i++) {
params.push_back(invoke->getOperand(i));
}
CallInst *call_inst = CallInst::Create(
invoke->getCalledValue(),
params.begin(), params.end(),
"",
invoke);
// Transfer the debugging metadata if any.
MDNode *node = invoke->getMetadata(dbg_mdkind);
if (node !=NULL) {
call_inst->setMetadata(dbg_mdkind, node);
}
invoke->replaceAllUsesWith(call_inst);
BasicBlock *normal_bb = dyn_cast<BasicBlock>(invoke->getNormalDest());
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();
}
}
for (std::vector<MAsgnValue>::iterator i = masgn_values.begin();
i != masgn_values.end(); ++i) {
MAsgnValue &v = *i;
if (v.ary->hasNUses(0)) {
v.ary->eraseFromParent();
}
}
current_block_arg = old_current_block_arg;
rescue_rethrow_bb = old_rescue_rethrow_bb;
rescue_invoke_bb = old_rescue_invoke_bb;
ensure_node = old_ensure_node;
ensure_bb = old_ensure_bb;
argv_buffer = old_argv_buffer;
bb = old_bb;
entry_bb = old_entry_bb;
lvars = old_lvars;
masgn_values = old_masgn_values;
current_self = old_self;
current_var_uses = old_current_var_uses;
running_block = old_running_block;
debug_subprogram = old_debug_subprogram;
return f;
}
Value *
RoxorCompiler::compile_call(NODE *node, bool use_tco)
{
NODE *recv = node->nd_recv;
NODE *args = node->nd_args;
ID 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);
}
bool splat_args = false;
bool positive_arity = false;
if (nd_type(node) == NODE_ZSUPER) {
assert(args == NULL);
assert(current_non_block_func != NULL);
const long s = current_non_block_func->getArgumentList().size();
positive_arity = s - 2 > 0; /* skip self and sel */
}
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. Not for everyone.
if (use_tco && !block_given && !super_call && !splat_args
&& !block_declaration && 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) {
// We check a global variable that we initialize to 0 in order
// to verify that the current method did not overwrite itself.
// This can happen. In this case, we do a normal dispatch.
SEL sel = mid_to_sel(mid, positive_arity ? 1 : 0);
GlobalVariable *gvar = GET_CORE()->redefined_op_gvar(sel, true);
new StoreInst(ConstantInt::get(Type::getInt8Ty(context), 0), gvar,
f->getEntryBlock().getFirstNonPHI());
Value *isNotRedefined = new ICmpInst(*bb, ICmpInst::ICMP_EQ,
new LoadInst(gvar, "", bb), ConstantInt::get(Int8Ty, 0));
BasicBlock *thenBB = BasicBlock::Create(context, "op_not_redef", f);
BasicBlock *elseBB = BasicBlock::Create(context, "op_dispatch", f);
BasicBlock *mergeBB = BasicBlock::Create(context, "op_merge", f);
BranchInst::Create(thenBB, elseBB, isNotRedefined, bb);
// Compile optimized recursive call.
bb = thenBB;
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); // Promote for tail call elimitation.
Value *optz_value = cast<Value>(inst);
thenBB = bb;
BranchInst::Create(mergeBB, bb);
// Compile regular dispatch call.
bb = elseBB;
Value *unoptz_value = compile_call(node, false);
elseBB = bb;
BranchInst::Create(mergeBB, bb);
bb = mergeBB;
PHINode *pn = PHINode::Create(RubyObjTy, "", bb);
pn->addIncoming(optz_value, thenBB);
pn->addIncoming(unoptz_value, elseBB);
return pn;
}
}
// 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;
// Prepare the selector.
Value *sel_val;
SEL sel;
if (mid != 0) {
sel = mid_to_sel(mid, positive_arity ? 1 : 0);
sel_val = compile_sel(sel);
if (block_declaration && super_call) {
// A super call inside a block. The selector cannot
// be determined at compilation time, but at runtime:
//
// VALUE my_block(VALUE rcv, SEL sel, ...)
// // ...
// SEL super_sel = sel;
// if (super_sel == 0)
// super_sel = <hardcoded-mid>;
// rb_vm_dispatch(..., super_sel, ...);
Function *f = bb->getParent();
Function::arg_iterator arg = f->arg_begin();
arg++; // skip self
Value *dyn_sel = arg;
Value *is_null = new ICmpInst(*bb, ICmpInst::ICMP_EQ, dyn_sel,
compile_const_pointer(NULL));
sel_val = SelectInst::Create(is_null, sel_val, dyn_sel, "", bb);
}
}
else {
assert(super_call);
// A super call outside a method definition. Compile a
// null selector, the runtime will raise an exception.
sel = 0;
sel_val = compile_const_pointer(NULL);
}
// 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));
NODE *real_args = args;
if (real_args != NULL && nd_type(real_args) == NODE_BLOCK_PASS) {
real_args = args->nd_head;
}
// Call option.
unsigned char call_opt = 0;
if (super_call) {
call_opt |= DISPATCH_SUPER;
}
else if (nd_type(node) == NODE_VCALL) {
call_opt |= DISPATCH_VCALL;
}
else if (nd_type(node) == NODE_FCALL) {
call_opt |= DISPATCH_FCALL;
}
params.push_back(ConstantInt::get(Int8Ty, call_opt));
// Arguments.
int argc = 0;
if (nd_type(node) == NODE_ZSUPER) {
Function::ArgumentListType &fargs =
current_non_block_func->getArgumentList();
const int fargs_arity = fargs.size() - 2;
params.push_back(ConstantInt::get(Int32Ty, fargs_arity));
Function::ArgumentListType::iterator iter = fargs.begin();
iter++; // skip self
iter++; // skip sel
const int rest_pos = current_arity.max == -1
? (current_arity.left_req
+ (current_arity.real - current_arity.min - 1))
: -1;
int i = 0;
while (iter != fargs.end()) {
if (i == rest_pos) {
params.push_back(splatArgFollowsVal);
splat_args = true;
}
// We can't simply push the direct argument address
// because if may have a default value.
ID argid = rb_intern(iter->getName().data());
Value *argslot;
if (block_declaration) {
if (std::find(dvars.begin(), dvars.end(), argid)
== dvars.end()) {
// Dvar does not exist yet, so we create it
// on demand!
dvars.push_back(argid);
}
argslot = compile_dvar_slot(argid);
}
else {
argslot = compile_lvar_slot(argid);
}
params.push_back(new LoadInst(argslot, "", bb));
++i;
++iter;
}
argc = fargs_arity;
}
else if (real_args != NULL) {
std::vector<Value *> arguments;
compile_dispatch_arguments(real_args, 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);
}
}
else {
params.push_back(ConstantInt::get(Int32Ty, 0));
}
// In case we have splat args, modify the call option.
if (splat_args) {
call_opt |= DISPATCH_SPLAT;
params[4] = ConstantInt::get(Int8Ty, call_opt);
}
// Restore the block state.
current_block_func = old_current_block_func;
current_block_node = old_current_block_node;
// Now compile the block and insert it in the params list!
Value *blockVal;
if (args != NULL && nd_type(args) == NODE_BLOCK_PASS) {
assert(!block_given);
assert(args->nd_body != NULL);
blockVal = compile_block_get(compile_node(args->nd_body));
}
else {
if (block_given) {
blockVal = compile_prepare_block();
}
else if (nd_type(node) == NODE_SUPER || nd_type(node) == NODE_ZSUPER) {
if (current_block_arg != NULL) {
blockVal = compile_block_get(current_block_arg);
}
else {
if (currentBlockFunc == NULL) {
currentBlockFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_current_block", PtrTy, NULL));
}
blockVal = CallInst::Create(currentBlockFunc, "", bb);
}
}
else {
blockVal = compile_const_pointer(NULL);
}
}
params[3] = blockVal;
// If we are calling a method that needs a reference to the current outer,
// compile a reference to it.
if (!super_call
&& (sel == selEval
|| sel == selInstanceEval
|| sel == selClassEval
|| sel == selModuleEval
|| sel == selNesting
|| sel == selConstants
|| sel == selBinding)) {
if (current_mid != 0) {
outer_stack_uses = true;
}
compile_set_current_outer();
}
// If we are calling a method that needs a top-level binding object, let's
// create it. (Note: this won't work if the method is aliased, but we can
// live with that for now)
if (debug_mode
|| (!super_call
&& (sel == selEval
|| sel == selInstanceEval
|| sel == selClassEval
|| sel == selModuleEval
|| sel == selLocalVariables
|| sel == selBinding))) {
compile_binding();
}
else {
can_interpret = true;
}
// Can we optimize the call?
if (!super_call && !splat_args) {
Value *opt_call = compile_optimized_dispatch_call(sel, argc, params);
if (opt_call != NULL) {
can_interpret = false;
return opt_call;
}
}
// Looks like we can't, just do a regular dispatch then.
return compile_dispatch_call(params);
}
Value *
RoxorCompiler::compile_yield(NODE *node)
{
std::vector<Value *> args;
int argc = 0;
if (node->nd_head != NULL) {
compile_dispatch_arguments(node->nd_head, args, &argc);
}
Value *argv = recompile_dispatch_argv(args, 0);
std::vector<Value *> params;
params.push_back(ConstantInt::get(Int32Ty, argc));
unsigned char opt = 0;
if (argc < (int)args.size()) {
opt |= DISPATCH_SPLAT;
}
params.push_back(ConstantInt::get(Int8Ty, opt));
params.push_back(argv);
Instruction *val = compile_protected_call(yieldFunc, params);
attach_current_line_metadata(val);
Value *broken = CallInst::Create(getBrokenFunc, "", bb);
Value *is_broken = new ICmpInst(*bb, ICmpInst::ICMP_NE, broken, undefVal);
Function *f = bb->getParent();
BasicBlock *broken_bb = BasicBlock::Create(context, "broken", f);
BasicBlock *next_bb = BasicBlock::Create(context, "next", f);
BranchInst::Create(broken_bb, next_bb, is_broken, bb);
bb = broken_bb;
ReturnInst::Create(context, broken, bb);
bb = next_bb;
return val;
}
inline Value *
RoxorCompiler::compile_node0(NODE *node)
{
switch (nd_type(node)) {
case NODE_SCOPE:
can_interpret = true;
return cast<Value>(compile_scope(node));
case NODE_DVAR:
case NODE_LVAR:
assert(node->nd_vid > 0);
return new LoadInst(compile_lvar_slot(node->nd_vid), "", bb);
case NODE_GVAR:
return compile_gvar_get(node);
case NODE_GASGN:
assert(node->nd_value != NULL);
return compile_gvar_assignment(node, compile_node(node->nd_value));
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:
return compile_multiple_assignment(node);
case NODE_DASGN:
case NODE_DASGN_CURR:
assert(node->nd_vid > 0);
assert(node->nd_value != NULL);
return compile_dvar_assignment(node->nd_vid,
compile_node(node->nd_value));
case NODE_LASGN:
assert(node->nd_vid > 0);
assert(node->nd_value != NULL);
return compile_lvar_assignment(node->nd_vid,
compile_node(node->nd_value));
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(current_self);
params.push_back(recv);
params.push_back(compile_sel(sel));
params.push_back(compile_const_pointer(NULL));
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);
}
unsigned char opt = 0;
if (argc < (int)arguments.size()) {
opt |= DISPATCH_SPLAT;
}
params.push_back(ConstantInt::get(Int8Ty, opt));
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(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(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(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));
}
Value *args[] = {
val,
ConstantInt::get(Int32Ty, flag)
};
return compile_protected_call(newRegexpFunc, args, args + 2);
}
break;
case NODE_DSYM:
{
Value *val = compile_dstr(node);
if (strInternFunc == NULL) {
strInternFunc = cast<Function>(module->getOrInsertFunction(
"rb_str_intern_fast",
RubyObjTy, RubyObjTy, NULL));
}
Value *args[] = { val };
return compile_protected_call(strInternFunc, args, args + 1);
}
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 = NULL;
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, rb_vm_outer_t *outer_stack);
defineClassFunc = cast<Function>(
module->getOrInsertFunction(
"rb_vm_define_class",
RubyObjTy, IntTy, RubyObjTy, RubyObjTy,
Int32Ty, Int8Ty, PtrTy, NULL));
}
int flags = 0;
Value *cpath = compile_class_path(node->nd_cpath, &flags);
if (nd_type(node) == NODE_MODULE) {
flags |= DEFINE_MODULE;
}
Value *args[] = {
compile_id(path),
cpath,
super == NULL ? zeroVal : compile_node(super),
ConstantInt::get(Int32Ty, flags),
ConstantInt::get(Int8Ty,
(flags & DEFINE_OUTER) && dynamic_class
? 1 : 0),
compile_outer_stack()
};
Instruction *insn = compile_protected_call(defineClassFunc,
args, args + 6);
attach_current_line_metadata(insn);
classVal = insn;
}
NODE *body = node->nd_body;
if (body != NULL) {
assert(nd_type(body) == NODE_SCOPE);
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_block_chain = current_block_chain;
bool old_dynamic_class = dynamic_class;
GlobalVariable *old_outer_stack = outer_stack;
bool old_outer_stack_uses = outer_stack_uses;
compile_push_outer(classVal);
current_block_chain = false;
dynamic_class = false;
new StoreInst(classVal, current_opened_class, bb);
compile_set_current_scope(classVal, publicScope);
bool old_block_declaration = block_declaration;
block_declaration = false;
std::map<ID, void *> old_ivars_slots_cache
= ivars_slots_cache;
old_ivars_slots_cache.clear();
std::vector<ID> old_dvars = dvars;
dvars.clear();
// Compile the scope.
DEBUG_LEVEL_INC();
Value *val = compile_node(body);
assert(Function::classof(val));
Function *f = cast<Function>(val);
GET_CORE()->optimize(f);
DEBUG_LEVEL_DEC();
dvars = old_dvars;
ivars_slots_cache = old_ivars_slots_cache;
block_declaration = old_block_declaration;
// Create a rescue block for the module scope function,
// since it might raise an exception and we do want
// to properly restore the context information.
Function *main_f = bb->getParent();
BasicBlock *old_rescue_invoke_bb = rescue_invoke_bb;
BasicBlock *new_rescue_invoke_bb =
BasicBlock::Create(context, "rescue", main_f);
rescue_invoke_bb = new_rescue_invoke_bb;
// Run the scope.
std::vector<Value *> params;
params.push_back(classVal);
params.push_back(compile_const_pointer(NULL));
val = compile_protected_call(f, params);
BasicBlock *normal_bb = bb;
// The rescue block - restore context before
// propagating the exception.
bb = new_rescue_invoke_bb;
compile_landing_pad_header();
compile_pop_outer(!outer_stack_uses);
compile_set_current_scope(classVal, defaultScope);
compile_rethrow_exception();
// The normal block - restore context.
bb = normal_bb;
compile_pop_outer(!outer_stack_uses);
compile_set_current_scope(classVal, defaultScope);
outer_stack_uses = old_outer_stack_uses;
outer_stack = old_outer_stack;
dynamic_class = old_dynamic_class;
current_self = old_self;
current_opened_class = old_class;
current_block_chain = old_current_block_chain;
rescue_invoke_bb = old_rescue_invoke_bb;
return val;
}
}
return nilVal;
}
break;
case NODE_SUPER:
case NODE_ZSUPER:
case NODE_CALL:
case NODE_FCALL:
case NODE_VCALL:
return compile_call(node);
case NODE_ATTRASGN:
return compile_attribute_assign(node, NULL);
case NODE_BREAK:
case NODE_NEXT:
case NODE_REDO:
case NODE_RETURN:
case NODE_RETRY:
return compile_jump(node);
case NODE_CONST:
assert(node->nd_vid > 0);
if (current_mid != 0) {