Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

5877 lines (5174 sloc) 141.183 kB
/*
* MacRuby VM.
*
* This file is covered by the Ruby license. See COPYING for more details.
*
* Copyright (C) 2012, The MacRuby Team. All rights reserved.
* Copyright (C) 2008-2011, Apple Inc. All rights reserved.
*/
#define ROXOR_VM_DEBUG 0
#define ROXOR_COMPILER_DEBUG 0
#define ROXOR_VM_DEBUG_CONST 0
#if MACRUBY_STATIC
# include <vector>
# include <map>
# include <string>
#else
# include <llvm/Module.h>
# include <llvm/DerivedTypes.h>
# include <llvm/Constants.h>
# include <llvm/CallingConv.h>
# include <llvm/Instructions.h>
# include <llvm/PassManager.h>
# include <llvm/Analysis/DebugInfo.h>
# if !defined(LLVM_TOT)
# include <llvm/Analysis/DIBuilder.h>
# endif
# include <llvm/Analysis/Verifier.h>
# include <llvm/Target/TargetData.h>
# include <llvm/CodeGen/MachineFunction.h>
# include <llvm/ExecutionEngine/JIT.h>
# include <llvm/ExecutionEngine/JITMemoryManager.h>
# include <llvm/ExecutionEngine/JITEventListener.h>
# include <llvm/ExecutionEngine/GenericValue.h>
# include <llvm/Target/TargetData.h>
# include <llvm/Target/TargetMachine.h>
# include <llvm/Target/TargetOptions.h>
# include <llvm/Target/TargetSelect.h>
# include <llvm/Transforms/Scalar.h>
# include <llvm/Transforms/IPO.h>
# include <llvm/Support/raw_ostream.h>
# if !defined(LLVM_TOT)
# include <llvm/Support/system_error.h>
# endif
# include <llvm/Support/PrettyStackTrace.h>
# include <llvm/Support/MemoryBuffer.h>
# include <llvm/Support/StandardPasses.h>
# include <llvm/Intrinsics.h>
# include <llvm/Bitcode/ReaderWriter.h>
# include <llvm/LLVMContext.h>
# include "llvm/ADT/Statistic.h"
using namespace llvm;
#endif // MACRUBY_STATIC
#if ROXOR_COMPILER_DEBUG
# include <mach/mach.h>
# include <mach/mach_time.h>
#endif
#include "macruby_internal.h"
#include "ruby/node.h"
#include "id.h"
#include "vm.h"
#include "compiler.h"
#include "debugger.h"
#include "interpreter.h"
#include "objc.h"
#include "dtrace.h"
#include "class.h"
#include <objc/objc-exception.h>
#include <execinfo.h>
#include <dlfcn.h>
#include <iostream>
#include <fstream>
RoxorCore *RoxorCore::shared = NULL;
RoxorVM *RoxorVM::main = NULL;
pthread_key_t RoxorVM::vm_thread_key;
VALUE rb_cTopLevel = 0;
// A simple class that acquires the core global lock in its constructor and
// releases it in its destructor. It is used to make sure the lock will be
// released in case an exception happens inside the scope (since C++ exceptions
// call object destructors).
class RoxorCoreLock {
private:
bool locked;
public:
RoxorCoreLock() {
GET_CORE()->lock();
locked = true;
}
~RoxorCoreLock() {
if (locked) {
GET_CORE()->unlock();
locked = false;
}
}
void unlock(void) {
assert(locked);
GET_CORE()->unlock();
locked = false;
}
};
#if !defined(MACRUBY_STATIC)
class RoxorFunction {
public:
// Information retrieved from JITManager.
Function *f;
unsigned char *start;
unsigned char *end;
std::vector<unsigned char *> ehs;
// Information retrieved from JITListener.
std::string path;
class Line {
public:
uintptr_t address;
unsigned line;
Line(uintptr_t _address, unsigned _line) {
address = _address;
line = _line;
}
};
std::vector<Line> lines;
// Information retrieved later (lazily).
void *imp;
RoxorFunction(Function *_f, unsigned char *_start,
unsigned char *_end) {
f = _f;
start = _start;
end = _end;
imp = NULL;
}
};
class RoxorJITManager : public JITMemoryManager, public JITEventListener {
private:
JITMemoryManager *mm;
std::vector<RoxorFunction *> functions;
RoxorFunction *current_function(void) {
assert(!functions.empty());
return functions.back();
}
public:
RoxorJITManager() : JITMemoryManager() {
mm = CreateDefaultMemManager();
}
RoxorFunction *find_function(uint8_t *addr) {
if (functions.empty()) {
return NULL;
}
// TODO optimize me!
RoxorFunction *front = functions.front();
RoxorFunction *back = functions.back();
if (addr < front->start || addr > back->end) {
return NULL;
}
std::vector<RoxorFunction *>::iterator iter =
functions.begin();
while (iter != functions.end()) {
RoxorFunction *f = *iter;
if (addr >= f->start && addr <= f->end) {
return f;
}
++iter;
}
return NULL;
}
RoxorFunction *delete_function(Function *func) {
std::vector<RoxorFunction *>::iterator iter =
functions.begin();
while (iter != functions.end()) {
RoxorFunction *f = *iter;
if (f->f == func) {
functions.erase(iter);
return f;
}
++iter;
}
return NULL;
}
// JITMemoryManager callbacks.
void setMemoryWritable(void) {
mm->setMemoryWritable();
}
void setMemoryExecutable(void) {
mm->setMemoryExecutable();
}
uint8_t *allocateSpace(intptr_t Size, unsigned Alignment) {
return mm->allocateSpace(Size, Alignment);
}
uint8_t *allocateGlobal(uintptr_t Size, unsigned Alignment) {
return mm->allocateGlobal(Size, Alignment);
}
void AllocateGOT(void) {
mm->AllocateGOT();
}
uint8_t *getGOTBase() const {
return mm->getGOTBase();
}
uint8_t *startFunctionBody(const Function *F,
uintptr_t &ActualSize) {
return mm->startFunctionBody(F, ActualSize);
}
uint8_t *allocateStub(const GlobalValue* F,
unsigned StubSize,
unsigned Alignment) {
return mm->allocateStub(F, StubSize, Alignment);
}
void endFunctionBody(const Function *F, uint8_t *FunctionStart,
uint8_t *FunctionEnd) {
mm->endFunctionBody(F, FunctionStart, FunctionEnd);
Function *f = const_cast<Function *>(F);
functions.push_back(new RoxorFunction(f, FunctionStart,
FunctionEnd));
}
void deallocateFunctionBody(void *data) {
mm->deallocateFunctionBody(data);
}
void deallocateExceptionTable(void *data) {
mm->deallocateExceptionTable(data);
}
uint8_t* startExceptionTable(const Function* F,
uintptr_t &ActualSize) {
return mm->startExceptionTable(F, ActualSize);
}
void endExceptionTable(const Function *F, uint8_t *TableStart,
uint8_t *TableEnd, uint8_t* FrameRegister) {
current_function()->ehs.push_back(FrameRegister);
mm->endExceptionTable(F, TableStart, TableEnd, FrameRegister);
}
void setPoisonMemory(bool poison) {
mm->setPoisonMemory(poison);
}
// JITEventListener callbacks.
void NotifyFunctionEmitted(const Function &F,
void *Code, size_t Size,
const EmittedFunctionDetails &Details) {
RoxorFunction *function = current_function();
std::string path;
for (std::vector<EmittedFunctionDetails::LineStart>::const_iterator iter = Details.LineStarts.begin(); iter != Details.LineStarts.end(); ++iter) {
MDNode *scope = iter->Loc.getAsMDNode(F.getContext());
DILocation loc = DILocation(scope);
if (path.size() == 0) {
RoxorCompiler::shared->generate_location_path(path, loc);
}
//printf("%p -> %d\n", (void*)iter->Address, loc.getLineNumber());
RoxorFunction::Line line(iter->Address, loc.getLineNumber());
function->lines.push_back(line);
}
function->path = path;
}
};
#endif
extern "C" void *__cxa_allocate_exception(size_t);
extern "C" void __cxa_throw(void *, void *, void (*)(void *));
extern "C" void __cxa_rethrow(void);
extern "C" std::type_info *__cxa_current_exception_type(void);
RoxorCore::RoxorCore(void)
{
running = false;
abort_on_exception = false;
pthread_assert(pthread_mutex_init(&gl, 0));
// Will be set later.
default_random = Qnil;
load_path = rb_ary_new();
GC_RETAIN(load_path);
loaded_features = rb_ary_new();
GC_RETAIN(loaded_features);
threads = rb_ary_new();
GC_RETAIN(threads);
#if !MACRUBY_STATIC
bs_parser = NULL;
llvm_start_multithreaded();
interpreter_enabled = getenv("VM_DISABLE_INTERPRETER") == NULL;
// The JIT is created later, if necessary.
InitializeNativeTarget();
jmm = NULL;
ee = NULL;
fpm = NULL;
# if ROXOR_VM_DEBUG
functions_compiled = 0;
# endif
#endif // !MACRUBY_STATIC
}
void
RoxorCore::prepare_jit(void)
{
#if !defined(MACRUBY_STATIC)
assert(ee == NULL);
jmm = new RoxorJITManager;
opt_level = CodeGenOpt::Default;
const char *env_str = getenv("VM_OPT_LEVEL");
if (env_str != NULL) {
const int tmp = atoi(env_str);
if (tmp >= 0 && tmp <= 3) {
switch (tmp) {
case 0:
opt_level = CodeGenOpt::None;
break;
case 1:
opt_level = CodeGenOpt::Less;
break;
case 2:
opt_level = CodeGenOpt::Default;
break;
case 3:
opt_level = CodeGenOpt::Aggressive;
break;
}
}
}
std::string err;
ee = ExecutionEngine::createJIT(RoxorCompiler::module, &err, jmm,
opt_level, false);
if (ee == NULL) {
fprintf(stderr, "error while creating JIT: %s\n", err.c_str());
abort();
}
ee->DisableLazyCompilation();
ee->RegisterJITEventListener(jmm);
fpm = new FunctionPassManager(RoxorCompiler::module);
fpm->add(new TargetData(*ee->getTargetData()));
// Do simple "peephole" optimizations and bit-twiddling optzns.
fpm->add(createInstructionCombiningPass());
// Eliminate unnecessary alloca.
fpm->add(createPromoteMemoryToRegisterPass());
// Reassociate expressions.
fpm->add(createReassociatePass());
// Eliminate Common SubExpressions.
fpm->add(createGVNPass());
// Simplify the control flow graph (deleting unreachable blocks, etc).
fpm->add(createCFGSimplificationPass());
// Eliminate tail calls.
fpm->add(createTailCallEliminationPass());
#endif
}
RoxorCore::~RoxorCore(void)
{
// TODO
}
RoxorVM::RoxorVM(void)
{
current_top_object = Qnil;
current_class = NULL;
outer_stack = NULL;
current_outer = NULL;
safe_level = 0;
backref = Qnil;
broken_with = Qundef;
last_line = Qnil;
last_status = Qnil;
errinfo = Qnil;
parse_in_eval = false;
has_ensure = false;
return_from_block = -1;
special_exc = NULL;
current_super_class = NULL;
current_super_sel = 0;
current_mri_method_self = Qnil;
current_mri_method_sel = 0;
mcache = (struct mcache *)calloc(VM_MCACHE_SIZE, sizeof(struct mcache));
assert(mcache != NULL);
}
static inline void *
block_cache_key(const rb_vm_block_t *b)
{
if ((b->flags & VM_BLOCK_IFUNC) == VM_BLOCK_IFUNC) {
return (void *)b->imp;
}
return (void *)b->userdata;
}
RoxorVM::RoxorVM(const RoxorVM &vm)
{
current_top_object = vm.current_top_object;
current_class = vm.current_class;
outer_stack = vm.outer_stack;
GC_RETAIN(outer_stack);
current_outer = vm.current_outer;
safe_level = vm.safe_level;
std::vector<rb_vm_block_t *> &vm_blocks =
const_cast<RoxorVM &>(vm).current_blocks;
for (std::vector<rb_vm_block_t *>::iterator i = vm_blocks.begin();
(i + 1) != vm_blocks.end();
++i) {
const rb_vm_block_t *orig = *i;
rb_vm_block_t *b = NULL;
if (orig != NULL) {
#if 1
b = const_cast<rb_vm_block_t *>(orig);
#else
// XXX: This code does not work yet, it raises a failed integrity
// check when running the specs.
const size_t block_size = sizeof(rb_vm_block_t *)
+ (orig->dvars_size * sizeof(VALUE *));
b = (rb_vm_block_t *)xmalloc(block_size);
memcpy(b, orig, block_size);
b->proc = orig->proc; // weak
GC_WB(&b->self, orig->self);
GC_WB(&b->locals, orig->locals);
GC_WB(&b->parent_block, orig->parent_block); // XXX not sure
#endif
GC_RETAIN(b);
blocks[block_cache_key(orig)] = b;
}
current_blocks.push_back(b);
}
// TODO bindings, exceptions?
backref = Qnil;
broken_with = Qundef;
last_line = Qnil;
last_status = Qnil;
errinfo = Qnil;
parse_in_eval = false;
has_ensure = false;
return_from_block = -1;
special_exc = NULL;
current_super_class = NULL;
current_super_sel = 0;
mcache = (struct mcache *)calloc(VM_MCACHE_SIZE, sizeof(struct mcache));
assert(mcache != NULL);
}
RoxorVM::~RoxorVM(void)
{
for (std::map<void *, rb_vm_block_t *>::iterator i = blocks.begin();
i != blocks.end();
++i) {
GC_RELEASE(i->second);
}
blocks.clear();
GC_RELEASE(outer_stack);
GC_RELEASE(backref);
GC_RELEASE(broken_with);
GC_RELEASE(last_status);
GC_RELEASE(errinfo);
free(mcache);
mcache = NULL;
}
void
RoxorVM::debug_blocks(void)
{
for (std::vector<rb_vm_block_t *>::iterator i = current_blocks.begin();
i != current_blocks.end();
++i) {
printf("%p ", *i);
}
printf("\n");
}
void
RoxorVM::debug_exceptions(void)
{
for (std::vector<VALUE>::iterator i = current_exceptions.begin();
i != current_exceptions.end();
++i) {
printf("current_exceptions[%d] = (%p) \"%s\"",
(int)(i - current_exceptions.begin()),
(void *)*i,
RSTRING_PTR(rb_inspect(*i)));
}
printf("\n");
}
#if !defined(MACRUBY_STATIC)
static bool
should_optimize(Function *func)
{
// Don't optimize big functions.
size_t insns = 0;
for (Function::iterator i = func->begin(); i != func->end(); ++i) {
insns += i->size();
}
return insns < 2000;
}
void
RoxorCore::optimize(Function *func)
{
switch (opt_level) {
case CodeGenOpt::None:
break;
case CodeGenOpt::Aggressive:
RoxorCompiler::shared->inline_function_calls(func);
goto optimize;
default:
if (ruby_aot_compile || should_optimize(func)) {
optimize:
if (fpm != NULL) {
fpm->run(*func);
}
}
break;
}
}
extern "C"
void
rb_verify_module(void)
{
if (verifyModule(*RoxorCompiler::module, PrintMessageAction)) {
printf("Error during module verification\n");
abort();
}
}
IMP
RoxorCore::compile(Function *func, bool run_optimize)
{
std::map<Function *, IMP>::iterator iter = JITcache.find(func);
if (iter != JITcache.end()) {
return iter->second;
}
#if ROXOR_COMPILER_DEBUG
// in AOT mode, the verifier is already called
// (and calling it here would check functions not fully compiled yet)
if (!ruby_aot_compile) {
rb_verify_module();
}
uint64_t start = mach_absolute_time();
#endif
// Optimize if needed.
if (run_optimize) {
optimize(func);
}
// Compile & cache.
IMP imp = (IMP)ee->getPointerToFunction(func);
JITcache[func] = imp;
#if ROXOR_COMPILER_DEBUG
uint64_t elapsed = mach_absolute_time() - start;
static mach_timebase_info_data_t sTimebaseInfo;
if (sTimebaseInfo.denom == 0) {
(void) mach_timebase_info(&sTimebaseInfo);
}
uint64_t elapsedNano = elapsed * sTimebaseInfo.numer / sTimebaseInfo.denom;
fprintf(stderr, "compilation of LLVM function %p done, took %lld ns\n",
func, elapsedNano);
#endif
#if ROXOR_VM_DEBUG
functions_compiled++;
#endif
return imp;
}
// in libgcc
extern "C" void __deregister_frame(const void *);
void
RoxorCore::delenda(Function *func)
{
assert(func->use_empty());
RoxorCoreLock lock;
// Remove from cache.
std::map<Function *, IMP>::iterator iter = JITcache.find(func);
if (iter != JITcache.end()) {
JITcache.erase(iter);
}
// Delete for JIT memory manager list.
RoxorFunction *f = jmm->delete_function(func);
assert(f != NULL);
// Unregister each dwarf exception handler.
// XXX this should really be done by LLVM...
for (std::vector<unsigned char *>::iterator i = f->ehs.begin();
i != f->ehs.end(); ++i) {
__deregister_frame((const void *)*i);
}
// Remove the compiler scope.
delete f;
// Delete machine code.
ee->freeMachineCodeForFunction(func);
// Delete IR.
func->eraseFromParent();
}
#endif
// Dummy function to be used for debugging (in gdb).
extern "C"
void
rb_symbolicate(void *addr)
{
char path[1000];
char name[100];
unsigned long ln = 0;
if (GET_CORE()->symbolize_call_address(addr, path, sizeof path,
&ln, name, sizeof name, NULL)) {
printf("addr %p selector %s location %s:%ld\n",
addr, name, path, ln);
}
else {
printf("addr %p unknown\n", addr);
}
}
bool
RoxorCore::symbolize_call_address(void *addr, char *path, size_t path_len,
unsigned long *ln, char *name, size_t name_len,
unsigned int *interpreter_frame_idx)
{
#if MACRUBY_STATIC
return false;
#else
if (jmm == NULL) {
return false;
}
RoxorFunction *f = jmm->find_function((unsigned char *)addr);
if (f != NULL) {
if (f->imp == NULL) {
f->imp = ee->getPointerToFunctionOrStub(f->f);
}
}
else {
std::string fr_name;
std::string fr_path;
unsigned int fr_line = 0;
if (interpreter_frame_idx == NULL
|| !RoxorInterpreter::shared->frame_at_index(*interpreter_frame_idx,
addr, &fr_name, &fr_path, &fr_line)) {
return false;
}
(*interpreter_frame_idx)++;
if (name != NULL) {
strlcpy(name, fr_name.c_str(), name_len);
}
if (path != NULL) {
strlcpy(path, fr_path.c_str(), path_len);
}
if (ln != NULL) {
*ln = fr_line;
}
return true;
}
if (f != NULL) {
if (ln != NULL) {
*ln = 0;
for (std::vector<RoxorFunction::Line>::iterator iter =
f->lines.begin(); iter != f->lines.end(); ++iter) {
if ((uintptr_t)addr <= (*iter).address) {
break;
}
*ln = (*iter).line;
}
}
if (path != NULL) {
strlcpy(path, f->path.c_str(), path_len);
}
if (name != NULL) {
std::map<IMP, rb_vm_method_node_t *>::iterator iter =
ruby_imps.find((IMP)f->imp);
if (iter == ruby_imps.end()) {
strlcpy(name, "block", name_len);
}
else {
strlcpy(name, sel_getName(iter->second->sel), name_len);
}
}
}
else {
if (ln != NULL) {
*ln = 0;
}
if (path != NULL) {
strlcpy(path, "core", path_len);
}
if (name != NULL) {
name[0] = '\0';
}
}
return true;
#endif
}
void
RoxorCore::symbolize_backtrace_entry(int index, char *path, size_t path_len,
unsigned long *ln, char *name, size_t name_len)
{
void *callstack[10];
const int callstack_n = backtrace(callstack, 10);
index++; // count us!
if (callstack_n < index
|| !GET_CORE()->symbolize_call_address(callstack[index], path,
path_len, ln, name, name_len, NULL)) {
if (path != NULL) {
strlcpy(path, "core", path_len);
}
if (ln != NULL) {
*ln = 0;
}
}
}
struct ccache *
RoxorCore::constant_cache_get(ID path)
{
std::map<ID, struct ccache *>::iterator iter = ccache.find(path);
if (iter == ccache.end()) {
struct ccache *cache = (struct ccache *)malloc(sizeof(struct ccache));
assert(cache != NULL);
cache->outer = 0;
cache->outer_stack = NULL;
cache->val = Qundef;
ccache[path] = cache;
return cache;
}
return iter->second;
}
extern "C"
void *
rb_vm_get_constant_cache(const char *name)
{
return GET_CORE()->constant_cache_get(rb_intern(name));
}
rb_vm_method_node_t *
RoxorCore::method_node_get(IMP imp, bool create)
{
rb_vm_method_node_t *n;
std::map<IMP, rb_vm_method_node_t *>::iterator iter = ruby_imps.find(imp);
if (iter == ruby_imps.end()) {
if (create) {
n = (rb_vm_method_node_t *)malloc(sizeof(rb_vm_method_node_t));
assert(n != NULL);
ruby_imps[imp] = n;
}
else {
n = NULL;
}
}
else {
n = iter->second;
}
return n;
}
rb_vm_method_node_t *
RoxorCore::method_node_get(Method m, bool create)
{
rb_vm_method_node_t *n;
std::map<Method, rb_vm_method_node_t *>::iterator iter =
ruby_methods.find(m);
if (iter == ruby_methods.end()) {
if (create) {
n = (rb_vm_method_node_t *)malloc(sizeof(rb_vm_method_node_t));
assert(n != NULL);
ruby_methods[m] = n;
}
else {
n = NULL;
}
}
else {
n = iter->second;
}
return n;
}
extern "C"
bool
rb_vm_is_ruby_method(Method m)
{
return GET_CORE()->method_node_get(m) != NULL;
}
#if !defined(MACRUBY_STATIC)
size_t
RoxorCore::get_sizeof(const Type *type)
{
return ee->getTargetData()->getTypeSizeInBits(type) / 8;
}
size_t
RoxorCore::get_sizeof(const char *type)
{
return get_sizeof(RoxorCompiler::shared->convert_type(type));
}
#ifdef __LP64__
# define LARGE_STRUCT_SIZE 128
#else
# define LARGE_STRUCT_SIZE 64
#endif /* !__LP64__ */
bool
RoxorCore::is_large_struct_type(const Type *type)
{
return type->getTypeID() == Type::StructTyID
&& ee->getTargetData()->getTypeSizeInBits(type) > LARGE_STRUCT_SIZE;
}
GlobalVariable *
RoxorCore::redefined_op_gvar(SEL sel, bool create)
{
std::map <SEL, GlobalVariable *>::iterator iter =
redefined_ops_gvars.find(sel);
GlobalVariable *gvar = NULL;
if (iter == redefined_ops_gvars.end()) {
if (create) {
// TODO: if OPTZ_LEVEL is 3, force global variables to always be
// true and read-only.
gvar = new GlobalVariable(*RoxorCompiler::module,
Type::getInt8Ty(context),
false,
GlobalValue::InternalLinkage,
ConstantInt::get(Type::getInt8Ty(context), 0),
"");
assert(gvar != NULL);
redefined_ops_gvars[sel] = gvar;
}
}
else {
gvar = iter->second;
}
return gvar;
}
#endif
bool
RoxorCore::should_invalidate_inline_op(SEL sel, Class klass)
{
if (sel == selEq || sel == selEqq || sel == selNeq) {
return klass == (Class)rb_cFixnum
|| klass == (Class)rb_cFloat
|| klass == (Class)rb_cBignum
|| klass == (Class)rb_cSymbol
|| klass == (Class)rb_cNSString
|| klass == (Class)rb_cNSMutableString
|| klass == (Class)rb_cNSArray
|| klass == (Class)rb_cNSMutableArray
|| klass == (Class)rb_cNSHash
|| klass == (Class)rb_cNSMutableHash;
}
if (sel == selPLUS || sel == selMINUS || sel == selDIV
|| sel == selMULT || sel == selLT || sel == selLE
|| sel == selGT || sel == selGE) {
return klass == (Class)rb_cFixnum
|| klass == (Class)rb_cFloat
|| klass == (Class)rb_cBignum;
}
if (sel == selLTLT || sel == selAREF || sel == selASET) {
return klass == (Class)rb_cNSArray
|| klass == (Class)rb_cNSMutableArray;
}
if (sel == selSend || sel == sel__send__ || sel == selEval) {
// Matches any class, since these are Kernel methods.
return true;
}
// Assume yes by default.
return true;
}
static ID
sanitize_mid(SEL sel)
{
const char *selname = sel_getName(sel);
const size_t sellen = strlen(selname);
if (selname[sellen - 1] == ':') {
if (memchr(selname, ':', sellen - 1) != NULL) {
return 0;
}
char buf[100];
strncpy(buf, selname, sizeof buf);
buf[sellen - 1] = '\0';
return rb_intern(buf);
}
return rb_intern(selname);
}
void
RoxorCore::method_added(Class klass, SEL sel)
{
if (get_running()) {
// Call method_added: or singleton_method_added:.
ID mid = sanitize_mid(sel);
if (mid != 0) {
VALUE sym = ID2SYM(mid);
if (RCLASS_SINGLETON(klass)) {
VALUE sk = rb_singleton_class_attached_object((VALUE)klass);
rb_vm_call(sk, selSingletonMethodAdded, 1, &sym);
}
else {
rb_vm_call((VALUE)klass, selMethodAdded, 1, &sym);
}
}
}
}
void
RoxorCore::invalidate_method_cache(SEL sel)
{
struct mcache *cache = GET_VM()->get_mcache();
for (int i = 0; i < VM_MCACHE_SIZE; i++) {
struct mcache *e = &cache[i];
if (e->sel == sel) {
e->flag = 0;
}
}
}
rb_vm_method_node_t *
RoxorCore::add_method(Class klass, SEL sel, IMP imp, IMP ruby_imp,
const rb_vm_arity_t &arity, int flags, const char *types)
{
// #initialize and #initialize_copy are always private.
if (sel == selInitialize || sel == selInitialize2
|| sel == selInitializeCopy) {
flags |= VM_METHOD_PRIVATE;
}
#if ROXOR_VM_DEBUG
printf("defining %c[%s %s] with imp %p/%p types %s flags %d arity %d\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel),
imp,
ruby_imp,
types,
flags,
arity.real);
#endif
// Register the implementation into the runtime.
class_replaceMethod(klass, sel, imp, types);
// Cache the method.
Method m = class_getInstanceMethod(klass, sel);
assert(m != NULL);
assert(method_getImplementation(m) == imp);
rb_vm_method_node_t *real_node = method_node_get(m, true);
real_node->klass = klass;
real_node->objc_imp = imp;
real_node->ruby_imp = ruby_imp;
real_node->arity = arity;
real_node->flags = flags;
real_node->sel = sel;
// Cache the implementation.
std::map<IMP, rb_vm_method_node_t *>::iterator iter2 = ruby_imps.find(imp);
rb_vm_method_node_t *node;
if (iter2 == ruby_imps.end()) {
node = (rb_vm_method_node_t *)malloc(sizeof(rb_vm_method_node_t));
node->objc_imp = imp;
ruby_imps[imp] = node;
}
else {
node = iter2->second;
assert(node->objc_imp == imp);
}
node->klass = klass;
node->arity = arity;
node->flags = flags;
node->sel = sel;
node->ruby_imp = ruby_imp;
if (imp != ruby_imp) {
ruby_imps[ruby_imp] = node;
}
if (running) {
// Invalidate respond_to cache.
invalidate_respond_to_cache();
// Invalidate dispatch cache.
invalidate_method_cache(sel);
// Invalidate inline operations.
#if !defined(MACRUBY_STATIC)
GlobalVariable *gvar = redefined_op_gvar(sel, false);
if (gvar != NULL && should_invalidate_inline_op(sel, klass)) {
void *val = ee->getOrEmitGlobalVariable(gvar);
#if ROXOR_VM_DEBUG
printf("change redefined global for [%s %s] to true\n",
class_getName(klass),
sel_getName(sel));
#endif
assert(val != NULL);
*(unsigned char *)val = 1;
}
#endif
}
// If alloc is redefined, mark the class as such.
if (sel == selAlloc
&& (RCLASS_VERSION(klass) & RCLASS_HAS_ROBJECT_ALLOC)
== RCLASS_HAS_ROBJECT_ALLOC) {
RCLASS_SET_VERSION(klass, (RCLASS_VERSION(klass) ^
RCLASS_HAS_ROBJECT_ALLOC));
}
// Forward method definition to the included classes.
if (RCLASS_VERSION(klass) & RCLASS_IS_INCLUDED) {
VALUE included_in_classes = rb_attr_get((VALUE)klass,
idIncludedInClasses);
if (included_in_classes != Qnil) {
for (int i = 0, count = RARRAY_LEN(included_in_classes);
i < count; i++) {
VALUE mod = RARRAY_AT(included_in_classes, i);
#if ROXOR_VM_DEBUG
printf("forward %c[%s %s] with imp %p node %p types %s\n",
class_isMetaClass((Class)mod) ? '+' : '-',
class_getName((Class)mod),
sel_getName(sel),
imp,
node,
types);
#endif
class_replaceMethod((Class)mod, sel, imp, types);
Method m = class_getInstanceMethod((Class)mod, sel);
assert(m != NULL);
assert(method_getImplementation(m) == imp);
node = method_node_get(m, true);
node->klass = (Class)mod;
node->objc_imp = imp;
node->ruby_imp = ruby_imp;
node->arity = arity;
node->flags = flags;
node->sel = sel;
}
}
}
return real_node;
}
void
RoxorCore::const_defined(ID path)
{
// Invalidate constant cache.
std::map<ID, struct ccache *>::iterator iter = ccache.find(path);
if (iter != ccache.end()) {
iter->second->val = Qundef;
}
}
extern "C"
bool
rb_vm_running(void)
{
return GET_CORE()->get_running();
}
extern "C"
void
rb_vm_set_running(bool flag)
{
GET_CORE()->set_running(flag);
}
extern "C"
VALUE
rb_vm_default_random(void)
{
return GET_CORE()->get_default_random();
}
extern "C"
void
rb_vm_set_default_random(VALUE random)
{
RoxorCore *core = GET_CORE();
RoxorCoreLock lock;
if (core->get_default_random() != random) {
GC_RELEASE(core->get_default_random());
GC_RETAIN(random);
core->set_default_random(random);
}
}
VALUE
RoxorCore::trap_cmd_for_signal(int signal)
{
RoxorCoreLock lock;
return trap_cmd[signal];
}
extern "C"
VALUE
rb_vm_trap_cmd_for_signal(int signal)
{
return GET_CORE()->trap_cmd_for_signal(signal);
}
int
RoxorCore::trap_level_for_signal(int signal)
{
RoxorCoreLock lock;
return trap_level[signal];
}
extern "C"
int
rb_vm_trap_level_for_signal(int signal)
{
return GET_CORE()->trap_level_for_signal(signal);
}
void
RoxorCore::set_trap_for_signal(VALUE trap, int level, int signal)
{
RoxorCoreLock lock;
VALUE oldtrap = trap_cmd[signal];
if (oldtrap != trap) {
GC_RELEASE(oldtrap);
GC_RETAIN(trap);
trap_cmd[signal] = trap;
trap_level[signal] = level;
}
}
extern "C"
void
rb_vm_set_trap_for_signal(VALUE trap, int level, int signal)
{
GET_CORE()->set_trap_for_signal(trap, level, signal);
}
extern "C"
bool
rb_vm_abort_on_exception(void)
{
return GET_CORE()->get_abort_on_exception();
}
extern "C"
void
rb_vm_set_abort_on_exception(bool flag)
{
GET_CORE()->set_abort_on_exception(flag);
}
static inline VALUE
rb_const_get_direct(VALUE klass, ID id)
{
// Search the given class.
CFDictionaryRef iv_dict = rb_class_ivar_dict(klass);
if (iv_dict != NULL) {
retry:
VALUE value;
if (CFDictionaryGetValueIfPresent(iv_dict, (const void *)id,
(const void **)&value)) {
if (value == Qundef) {
// Constant is a candidate for autoload. We must release the
// GIL before requiring the file and acquire it again.
GET_CORE()->unlock();
const bool autoloaded = RTEST(rb_autoload_load(klass, id));
GET_CORE()->lock();
if (autoloaded) {
goto retry;
}
}
return value;
}
}
// Search the included modules.
VALUE mods = rb_attr_get(klass, idIncludedModules);
if (mods != Qnil) {
int i, count = RARRAY_LEN(mods);
for (i = 0; i < count; i++) {
VALUE mod = RARRAY_AT(mods, i);
VALUE val = rb_const_get_direct(mod, id);
if (val != Qundef) {
return val;
}
}
}
return Qundef;
}
#if ROXOR_VM_DEBUG_CONST
extern "C" const char *ruby_node_name(int node);
static void
rb_vm_print_outer_stack(const char *fname, NODE *node, const char *function, int line,
rb_vm_outer_t *outer_stack, const char *prefix)
{
if (fname != NULL) {
printf("%s:", fname);
}
if (node != NULL) {
printf("%ld:%s:", nd_line(node), ruby_node_name(nd_type(node)));
}
printf("%s:%d:", function, line);
if (prefix != NULL && prefix[0] != '\0') {
printf("%s ", prefix);
}
printf("outer_stack(");
bool first = true;
for (rb_vm_outer_t *o = outer_stack; o != NULL; o = o->outer) {
if (first) {
first = false;
}
else {
printf(" > ");
}
printf("%s", class_getName(o->klass));
if (o->pushed_by_eval) {
printf("[skip]");
}
}
printf(")\n");
}
#endif
extern "C"
VALUE
rb_vm_const_lookup_level(VALUE outer, ID path, bool lexical, bool defined,
rb_vm_outer_t *outer_stack)
{
rb_vm_check_if_module(outer);
#if ROXOR_VM_DEBUG_CONST
printf("%s:%d:%s:outer(%s) path(%s) lexical(%s) defined(%s) outer_stack(%p)\n", __FILE__, __LINE__, __FUNCTION__,
class_getName((Class)outer), rb_id2name(path), lexical ? "true" : "false", defined ? "true" : "false", outer_stack);
if (lexical) {
GET_CORE()->lock();
rb_vm_print_outer_stack(NULL, NULL, __FUNCTION__, __LINE__,
GET_VM()->get_outer_stack(), "vm->get_outer_stack");
rb_vm_print_outer_stack(NULL, NULL, __FUNCTION__, __LINE__,
GET_VM()->get_current_outer(), "vm->get_current_outer");
GET_CORE()->unlock();
}
#endif
if (lexical && outer_stack != NULL) {
// Let's do a lexical lookup before a hierarchical one, by looking for
// the given constant in all modules under the given outer.
GET_CORE()->lock();
#if ROXOR_VM_DEBUG_CONST
rb_vm_print_outer_stack(NULL, NULL, __FUNCTION__, __LINE__,
outer_stack, "compile time");
#endif
rb_vm_outer_t *root_outer = outer_stack;
while (root_outer != NULL && root_outer->pushed_by_eval) {
root_outer = root_outer->outer;
}
for (rb_vm_outer_t *o = root_outer; o != NULL; o = o->outer) {
if (o->pushed_by_eval) {
continue;
}
VALUE val = rb_const_get_direct((VALUE)o->klass, path);
if (val != Qundef) {
GET_CORE()->unlock();
return defined ? Qtrue : val;
}
}
if (root_outer && !NIL_P(root_outer->klass)) {
outer = (VALUE)root_outer->klass;
}
GET_CORE()->unlock();
}
// Nothing was found earlier so here we do a hierarchical lookup.
return defined ? rb_const_defined(outer, path) : rb_const_get(outer, path);
}
extern "C"
void
rb_vm_const_is_defined(ID path)
{
GET_CORE()->const_defined(path);
}
extern "C"
VALUE
rb_vm_module_nesting(void)
{
VALUE ary = rb_ary_new();
for (rb_vm_outer_t *o = GET_VM()->get_current_outer(); o != NULL; o = o->outer) {
if (!o->pushed_by_eval) {
rb_ary_push(ary, (VALUE)o->klass);
}
}
return ary;
}
extern "C"
VALUE
rb_vm_module_constants(void)
{
VALUE cbase = 0;
void *data = 0;
for (rb_vm_outer_t *o = GET_VM()->get_current_outer(); o != NULL; o = o->outer) {
if (!o->pushed_by_eval) {
data = rb_mod_const_at((VALUE)o->klass, data);
if (cbase == 0) {
cbase = (VALUE)o->klass;
}
}
}
data = rb_mod_const_at(rb_cObject, data);
if (cbase == 0) {
cbase = rb_cObject;
}
if (cbase != 0) {
data = rb_mod_const_of(cbase, data);
}
return rb_const_list(data);
}
static VALUE
get_klass_const(VALUE outer, ID path, bool lexical, rb_vm_outer_t *outer_stack)
{
VALUE klass = Qundef;
if (lexical) {
if (rb_vm_const_lookup(outer, path, true, true, outer_stack) == Qtrue) {
klass = rb_vm_const_lookup(outer, path, true, false, outer_stack);
}
}
else {
if (rb_const_defined_at(outer, path)) {
klass = rb_const_get_at(outer, path);
}
}
if (klass != Qundef) {
rb_vm_check_if_module(klass);
if (outer != rb_cObject && !RCLASS_RUBY(klass)) {
// Ignore classes retrieved by the dynamic resolver.
klass = Qundef;
}
}
return klass;
}
extern "C"
VALUE
rb_vm_define_class(ID path, VALUE outer, VALUE super, int flags,
unsigned char dynamic_class, rb_vm_outer_t *outer_stack)
{
assert(path > 0);
if (flags & DEFINE_OUTER) {
if (outer_stack == NULL) {
outer = rb_cNSObject;
}
else {
outer = outer_stack->klass ? (VALUE)outer_stack->klass : Qnil;
}
}
rb_vm_check_if_module(outer);
VALUE klass = get_klass_const(outer, path, dynamic_class, outer_stack);
if (klass != Qundef) {
// Constant is already defined.
if (!(flags & DEFINE_MODULE) && super != 0) {
if (rb_class_real(RCLASS_SUPER(klass), true) != super) {
rb_raise(rb_eTypeError, "superclass mismatch for class %s",
rb_class2name(klass));
}
}
}
else {
// Prepare the constant outer.
VALUE const_outer;
if ((flags & DEFINE_OUTER) || (flags & DEFINE_SUB_OUTER)) {
const_outer = outer;
}
else {
const_outer = rb_cObject;
}
// Define the constant.
if (flags & DEFINE_MODULE) {
assert(super == 0);
klass = rb_define_module_id(path);
rb_set_class_path2(klass, outer, rb_id2name(path), const_outer);
rb_const_set(outer, path, klass);
}
else {
if (super == 0) {
super = rb_cObject;
}
else {
if (TYPE(super) != T_CLASS) {
rb_raise(rb_eTypeError,
"wrong argument type (expected Class)");
}
}
klass = rb_define_class_id(path, super);
rb_set_class_path2(klass, outer, rb_id2name(path), const_outer);
rb_const_set(outer, path, klass);
rb_class_inherited(super, klass);
}
}
#if ROXOR_VM_DEBUG
if (flags & DEFINE_MODULE) {
printf("define module %s::%s\n",
class_getName((Class)outer),
rb_id2name(path));
}
else {
printf("define class %s::%s < %s\n",
class_getName((Class)outer),
rb_id2name(path),
class_getName((Class)super));
}
#endif
return klass;
}
extern "C"
struct icache *
rb_vm_ivar_slot_allocate(void)
{
struct icache *icache = (struct icache *)malloc(sizeof(struct icache));
assert(icache != NULL);
icache->klass = 0;
icache->slot = SLOT_CACHE_VIRGIN;
return icache;
}
extern "C"
int
rb_vm_get_ivar_slot(VALUE obj, ID name, bool create)
{
if (TYPE(obj) == T_OBJECT) {
unsigned int i;
for (i = 0; i < ROBJECT(obj)->num_slots; i++) {
if (ROBJECT(obj)->slots[i].name == name) {
return i;
}
}
if (create) {
for (i = 0; i < ROBJECT(obj)->num_slots; i++) {
if (ROBJECT(obj)->slots[i].value == Qundef) {
ROBJECT(obj)->slots[i].name = name;
return i;
}
}
const int new_slot = ROBJECT(obj)->num_slots;
rb_vm_regrow_robject_slots(ROBJECT(obj), new_slot + 1);
ROBJECT(obj)->slots[new_slot].name = name;
return new_slot;
}
}
return -1;
}
extern "C" void rb_print_undef(VALUE, ID, int);
static void
vm_alias_method(Class klass, Method method, ID name, bool noargs)
{
IMP imp = method_getImplementation(method);
if (UNAVAILABLE_IMP(imp)) {
return;
}
const char *types = method_getTypeEncoding(method);
SEL sel = rb_vm_id_to_sel(name, noargs ? 0 : 1);
rb_vm_method_node_t *node = GET_CORE()->method_node_get(method);
if (node != NULL) {
GET_CORE()->add_method(klass, sel, imp, node->ruby_imp,
node->arity, node->flags, types);
}
else {
class_replaceMethod(klass, sel, imp, types);
}
}
static void
vm_alias(VALUE outer, ID name, ID def)
{
if (NIL_P(outer)) {
rb_raise(rb_eTypeError, "no class to make alias");
}
rb_frozen_class_p(outer);
if (outer == rb_cObject) {
rb_secure(4);
}
VALUE dest = outer;
Class klass = (Class)outer;
Class dest_klass = (Class)dest;
const bool klass_is_mod = TYPE(klass) == T_MODULE;
const char *def_str = rb_id2name(def);
SEL sel = sel_registerName(def_str);
Method def_method1 = class_getInstanceMethod(klass, sel);
if (def_method1 == NULL && klass_is_mod) {
def_method1 = class_getInstanceMethod((Class)rb_cObject, sel);
}
Method def_method2 = NULL;
if (def_str[strlen(def_str) - 1] != ':') {
char tmp[100];
snprintf(tmp, sizeof tmp, "%s:", def_str);
sel = sel_registerName(tmp);
def_method2 = class_getInstanceMethod(klass, sel);
if (def_method2 == NULL && klass_is_mod) {
def_method2 = class_getInstanceMethod((Class)rb_cObject, sel);
}
}
if (def_method1 == NULL && def_method2 == NULL) {
rb_print_undef((VALUE)klass, def, 0);
}
if (def_method1 != NULL) {
vm_alias_method(dest_klass, def_method1, name, true);
}
if (def_method2 != NULL) {
vm_alias_method(dest_klass, def_method2, name, false);
}
}
extern "C"
void
rb_vm_alias2(VALUE outer, VALUE name, VALUE def, unsigned char dynamic_class)
{
if (dynamic_class) {
Class k = GET_VM()->get_current_class();
if (NIL_P(outer)) {
rb_raise(rb_eTypeError, "no class to make alias");
}
else if (k != NULL) {
outer = (VALUE)k;
}
}
// Given arguments should always be symbols (compiled as such).
assert(TYPE(name) == T_SYMBOL);
assert(TYPE(def) == T_SYMBOL);
vm_alias(outer, SYM2ID(name), SYM2ID(def));
}
extern "C"
void
rb_vm_alias(VALUE outer, ID name, ID def)
{
vm_alias(outer, name, def);
}
extern "C"
void
rb_vm_undef(VALUE klass, ID name, unsigned char dynamic_class)
{
if (dynamic_class) {
Class k = GET_VM()->get_current_class();
if (k != NULL) {
klass = (VALUE)k;
}
}
rb_vm_undef_method((Class)klass, name, true);
}
extern "C"
void
rb_vm_undef2(VALUE klass, VALUE sym, unsigned char dynamic_class)
{
assert(TYPE(sym) == T_SYMBOL);
return rb_vm_undef(klass, SYM2ID(sym), dynamic_class);
}
extern "C"
VALUE
rb_vm_defined(VALUE self, int type, VALUE what, VALUE what2, rb_vm_outer_t *outer_stack)
{
const char *str = NULL;
switch (type) {
case DEFINED_IVAR:
if (rb_ivar_defined(self, (ID)what)) {
str = "instance-variable";
}
break;
case DEFINED_GVAR:
if (rb_gvar_defined((struct global_entry *)what)) {
str = "global-variable";
}
break;
case DEFINED_CVAR:
if (rb_cvar_defined(CLASS_OF(self), (ID)what)) {
str = "class variable";
}
break;
case DEFINED_CONST:
case DEFINED_LCONST:
{
if (rb_vm_const_lookup(what2, (ID)what, type == DEFINED_LCONST, true, outer_stack)) {
str = "constant";
}
}
break;
case DEFINED_SUPER:
case DEFINED_METHOD:
{
VALUE klass = CLASS_OF(self);
if (type == DEFINED_SUPER) {
klass = RCLASS_SUPER(klass);
}
if (what == 0) {
rb_raise(rb_eRuntimeError,
"defined?(super) out of a method block isn't supported");
}
const char *idname = rb_id2name((ID)what);
SEL sel = sel_registerName(idname);
bool ok = class_getInstanceMethod((Class)klass, sel) != NULL;
if (!ok && idname[strlen(idname) - 1] != ':') {
char buf[100];
snprintf(buf, sizeof buf, "%s:", idname);
sel = sel_registerName(buf);
ok = class_getInstanceMethod((Class)klass, sel) != NULL;
}
if (ok) {
str = type == DEFINED_SUPER ? "super" : "method";
}
}
break;
default:
printf("unknown defined? type %d", type);
abort();
}
return str == NULL ? Qnil : rb_str_new2(str);
}
static bool
kvo_sel(Class klass, const char *selname, const size_t selsize,
const char *begin, const char *end)
{
// ^#{begin}(.+)#{end}$ -> token
const size_t begin_len = strlen(begin);
const size_t end_len = strlen(end);
unsigned int token_beg = 0, token_end = selsize;
if (begin_len > 0) {
if (strncmp(selname, begin, begin_len) != 0 || selsize <= begin_len) {
return false;
}
token_beg = begin_len;
}
if (end_len > 0) {
const char *p = strstr(selname, end);
if (p == NULL || p + end_len != selname + selsize) {
return false;
}
token_end = p - selname;
}
const size_t token_len = token_end - token_beg;
char token[100];
if (token_len > sizeof(token)) {
return false;
}
memcpy(token, &selname[token_beg], token_len);
token[token_len] = '\0';
if (strchr(token, ':') != NULL) {
return false;
}
#if 1
// token must start with a capital character.
return isupper(token[0]);
#else
// token must start with a capital character.
if (!isupper(token[0])) {
return false;
}
// Decapitalize the token and look if it's a valid KVO attribute.
token[0] = tolower(token[0]);
SEL sel = sel_registerName(token);
return class_getInstanceMethod(klass, sel) != NULL;
#endif
}
static const char *
get_bs_method_type(bs_element_method_t *bs_method, int idx)
{
const char *type = rb_get_bs_method_type(bs_method, idx);
if (type == NULL) {
type = "@";
}
return type;
}
static void
resolve_method_type(char *buf, const size_t buflen, Class klass, Method m,
SEL sel, const unsigned int types_count)
{
bs_element_method_t *bs_method = GET_CORE()->find_bs_method(klass, sel);
if (m == NULL
|| !rb_objc_get_types(Qnil, klass, sel, m, bs_method, buf, buflen)) {
std::string *informal_type =
GET_CORE()->find_bs_informal_protocol_method(sel,
class_isMetaClass(klass));
if (informal_type != NULL) {
// Get the signature from the BridgeSupport database as an
// informal protocol method.
const char *informal_type_str = informal_type->c_str();
strlcpy(buf, informal_type_str, buflen);
for (unsigned int i = TypeArity(informal_type_str);
i < types_count; i++) {
strlcat(buf, "@", buflen);
}
}
else {
// Generate an automatic signature. We do check for KVO selectors
// which require a customized signature, otherwise we do generate
// one that assumes that the return value and all arguments are
// objects ('@').
const char *selname = sel_getName(sel);
const size_t selsize = strlen(selname);
if (kvo_sel(klass, selname, selsize, "countOf", "")) {
strlcpy(buf, "i@:", buflen);
}
else if (kvo_sel(klass, selname, selsize, "objectIn", "AtIndex:")) {
strlcpy(buf, "@@:i", buflen);
}
else if (kvo_sel(klass, selname, selsize, "insertObject:in",
"AtIndex:")) {
strlcpy(buf, "v@:@i", buflen);
}
else if (kvo_sel(klass, selname, selsize, "removeObjectFrom",
"AtIndex:")) {
strlcpy(buf, "v@:i", buflen);
}
else if (kvo_sel(klass, selname, selsize, "replaceObjectIn",
"AtIndex:withObject:")) {
strlcpy(buf, "v@:i@", buflen);
}
#if 0 // TODO
else if (kvo_sel(klass, selname, selsize, "get", ":range:")) {
}
#endif
else if (bs_method != NULL) {
buf[0] = '\0';
// retval, self and sel.
strlcat(buf, get_bs_method_type(bs_method, -1), buflen);
strlcat(buf, "@", buflen);
strlcat(buf, ":", buflen);
// Arguments.
for (unsigned int i = 3; i < types_count; i++) {
strlcat(buf, get_bs_method_type(bs_method, i - 3), buflen);
}
}
else {
assert(types_count < buflen);
// retval, self and sel.
buf[0] = strncmp(selname, "set", 3) == 0 ? 'v' : '@';
buf[1] = '@';
buf[2] = ':';
// Arguments.
for (unsigned int i = 3; i < types_count; i++) {
buf[i] = '@';
}
buf[types_count] = '\0';
}
}
}
else {
assert(strlen(buf) >= 3);
for (unsigned int i = rb_method_getNumberOfArguments(m) + 1;
i < types_count; i++) {
strlcat(buf, "@", buflen);
}
}
}
rb_vm_method_node_t *
RoxorCore::retype_method(Class klass, rb_vm_method_node_t *node,
const char *old_types, const char *new_types)
{
#if MACRUBY_STATIC
rb_raise(rb_eRuntimeError, "methods cannot be retyped in MacRuby static");
#else
if (strcmp(old_types, new_types) == 0) {
// No need to retype.
// XXX might be better to compare every type after filtering stack
// size and other crappy modifiers.
return node;
}
const int new_types_arity = TypeArity(new_types);
char buf[100];
if (node->arity.real + 3 >= new_types_arity) {
// The method arity is bigger than the number of types of the new
// signature, so we need to pad.
strlcpy(buf, new_types, sizeof buf);
for (int i = 0; i < node->arity.real + 3 - new_types_arity; i++) {
strlcat(buf, "@", sizeof buf);
}
new_types = &buf[0];
}
RoxorCoreLock lock;
// Re-generate ObjC stub.
Function *objc_func = RoxorCompiler::shared->compile_objc_stub(NULL,
node->ruby_imp, node->arity, new_types);
node->objc_imp = compile(objc_func, false);
// TODO: free LLVM machine code from old objc IMP
objc_to_ruby_stubs[node->ruby_imp] = node->objc_imp;
// Re-add the method.
return add_method(klass, node->sel, node->objc_imp, node->ruby_imp,
node->arity, node->flags, new_types);
#endif
}
struct vm_objc_imp_type {
const char *types;
IMP imp;
vm_objc_imp_type(const char *_types, IMP _imp) {
types = _types;
imp = _imp;
}
};
rb_vm_method_node_t *
RoxorCore::resolve_method(Class klass, SEL sel, void *func,
const rb_vm_arity_t &arity, int flags, IMP imp, Method m,
void *objc_imp_types)
{
#if MACRUBY_STATIC
assert(imp != NULL);
#else
if (imp == NULL) {
// Compile if necessary.
assert(func != NULL);
imp = compile((Function *)func);
}
#endif
// Resolve Objective-C signature.
const int types_count = arity.real + 3; // retval, self and sel
char types[100];
resolve_method_type(types, sizeof types, klass, m, sel, types_count);
// Retrieve previous-generated Objective-C stub if possible.
IMP objc_imp = NULL;
if (objc_imp_types != NULL) {
std::vector<vm_objc_imp_type> *v =
(std::vector<vm_objc_imp_type> *)objc_imp_types;
for (std::vector<vm_objc_imp_type>::iterator i = v->begin();
i != v->end(); ++i) {
if (strcmp(types, i->types) == 0) {
objc_imp = i->imp;
break;
}
}
}
#if MACRUBY_STATIC
if (objc_imp == NULL) {
printf("can't define method `%s' because no Objective-C stub was pre-compiled for types `%s'\n", sel_getName(sel), types);
abort();
}
#else
// Generate Objective-C stub if needed.
if (objc_imp == NULL) {
std::map<IMP, IMP>::iterator iter = objc_to_ruby_stubs.find(imp);
if (iter == objc_to_ruby_stubs.end()) {
Function *objc_func = RoxorCompiler::shared->compile_objc_stub(
(Function *)func, imp, arity, types);
objc_imp = compile(objc_func);
objc_to_ruby_stubs[imp] = objc_imp;
}
else {
objc_imp = iter->second;
}
}
// Delete the selector from the not-yet-JIT'ed cache if needed.
std::multimap<Class, SEL>::iterator iter2, last2;
iter2 = method_source_sels.find(klass);
if (iter2 != method_source_sels.end()) {
last2 = method_source_sels.upper_bound(klass);
while (iter2 != last2) {
if (iter2->second == sel) {
method_source_sels.erase(iter2);
break;
}
++iter2;
}
}
#endif
// Finally, add the method.
return add_method(klass, sel, objc_imp, imp, arity, flags, types);
}
#if !defined(MACRUBY_STATIC)
bool
RoxorCore::resolve_methods(std::map<Class, rb_vm_method_source_t *> *map,
Class klass, SEL sel)
{
bool did_something = false;
std::map<Class, rb_vm_method_source_t *>::iterator iter = map->begin();
while (iter != map->end()) {
Class k = iter->first;
while (k != klass && k != NULL) {
k = class_getSuperclass(k);
}
if (k != NULL) {
rb_vm_method_source_t *m = iter->second;
resolve_method(iter->first, sel, m->func, m->arity, m->flags,
NULL, NULL, NULL);
map->erase(iter++);
free(m);
did_something = true;
}
else {
++iter;
}
}
// If the map is empty, there is no point in keeping it.
if (map->size() == 0) {
std::map<SEL, std::map<Class, rb_vm_method_source_t *> *>::iterator
iter = method_sources.find(sel);
assert(iter != method_sources.end());
method_sources.erase(iter);
delete map;
}
return did_something;
}
extern "C"
bool
rb_vm_resolve_method(Class klass, SEL sel)
{
if (!GET_CORE()->get_running()) {
return false;
}
// Make sure the VM is created & registered to avoid a deadlock.
GET_VM();
RoxorCoreLock lock;
bool status = false;
#if ROXOR_VM_DEBUG
printf("resolving %c[%s %s]\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel));
#endif
std::map<Class, rb_vm_method_source_t *> *map =
GET_CORE()->method_sources_for_sel(sel, false);
if (map == NULL) {
goto bails;
}
// Find the class where the method should be defined.
while (map->find(klass) == map->end() && klass != NULL) {
klass = class_getSuperclass(klass);
}
if (klass == NULL) {
goto bails;
}
// Now let's resolve all methods of the given name on the given class
// and superclasses.
status = GET_CORE()->resolve_methods(map, klass, sel);
bails:
return status;
}
void
RoxorCore::prepare_method(Class klass, SEL sel, Function *func,
const rb_vm_arity_t &arity, int flags)
{
#if ROXOR_VM_DEBUG
printf("preparing %c[%s %s] on class %p LLVM func %p flags %d\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel),
klass,
func,
flags);
#endif
std::map<Class, rb_vm_method_source_t *> *map =
method_sources_for_sel(sel, true);
std::map<Class, rb_vm_method_source_t *>::iterator iter = map->find(klass);
rb_vm_method_source_t *m = NULL;
if (iter == map->end()) {
m = (rb_vm_method_source_t *)malloc(sizeof(rb_vm_method_source_t));
assert(m != NULL);
map->insert(std::make_pair(klass, m));
method_source_sels.insert(std::make_pair(klass, sel));
}
else {
m = iter->second;
}
m->func = func;
m->arity = arity;
m->flags = flags;
invalidate_respond_to_cache();
}
#endif
#if !defined(MACRUBY_STATIC)
static bool class_has_custom_resolver(Class klass);
#endif
static void
prepare_method(Class klass, bool dynamic_class, SEL sel, void *data,
const rb_vm_arity_t &arity, int flags, bool precompiled,
void *objc_imp_types)
{
if (OBJ_FROZEN(klass)) {
rb_error_frozen("class/module");
}
if (dynamic_class) {
Class k;
rb_vm_outer_t *o = GET_VM()->get_outer_stack();
if (o == NULL) {
k = (Class)rb_cNSObject;
}
else {
k = o->klass;
}
if (NIL_P(k)) {
rb_raise(rb_eTypeError, "no class/module to add method");
}
else if (klass != NULL) {
const bool meta = class_isMetaClass(klass);
klass = k;
if (meta && !class_isMetaClass(klass)) {
klass = *(Class *)klass;
}
}
}
else {
rb_vm_outer_t *o = GET_VM()->get_outer_stack();
if ((o != NULL && o->klass == NULL) || NIL_P(o)) {
rb_raise(rb_eTypeError, "no class/module to add method");
}
}
const long v = RCLASS_VERSION(klass);
if (v & RCLASS_SCOPE_PRIVATE) {
flags |= VM_METHOD_PRIVATE;
}
else if (v & RCLASS_SCOPE_PROTECTED) {
flags |= VM_METHOD_PROTECTED;
}
if (rb_objc_ignored_sel(sel)) {
return;
}
const char *sel_name = sel_getName(sel);
const bool genuine_selector = sel_name[strlen(sel_name) - 1] == ':';
#if !defined(MACRUBY_STATIC)
const bool custom_resolver = class_has_custom_resolver(klass);
#endif
bool redefined = false;
bool added_modfunc = false;
SEL orig_sel = sel;
Method m;
IMP imp = NULL;
prepare_method:
m = class_getInstanceMethod(klass, sel);
#if !defined(MACRUBY_STATIC)
if (m == NULL && rb_vm_resolve_method(klass, sel)) {
m = class_getInstanceMethod(klass, sel);
assert(m != NULL);
}
#endif
if (precompiled) {
if (imp == NULL) {
imp = (IMP)data;
}
assert(objc_imp_types != NULL);
GET_CORE()->resolve_method(klass, sel, NULL, arity, flags, imp, m,
objc_imp_types);
}
else {
#if MACRUBY_STATIC
abort();
#else
Function *func = (Function *)data;
GET_CORE()->lock();
if (m != NULL || custom_resolver) {
// The method already exists _or_ the class implemented a custom
// Objective-C method resolver - we need to JIT it.
if (imp == NULL) {
imp = GET_CORE()->compile(func);
}
GET_CORE()->resolve_method(klass, sel, func, arity, flags, imp, m,
objc_imp_types);
}
else {
// Let's keep the method and JIT it later on demand.
GET_CORE()->prepare_method(klass, sel, func, arity, flags);
}
GET_CORE()->unlock();
#endif
}
if (!redefined) {
char buf[100];
SEL new_sel = 0;
if (!genuine_selector) {
snprintf(buf, sizeof buf, "%s:", sel_name);
new_sel = sel_registerName(buf);
if (arity.max != arity.min) {
sel = new_sel;
redefined = true;
goto prepare_method;
}
}
else {
strlcpy(buf, sel_name, sizeof buf);
buf[strlen(buf) - 1] = 0; // remove the ending ':'
new_sel = sel_registerName(buf);
if (arity.min == 0) {
sel = new_sel;
redefined = true;
goto prepare_method;
}
}
Method tmp_m = class_getInstanceMethod(klass, new_sel);
if (tmp_m != NULL) {
// If we add -[foo:] and the class responds to -[foo], we need
// to disable it (and vice-versa).
class_replaceMethod(klass, new_sel,
(IMP)rb_vm_undefined_imp, method_getTypeEncoding(tmp_m));
// Invalidate the cache so that the previously defined
// implementation is not called anymore if the call was cached
GET_CORE()->invalidate_method_cache(new_sel);
}
}
if (RCLASS_VERSION(klass) & RCLASS_IS_INCLUDED) {
VALUE included_in_classes = rb_attr_get((VALUE)klass,
idIncludedInClasses);
if (included_in_classes != Qnil) {
int i, count = RARRAY_LEN(included_in_classes);
for (i = 0; i < count; i++) {
VALUE mod = RARRAY_AT(included_in_classes, i);
rb_vm_set_current_scope(mod, SCOPE_PUBLIC);
prepare_method((Class)mod, false, orig_sel, data, arity,
flags, precompiled, objc_imp_types);
rb_vm_set_current_scope(mod, SCOPE_DEFAULT);
}
}
}
GET_CORE()->method_added(klass, sel);
if (!added_modfunc && (v & RCLASS_SCOPE_MOD_FUNC)) {
added_modfunc = true;
redefined = false;
klass = *(Class *)klass;
sel = orig_sel;
goto prepare_method;
}
}
#if !defined(MACRUBY_STATIC)
extern "C"
void
rb_vm_prepare_method(Class klass, unsigned char dynamic_class, SEL sel,
Function *func, const rb_vm_arity_t arity, int flags)
{
prepare_method(klass, dynamic_class, sel, (void *)func, arity,
flags, false, NULL);
}
#endif
extern "C"
void
rb_vm_prepare_method2(Class klass, unsigned char dynamic_class, SEL sel,
IMP ruby_imp, const rb_vm_arity_t arity, int flags, ...)
{
std::vector<vm_objc_imp_type> v;
va_list ar;
va_start(ar, flags);
do {
const char *types = va_arg(ar, const char *);
if (types == NULL) {
break;
}
IMP imp = va_arg(ar, IMP);
assert(imp != NULL);
v.push_back(vm_objc_imp_type(types, imp));
}
while (true);
va_end(ar);
prepare_method(klass, dynamic_class, sel, (void *)ruby_imp, arity,
flags, true, (void *)&v);
}
#define VISI(x) ((x)&NOEX_MASK)
#define VISI_CHECK(x,f) (VISI(x) == (f))
static void
push_method(VALUE ary, SEL sel, int flags, int (*filter) (VALUE, ID, VALUE))
{
if (rb_objc_ignored_sel(sel)) {
return;
}
const char *selname = sel_getName(sel);
const size_t len = strlen(selname);
assert(len > 0);
char *buf = NULL;
const char *p = strchr(selname, ':');
if (p != NULL && strchr(p + 1, ':') == NULL) {
// remove trailing ':' for methods with arity 1
buf = (char *)malloc(len);
assert(buf != NULL);
strncpy(buf, selname, len);
buf[len - 1] = '\0';
selname = buf;
}
ID mid = rb_intern(selname);
VALUE sym = ID2SYM(mid);
if (buf != NULL) {
free(buf);
buf = NULL;
}
if (rb_ary_includes(ary, sym) == Qfalse) {
int type = NOEX_PUBLIC;
if (flags & VM_METHOD_PRIVATE) {
type = NOEX_PRIVATE;
}
else if (flags & VM_METHOD_PROTECTED) {
type = NOEX_PROTECTED;
}
(*filter)(sym, type, ary);
}
}
#if !defined(MACRUBY_STATIC)
rb_vm_method_source_t *
RoxorCore::method_source_get(Class klass, SEL sel)
{
std::map<SEL, std::map<Class, rb_vm_method_source_t *> *>::iterator iter
= method_sources.find(sel);
if (iter != method_sources.end()) {
std::map<Class, rb_vm_method_source_t *> *m = iter->second;
std::map<Class, rb_vm_method_source_t *>::iterator iter2
= m->find(klass);
if (iter2 != m->end()) {
return iter2->second;
}
}
return NULL;
}
#endif
void
RoxorCore::get_methods(VALUE ary, Class klass, bool include_objc_methods,
int (*filter) (VALUE, ID, VALUE))
{
RoxorCoreLock lock;
// TODO take into account undefined methods
unsigned int count;
Method *methods = class_copyMethodList(klass, &count);
if (methods != NULL) {
for (unsigned int i = 0; i < count; i++) {
Method m = methods[i];
if (UNAVAILABLE_IMP(method_getImplementation(m))) {
continue;
}
rb_vm_method_node_t *node = method_node_get(m);
if (node == NULL && !include_objc_methods) {
continue;
}
SEL sel = method_getName(m);
push_method(ary, sel, node == NULL ? 0 : node->flags, filter);
}
free(methods);
}
#if !defined(MACRUBY_STATIC)
Class k = klass;
std::multimap<Class, SEL>::iterator iter =
method_source_sels.find(k);
if (iter != method_source_sels.end()) {
std::multimap<Class, SEL>::iterator last =
method_source_sels.upper_bound(k);
for (; iter != last; ++iter) {
SEL sel = iter->second;
rb_vm_method_source_t *src = method_source_get(k, sel);
assert(src != NULL);
push_method(ary, sel, src->flags, filter);
}
}
#endif
}
extern "C"
void
rb_vm_push_methods(VALUE ary, VALUE mod, bool include_objc_methods,
int (*filter) (VALUE, ID, VALUE))
{
GET_CORE()->get_methods(ary, (Class)mod, include_objc_methods, filter);
}
extern "C"
void
rb_vm_copy_methods(Class from_class, Class to_class)
{
GET_CORE()->copy_methods(from_class, to_class);
}
extern "C"
bool
rb_vm_copy_method(Class klass, Method m)
{
return GET_CORE()->copy_method(klass, m);
}
bool
RoxorCore::copy_method(Class klass, Method m)
{
SEL sel = method_getName(m);
#if ROXOR_VM_DEBUG
printf("copy %c[%s %s] from method %p imp %p\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel),
m,
method_getImplementation(m));
#endif
class_replaceMethod(klass, sel, method_getImplementation(m),
method_getTypeEncoding(m));
rb_vm_method_node_t *node = method_node_get(m);
if (node != NULL) {
Method m2 = class_getInstanceMethod(klass, sel);
assert(m2 != NULL);
assert(method_getImplementation(m2) == method_getImplementation(m));
rb_vm_method_node_t *node2 = method_node_get(m2, true);
memcpy(node2, node, sizeof(rb_vm_method_node_t));
}
return true;
}
void
RoxorCore::copy_methods(Class from_class, Class to_class)
{
Method *methods;
unsigned int i, methods_count;
// Copy existing Objective-C methods.
methods = class_copyMethodList(from_class, &methods_count);
if (methods != NULL) {
for (i = 0; i < methods_count; i++) {
Method m = methods[i];
if (!copy_method(to_class, m)) {
continue;
}
#if !defined(MACRUBY_STATIC)
SEL sel = method_getName(m);
std::map<Class, rb_vm_method_source_t *> *map =
method_sources_for_sel(sel, false);
if (map != NULL) {
// There might be some non-JIT'ed yet methods on subclasses.
resolve_methods(map, to_class, sel);
}
#endif
}
free(methods);
}
#if !defined(MACRUBY_STATIC)
// Copy methods that have not been JIT'ed yet.
// First, make a list of selectors.
std::vector<SEL> sels_to_copy;
std::multimap<Class, SEL>::iterator iter =
method_source_sels.find(from_class);
if (iter != method_source_sels.end()) {
std::multimap<Class, SEL>::iterator last =
method_source_sels.upper_bound(from_class);
for (; iter != last; ++iter) {
sels_to_copy.push_back(iter->second);
}
}
// Force a resolving of these selectors on the target class. This must be
// done outside the next loop since the resolver messes up the Core
// structures.
for (std::vector<SEL>::iterator iter = sels_to_copy.begin();
iter != sels_to_copy.end();
++iter) {
class_getInstanceMethod(to_class, *iter);
}
// Now, let's really copy the lazy methods.
std::vector<SEL> sels_to_add;
for (std::vector<SEL>::iterator iter = sels_to_copy.begin();
iter != sels_to_copy.end();
++iter) {
SEL sel = *iter;
std::map<Class, rb_vm_method_source_t *> *dict =
method_sources_for_sel(sel, false);
if (dict == NULL) {
continue;
}
std::map<Class, rb_vm_method_source_t *>::iterator
iter2 = dict->find(from_class);
if (iter2 == dict->end()) {
continue;
}
rb_vm_method_source_t *m_src = iter2->second;
Method m = class_getInstanceMethod(to_class, sel);
if (m != NULL) {
// The method already exists on the target class, we need to
// JIT it.
IMP imp = GET_CORE()->compile(m_src->func);
resolve_method(to_class, sel, m_src->func, m_src->arity,
m_src->flags, imp, m, NULL);
}
else {
#if ROXOR_VM_DEBUG
printf("lazy copy %c[%s %s] to %c%s\n",
class_isMetaClass(from_class) ? '+' : '-',
class_getName(from_class),
sel_getName(sel),
class_isMetaClass(to_class) ? '+' : '-',
class_getName(to_class));
#endif
rb_vm_method_source_t *m = (rb_vm_method_source_t *)
malloc(sizeof(rb_vm_method_source_t));
assert(m != NULL);
m->func = m_src->func;
m->arity = m_src->arity;
m->flags = m_src->flags;
dict->insert(std::make_pair(to_class, m));
sels_to_add.push_back(sel);
}
}
for (std::vector<SEL>::iterator i = sels_to_add.begin();
i != sels_to_add.end();
++i) {
method_source_sels.insert(std::make_pair(to_class, *i));
}
#endif
}
extern "C"
bool
rb_vm_lookup_method2(Class klass, ID mid, SEL *psel, IMP *pimp,
rb_vm_method_node_t **pnode)
{
const char *idstr = rb_id2name(mid);
SEL sel = sel_registerName(idstr);
if (!rb_vm_lookup_method(klass, sel, pimp, pnode)) {
char buf[100];
snprintf(buf, sizeof buf, "%s:", idstr);
sel = sel_registerName(buf);
if (!rb_vm_lookup_method(klass, sel, pimp, pnode)) {
return false;
}
}
if (psel != NULL) {
*psel = sel;
}
return true;
}
extern "C"
bool
rb_vm_lookup_method(Class klass, SEL sel, IMP *pimp,
rb_vm_method_node_t **pnode)
{
Method m = class_getInstanceMethod(klass, sel);
if (m == NULL) {
return false;
}
IMP imp = method_getImplementation(m);
if (UNAVAILABLE_IMP(imp)) {
return false;
}
if (pimp != NULL) {
*pimp = imp;
}
if (pnode != NULL) {
*pnode = GET_CORE()->method_node_get(m);
}
return true;
}
extern "C"
void
rb_vm_define_attr(Class klass, const char *name, bool read, bool write)
{
assert(klass != NULL);
assert(read || write);
#if MACRUBY_STATIC
rb_raise(rb_eRuntimeError, "attr_* is not supported in MacRuby static");
#else
char buf[100];
snprintf(buf, sizeof buf, "@%s", name);
ID iname = rb_intern(buf);
if (read) {
GET_CORE()->lock();
Function *f = RoxorCompiler::shared->compile_read_attr(iname);
GET_CORE()->unlock();
SEL sel = sel_registerName(name);
rb_vm_prepare_method(klass, false, sel, f, rb_vm_arity(0),
VM_METHOD_FBODY);
}
if (write) {
GET_CORE()->lock();
Function *f = RoxorCompiler::shared->compile_write_attr(iname);
GET_CORE()->unlock();
snprintf(buf, sizeof buf, "%s=:", name);
SEL sel = sel_registerName(buf);
rb_vm_prepare_method(klass, false, sel, f, rb_vm_arity(1),
VM_METHOD_FBODY);
}
#endif
}
static rb_vm_method_node_t *
__rb_vm_define_method(Class klass, SEL sel, IMP objc_imp, IMP ruby_imp,
const rb_vm_arity_t &arity, int flags, bool direct)
{
assert(klass != NULL);
if (rb_objc_ignored_sel(sel)) {
return NULL;
}
if (OBJ_FROZEN(klass)) {
rb_error_frozen("class/module");
}
const char *sel_name = sel_getName(sel);
const bool genuine_selector = sel_name[strlen(sel_name) - 1] == ':';
int types_count = genuine_selector ? arity.real + 3 : 3;
bool redefined = direct;
rb_vm_method_node_t *node;
define_method:
Method method = class_getInstanceMethod(klass, sel);
char types[100];
resolve_method_type(types, sizeof types, klass, method, sel, types_count);
GET_CORE()->lock();
node = GET_CORE()->add_method(klass, sel, objc_imp, ruby_imp, arity,
flags, types);
GET_CORE()->unlock();
if (!redefined) {
if (!genuine_selector && arity.max != arity.min) {
char buf[100];
snprintf(buf, sizeof buf, "%s:", sel_name);
sel = sel_registerName(buf);
types_count = arity.real + 3;
redefined = true;
goto define_method;
}
else if (genuine_selector && arity.min == 0) {
char buf[100];
strlcpy(buf, sel_name, sizeof buf);
buf[strlen(buf) - 1] = 0; // remove the ending ':'
sel = sel_registerName(buf);
types_count = 3;
redefined = true;
goto define_method;
}
}
return node;
}
extern "C"
rb_vm_method_node_t *
rb_vm_define_method(Class klass, SEL sel, IMP imp, NODE *node, bool direct)
{
assert(node != NULL);
// TODO: create objc_imp
return __rb_vm_define_method(klass, sel, imp, imp, rb_vm_node_arity(node),
rb_vm_node_flags(node), direct);
}
extern "C"
rb_vm_method_node_t *
rb_vm_define_method2(Class klass, SEL sel, rb_vm_method_node_t *node,
long flags, bool direct)
{
assert(node != NULL);
if (flags == -1) {
flags = node->flags;
flags &= ~VM_METHOD_PRIVATE;
flags &= ~VM_METHOD_PROTECTED;
}
return __rb_vm_define_method(klass, sel, node->objc_imp, node->ruby_imp,
node->arity, flags, direct);
}
#if !defined(MACRUBY_STATIC)
extern "C"
void
rb_vm_define_method3(Class klass, ID mid, rb_vm_block_t *block)
{
assert(block != NULL);
const int arity = rb_vm_arity_n(block->arity);
SEL sel = rb_vm_id_to_sel(mid, arity);
GET_CORE()->lock();
Function *func = RoxorCompiler::shared->compile_block_caller(block);
IMP imp = GET_CORE()->compile(func);
GET_CORE()->unlock();
NODE *body = rb_vm_cfunc_node_from_imp(klass, arity < -1 ? -2 : arity, imp, 0);
GC_RETAIN(body);
GC_RETAIN(block);
rb_vm_define_method(klass, sel, imp, body, false);
}
extern "C"
void *
rb_vm_generate_mri_stub(void *imp, const int arity)
{
Function *func = RoxorCompiler::shared->compile_mri_stub(imp, arity);
if (func == NULL) {
// Not needed!
return imp;
}
return (void *)GET_CORE()->compile(func, false);
}
#endif
void
RoxorCore::undef_method(Class klass, SEL sel)
{
#if ROXOR_VM_DEBUG
printf("undef %c[%s %s]\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel));
#endif
class_replaceMethod((Class)klass, sel, (IMP)rb_vm_undefined_imp, "@@:");
invalidate_respond_to_cache();
#if 0
std::map<Method, rb_vm_method_node_t *>::iterator iter
= ruby_methods.find(m);
assert(iter != ruby_methods.end());
free(iter->second);
ruby_methods.erase(iter);
#endif
if (get_running()) {
ID mid = sanitize_mid(sel);
if (mid != 0) {
VALUE sym = ID2SYM(mid);
if (RCLASS_SINGLETON(klass)) {
VALUE sk = rb_singleton_class_attached_object((VALUE)klass);
rb_vm_call(sk, selSingletonMethodUndefined, 1, &sym);
}
else {
rb_vm_call((VALUE)klass, selMethodUndefined, 1, &sym);
}
}
}
}
extern "C"
void
rb_vm_undef_method(Class klass, ID name, bool check)
{
if (NIL_P(klass)) {
rb_raise(rb_eTypeError, "no class to undef method");
}
rb_frozen_class_p((VALUE)klass);
const char *name_str = rb_id2name(name);
SEL sel0 = rb_vm_name_to_sel(name_str, 0);
SEL sel1 = rb_vm_name_to_sel(name_str, 1);
rb_vm_method_node_t *node0 = NULL;
rb_vm_method_node_t *node1 = NULL;
bool exist0 = rb_vm_lookup_method((Class)klass, sel0, NULL, &node0);
bool exist1 = rb_vm_lookup_method((Class)klass, sel1, NULL, &node1);
if (!exist0 && !exist1 && check) {
rb_raise(rb_eNameError, "undefined method `%s' for %s `%s'",
name_str,
TYPE(klass) == T_MODULE ? "module" : "class",
rb_class2name((VALUE)klass));
}
if ((exist0 && node0 == NULL) || (exist1 && node1 == NULL)) {
if (check) {
rb_raise(rb_eRuntimeError,
"cannot undefine method `%s' because it is a native method",
name_str);
}
}
else {
GET_CORE()->undef_method(klass, sel0);
if (sel0 != sel1) {
GET_CORE()->undef_method(klass, sel1);
}
}
}
void
RoxorCore::remove_method(Class klass, SEL sel)
{
#if ROXOR_VM_DEBUG
printf("remove %c[%s %s]\n",
class_isMetaClass(klass) ? '+' : '-',
class_getName(klass),
sel_getName(sel));
#endif
Method m = class_getInstanceMethod(klass, sel);
assert(m != NULL);
method_setImplementation(m, (IMP)rb_vm_removed_imp);
invalidate_respond_to_cache();
ID mid = sanitize_mid(sel);
if (mid != 0) {
VALUE sym = ID2SYM(mid);
if (RCLASS_SINGLETON(klass)) {
VALUE sk = rb_singleton_class_attached_object((VALUE)klass);
rb_vm_call(sk, selSingletonMethodRemoved, 1, &sym);
}
else {
rb_vm_call((VALUE)klass, selMethodRemoved, 1, &sym);
}
}
}
extern "C"
void
rb_vm_remove_method(Class klass, ID name)
{
rb_vm_method_node_t *node = NULL;
if (!rb_vm_lookup_method2((Class)klass, name, NULL, NULL, &node)) {
rb_raise(rb_eNameError, "undefined method `%s' for %s `%s'",
rb_id2name(name),
TYPE(klass) == T_MODULE ? "module" : "class",
rb_class2name((VALUE)klass));
}
if (node == NULL) {
rb_raise(rb_eRuntimeError,
"cannot remove method `%s' because it is a native method",
rb_id2name(name));
}
if (node->klass != klass) {
rb_raise(rb_eNameError, "method `%s' not defined in %s",
rb_id2name(name), rb_class2name((VALUE)klass));
}
GET_CORE()->remove_method(klass, node->sel);
}
extern "C"
VALUE
rb_vm_method_missing(VALUE obj, int argc, const VALUE *argv)
{
if (argc == 0 || !SYMBOL_P(argv[0])) {
rb_raise(rb_eArgError, "no id given");
}
const rb_vm_method_missing_reason_t last_call_status =
GET_VM()->get_method_missing_reason();
const char *format = NULL;
VALUE exc = rb_eNoMethodError;
switch (last_call_status) {
case METHOD_MISSING_PRIVATE:
format = "private method `%s' called for %s";
break;
case METHOD_MISSING_PROTECTED:
format = "protected method `%s' called for %s";
break;
case METHOD_MISSING_VCALL:
format = "undefined local variable or method `%s' for %s";
exc = rb_eNameError;
break;
case METHOD_MISSING_SUPER:
format = "super: no superclass method `%s' for %s";
break;
case METHOD_MISSING_DEFAULT:
default:
format = "undefined method `%s' for %s";
break;
}
VALUE meth = rb_sym_to_s(argv[0]);
if (!rb_vm_respond_to(obj, selToS, true)) {
// In case #to_s was undefined on the object, let's generate a
// basic string based on it, because otherwise the following code
// will raise a #method_missing which will result in an infinite loop.
obj = rb_any_to_s(obj);
}
int n = 0;
VALUE args[3];
VALUE not_args[3] = {rb_str_new2(format), obj, meth};
args[n++] = rb_vm_call(rb_cNameErrorMesg, selNot2, 3, not_args);
args[n++] = meth;
if (exc == rb_eNoMethodError) {
args[n++] = rb_ary_new4(argc - 1, argv + 1);
}
exc = rb_class_new_instance(n, args, exc);
rb_exc_raise(exc);
abort(); // never reached
}
void *
RoxorCore::gen_large_arity_stub(int argc, bool is_block)
{
RoxorCoreLock lock;
std::map<int, void *> &stubs =
is_block ? rb_large_arity_bstubs : rb_large_arity_rstubs;
std::map<int, void *>::iterator iter = stubs.find(argc);
void *stub;
if (iter == stubs.end()) {
#if MACRUBY_STATIC
printf("uncached large arity stub (%d)\n", argc);
abort();
#else
Function *f = RoxorCompiler::shared->compile_long_arity_stub(argc,
is_block);
stub = (void *)compile(f, false);
stubs.insert(std::make_pair(argc, stub));
#endif
}
else {
stub = iter->second;
}
return stub;
}
void *
RoxorCore::gen_stub(std::string types, bool variadic, int min_argc,
bool is_objc)
{
RoxorCoreLock lock;
#if ROXOR_VM_DEBUG
printf("gen Ruby -> %s stub with types %s\n", is_objc ? "ObjC" : "C",
types.c_str());
#endif
std::map<std::string, void *> &stubs = is_objc ? objc_stubs : c_stubs;
std::map<std::string, void *>::iterator iter = stubs.find(types);
void *stub;
if (iter == stubs.end()) {
#if MACRUBY_STATIC
printf("uncached %s stub `%s'\n", is_objc ? "ObjC" : "C",
types.c_str());
abort();
#else
Function *f = RoxorCompiler::shared->compile_stub(types.c_str(),
variadic, min_argc, is_objc);
stub = (void *)compile(f);
stubs.insert(std::make_pair(types, stub));
#endif
}
else {
stub = iter->second;
}
return stub;
}
void *
RoxorCore::gen_to_rval_convertor(std::string type)
{
RoxorCoreLock lock;
std::map<std::string, void *>::iterator iter =
to_rval_convertors.find(type);
if (iter != to_rval_convertors.end()) {
return iter->second;
}
#if MACRUBY_STATIC
printf("uncached to_rval convertor %s\n", type.c_str());
abort();
#else
Function *f = RoxorCompiler::shared->compile_to_rval_convertor(
type.c_str());
void *convertor = (void *)compile(f);
to_rval_convertors.insert(std::make_pair(type, convertor));
return convertor;
#endif
}
void *
RoxorCore::gen_to_ocval_convertor(std::string type)
{
RoxorCoreLock lock;
std::map<std::string, void *>::iterator iter =
to_ocval_convertors.find(type);
if (iter != to_ocval_convertors.end()) {
return iter->second;
}
#if MACRUBY_STATIC
printf("uncached to_ocval convertor %s\n", type.c_str());
abort();
#else
Function *f = RoxorCompiler::shared->compile_to_ocval_convertor(
type.c_str());
void *convertor = (void *)compile(f);
to_ocval_convertors.insert(std::make_pair(type, convertor));
return convertor;
#endif
}
static const int VM_LVAR_USES_SIZE = 8;
enum {
VM_LVAR_USE_TYPE_BLOCK = 1,
VM_LVAR_USE_TYPE_BINDING = 2
};
struct rb_vm_var_uses {
int uses_count;
void *uses[VM_LVAR_USES_SIZE];
unsigned char use_types[VM_LVAR_USES_SIZE];
struct rb_vm_var_uses *next;
};
static void
rb_vm_add_lvar_use(rb_vm_var_uses **var_uses, void *use,
unsigned char use_type)
{
if (var_uses == NULL) {
return;
}
if ((*var_uses == NULL)
|| ((*var_uses)->uses_count == VM_LVAR_USES_SIZE)) {
rb_vm_var_uses *new_uses =
(rb_vm_var_uses *)xmalloc(sizeof(rb_vm_var_uses));
new_uses->uses_count = 0;
GC_WB(&new_uses->next, *var_uses);
// var_uses should be on the stack so no need for GC_WB
*var_uses = new_uses;
}
const int current_index = (*var_uses)->uses_count;
GC_WB(&(*var_uses)->uses[current_index], use);
(*var_uses)->use_types[current_index] = use_type;
++(*var_uses)->uses_count;
}
extern "C"
void
rb_vm_add_block_lvar_use(rb_vm_block_t *block)
{
for (rb_vm_block_t *block_for_uses = block;
block_for_uses != NULL;
block_for_uses = block_for_uses->parent_block) {
rb_vm_add_lvar_use(block_for_uses->parent_var_uses, block,
VM_LVAR_USE_TYPE_BLOCK);
}
}
static void
rb_vm_add_binding_lvar_use(rb_vm_binding_t *binding, rb_vm_block_t *block,
rb_vm_var_uses **parent_var_uses)
{
for (rb_vm_block_t *block_for_uses = block;
block_for_uses != NULL;
block_for_uses = block_for_uses->parent_block) {
rb_vm_add_lvar_use(block_for_uses->parent_var_uses, binding,
VM_LVAR_USE_TYPE_BINDING);
}
rb_vm_add_lvar_use(parent_var_uses, binding, VM_LVAR_USE_TYPE_BINDING);
}
rb_vm_outer_t *
RoxorVM::push_outer(Class klass)
{
rb_vm_outer_t *o = (rb_vm_outer_t *)xmalloc(sizeof(rb_vm_outer_t));
o->klass = klass;
GC_WB(&o->outer, outer_stack);
o->pushed_by_eval = false;
outer_stack = o;
GC_RETAIN(outer_stack);
#if ROXOR_VM_DEBUG_CONST
rb_vm_print_outer_stack(NULL, NULL, __FUNCTION__, __LINE__,
outer_stack, "push_outer");
#endif
return o;
}
void
RoxorVM::pop_outer(bool need_release)
{
assert(outer_stack != NULL);
rb_vm_outer_t *old = outer_stack;
outer_stack = outer_stack->outer;
if (need_release) {
GC_RELEASE(old);
}
#if ROXOR_VM_DEBUG_CONST
rb_vm_print_outer_stack(NULL, NULL, __FUNCTION__, __LINE__,
outer_stack, "pop_outer");
#endif
}
rb_vm_outer_t *
rb_vm_push_outer(Class klass)
{
return GET_VM()->push_outer(klass);
}
void
rb_vm_pop_outer(unsigned char need_release)
{
GET_VM()->pop_outer(need_release);
}
rb_vm_outer_t *
rb_vm_get_outer_stack(void)
{
return GET_VM()->get_outer_stack();
}
rb_vm_outer_t *
rb_vm_set_current_outer(rb_vm_outer_t *outer)
{
RoxorVM *vm = GET_VM();
rb_vm_outer_t *old = vm->get_current_outer();
vm->set_current_outer(outer);
return old;
}
struct rb_vm_kept_local {
ID name;
VALUE *stack_address;
VALUE *new_address;
};
extern "C"
void
rb_vm_keep_vars(rb_vm_var_uses *uses, int lvars_size, ...)
{
rb_vm_var_uses *current = uses;
if (current == NULL || current->uses_count == 0) {
// there's no use alive so nothing to do
return;
}
rb_vm_kept_local *locals = NULL;
if (lvars_size > 0) {
locals = (rb_vm_kept_local *)xmalloc(sizeof(rb_vm_kept_local)
* lvars_size);
va_list ar;
va_start(ar, lvars_size);
for (int i = 0; i < lvars_size; ++i) {
locals[i].name = va_arg(ar, ID);
locals[i].stack_address = va_arg(ar, VALUE *);
GC_WB(&locals[i].new_address, (VALUE *)xmalloc(sizeof(VALUE)));
GC_WB(locals[i].new_address, *locals[i].stack_address);
}
va_end(ar);
}
while (current != NULL) {
for (int use_index = 0; use_index < current->uses_count; ++use_index) {
void *use = current->uses[use_index];
unsigned char type = current->use_types[use_index];
rb_vm_local_t *locals_to_replace;
if (type == VM_LVAR_USE_TYPE_BLOCK) {
rb_vm_block_t *block = (rb_vm_block_t *)use;
for (int dvar_index = 0; dvar_index < block->dvars_size; ++dvar_index) {
for (int lvar_index = 0; lvar_index < lvars_size; ++lvar_index) {
if (block->dvars[dvar_index] == locals[lvar_index].stack_address) {
GC_WB(&block->dvars[dvar_index], locals[lvar_index].new_address);
break;
}
}
}
// the parent pointers can't be used anymore
block->parent_block = NULL;
block->parent_var_uses = NULL;
locals_to_replace = block->locals;
}
else { // VM_LVAR_USE_TYPE_BINDING
rb_vm_binding_t *binding = (rb_vm_binding_t *)use;
locals_to_replace = binding->locals;
}
for (rb_vm_local_t *l = locals_to_replace; l != NULL; l = l->next) {
for (int lvar_index = 0; lvar_index < lvars_size; ++lvar_index) {
if (l->value == locals[lvar_index].stack_address) {
GC_WB(&l->value, locals[lvar_index].new_address);
break;
}
}
}
// indicate to the GC that we do not have a reference here anymore
GC_WB(&current->uses[use_index], NULL);
}
current = current->next;
}
}
static inline rb_vm_local_t **
push_local(rb_vm_local_t **l, ID name, VALUE *value)
{
GC_WB(l, xmalloc(sizeof(rb_vm_local_t)));
(*l)->name = name;
(*l)->value = value;
(*l)->next = NULL;
return &(*l)->next;
}
extern "C"
rb_vm_binding_t *
rb_vm_create_binding(VALUE self, rb_vm_block_t *current_block,
rb_vm_binding_t *top_binding, rb_vm_outer_t *outer_stack,
int lvars_size, va_list lvars, bool vm_push)
{
rb_vm_binding_t *binding =
(rb_vm_binding_t *)xmalloc(sizeof(rb_vm_binding_t));
GC_WB(&binding->self, self);
GC_WB(&binding->next, top_binding);
GC_WB(&binding->outer_stack, outer_stack);
rb_vm_local_t **l = &binding->locals;
for (rb_vm_block_t *b = current_block; b != NULL; b = b->parent_block) {
for (rb_vm_local_t *li = b->locals; li != NULL; li = li->next) {
l = push_local(l, li->name, li->value);
}
}
for (int i = 0; i < lvars_size; ++i) {
ID name = va_arg(lvars, ID);
VALUE *value = va_arg(lvars, VALUE *);
l = push_local(l, name, value);
}
RoxorVM *vm = GET_VM();
GC_WB(&binding->block, vm->current_block());
if (vm_push) {
vm->push_current_binding(binding);
}
return binding;
}
extern "C"
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, ...)
{
if (dynamic_class) {
outer_stack = GET_VM()->get_outer_stack();
}
va_list lvars;
va_start(lvars, lvars_size);
rb_vm_binding_t *binding = rb_vm_create_binding(self, current_block,
top_binding, outer_stack, lvars_size, lvars, true);
va_end(lvars);
rb_vm_add_binding_lvar_use(binding, current_block, parent_var_uses);
}
extern "C"
rb_vm_binding_t *
rb_vm_current_binding(void)
{
return GET_VM()->current_binding();
}
extern "C"
void
rb_vm_add_binding(rb_vm_binding_t *binding)
{
GET_VM()->push_current_binding(binding);
}
extern "C"
void
rb_vm_pop_binding(void)
{
GET_VM()->pop_current_binding();
}
// Should be used inside a method implementation.
extern "C"
int
rb_block_given_p(void)
{
return GET_VM()->current_block() != NULL ? Qtrue : Qfalse;
}
// Should only be used by Proc.new.
extern "C"
rb_vm_block_t *
rb_vm_first_block(void)
{
return GET_VM()->first_block();
}
// Should only be used by #block_given?
extern "C"
bool
rb_vm_block_saved(void)
{
return GET_VM()->previous_block() != NULL;
}
extern "C"
rb_vm_block_t *
rb_vm_current_block(void)
{
return GET_VM()->current_block();
}
extern "C"
VALUE
rb_vm_current_block_object(void)
{
rb_vm_block_t *b = GET_VM()->current_block();
if (b != NULL) {
return rb_proc_alloc_with_block(rb_cProc, b);
}
return Qnil;
}
extern "C"
rb_vm_block_t *
rb_vm_create_block_from_method(rb_vm_method_t *method)
{
rb_vm_block_t *b = (rb_vm_block_t *)xmalloc(sizeof(rb_vm_block_t));
b->proc = Qnil;
GC_WB(&b->self, method->recv);
b->klass = 0;
b->arity = method->node == NULL
? rb_vm_arity(method->arity) : method->node->arity;
b->imp = (IMP)method;
b->flags = VM_BLOCK_PROC | VM_BLOCK_METHOD;
b->locals = NULL;
b->parent_var_uses = NULL;
b->parent_block = NULL;
b->dvars_size = 0;
return b;
}
static VALUE
rb_vm_block_call_sel(VALUE rcv, SEL sel, VALUE **dvars, rb_vm_block_t *b,
VALUE args)
{
const VALUE *argv = RARRAY_PTR(args);
const long argc = RARRAY_LEN(args);
if (argc == 0) {
rb_raise(rb_eArgError, "no receiver given");
}
SEL msel = argc - 1 == 0 ? (SEL)dvars[0] : (SEL)dvars[1];
return rb_vm_call(argv[0], msel, argc - 1, &argv[1]);
}
extern "C"
rb_vm_block_t *
rb_vm_create_block_calling_mid(ID mid)
{
rb_vm_block_t *b = (rb_vm_block_t *)xmalloc(sizeof(rb_vm_block_t)
+ (2 * sizeof(VALUE *)));
b->klass = 0;
b->proc = Qnil;
b->flags = VM_BLOCK_PROC;
b->imp = (IMP)rb_vm_block_call_sel;
// Arity is -1.
b->arity.min = 0;
b->arity.max = -1;
b->arity.left_req = 0;
b->arity.real = 1;
// Prepare 2 selectors for the dispatcher later. One for 0 arity, one for
// 1 or more arity.
const char *midstr = rb_id2name(mid);
if (midstr[strlen(midstr) - 1] == ':') {
rb_raise(rb_eArgError, "invalid method name `%s'", midstr);
}
char buf[100];
snprintf(buf, sizeof buf, "%s:", midstr);
*(b->dvars + 0) = (VALUE *)sel_registerName(midstr);
*(b->dvars + 1) = (VALUE *)sel_registerName(buf);
return b;
}
static VALUE
rb_vm_block_curry(VALUE rcv, SEL sel, VALUE **dvars, rb_vm_block_t *b,
VALUE args)
{
VALUE proc = (VALUE)dvars[0];
VALUE passed = (VALUE)dvars[1];
VALUE arity = (VALUE)dvars[2];
passed = rb_ary_plus(passed, args);
rb_ary_freeze(passed);
if (RARRAY_LEN(passed) < FIX2INT(arity)) {
return rb_vm_make_curry_proc(proc, passed, arity);
}
return rb_proc_call(proc, passed);
}
extern "C"
VALUE
rb_vm_make_curry_proc(VALUE proc, VALUE passed, VALUE arity)
{
// Proc.new { |*args| curry... }
rb_vm_block_t *b = (rb_vm_block_t *)xmalloc(sizeof(rb_vm_block_t)
+ (3 * sizeof(VALUE *)));
b->klass = 0;
b->proc = Qnil;
b->arity.min = 0;
b->arity.max = -1;
b->arity.left_req = 0;
b->arity.real = 1;
b->flags = VM_BLOCK_PROC;
b->imp = (IMP)rb_vm_block_curry;
GC_WB((b->dvars + 0), (VALUE *)proc);
GC_WB((b->dvars + 1), (VALUE *)passed);
*(b->dvars + 2) = (VALUE *)arity;
return rb_proc_alloc_with_block(rb_cProc, b);
}
static VALUE
rb_vm_iterate_block(VALUE rcv, SEL sel, VALUE **dvars, rb_vm_block_t *b,
VALUE args)
{
VALUE (*bl_proc) (ANYARGS) = (VALUE (*) (ANYARGS))dvars[0];
VALUE data2 = (VALUE)dvars[1];
return (*bl_proc)(args, data2, rcv);
}
extern "C"
VALUE
rb_iterate(VALUE (*it_proc) (VALUE), VALUE data1, VALUE (*bl_proc) (ANYARGS),
VALUE data2)
{
// Proc.new { |*args| curry... }
rb_vm_block_t *b = (rb_vm_block_t *)xmalloc(sizeof(rb_vm_block_t)
+ (2 * sizeof(VALUE *)));
b->klass = 0;
b->proc = Qnil;
b->arity.min = 0;
b->arity.max = -1;
b->arity.left_req = 0;
b->arity.real = 1;
b->flags = VM_BLOCK_PROC;
b->imp = (IMP)rb_vm_iterate_block;
b->dvars[0] =(VALUE *)bl_proc;
GC_WB(&b->dvars[1], (VALUE *)data2);
RoxorVM *vm = GET_VM();
vm->add_current_block(b);
struct Finally {
RoxorVM *vm;
Finally(RoxorVM *_vm) {
vm = _vm;
}
~Finally() {
vm->pop_current_block();
}
} finalizer(vm);
return (*it_proc)(data1);
}
#if 0
static inline IMP
class_respond_to(Class klass, SEL sel)
{
IMP imp = class_getMethodImplementation(klass, sel);
if (imp == _objc_msgForward) {
if (rb_vm_resolve_method(klass, sel)) {
imp = class_getMethodImplementation(klass, sel);
}
else {
imp = NULL;
}
}
return imp;
}
#endif
static inline void
__vm_raise(void)
{
VALUE rb_exc = GET_VM()->current_exception();
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): rb_exc = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(rb_exc)));
#endif
// DTrace probe: raise
if (MACRUBY_RAISE_ENABLED()) {
char *classname = (char *)rb_class2name(CLASS_OF(rb_exc));
char file[PATH_MAX];
unsigned long line = 0;
GET_CORE()->symbolize_backtrace_entry(2, file, sizeof file, &line,
NULL, 0);
MACRUBY_RAISE(classname, file, line);
}
#if __LP64__
// In 64-bit, an Objective-C exception is a C++ exception.
id exc = rb_rb2oc_exception(rb_exc);
objc_exception_throw(exc);
#else
void *exc = __cxa_allocate_exception(0);
__cxa_throw(exc, NULL, NULL);
#endif
}
void
RoxorVM::push_current_exception(VALUE exc)
{
assert(!NIL_P(exc));
GC_RETAIN(exc);
current_exceptions.push_back(exc);
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exc = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exc)));
#endif
//printf("PUSH %p %s\n", (void *)exc, RSTRING_PTR(rb_inspect(exc)));
}
void
RoxorVM::pop_current_exception(int pos)
{
RoxorSpecialException *sexc = get_special_exc();
if (sexc != NULL) {
return;
}
#if ROXOR_VM_DEBUG
if (!((size_t)pos < current_exceptions.size()))
{
printf("RoxorVM::%s (%s:%d) - "
"Warning: Assertion about to fail: "
"((size_t)pos < current_exceptions.size()); pos = %d; "
"current_exceptions.size() = %d\n",
__FUNCTION__, __FILE__, __LINE__, pos, (int)current_exceptions.size());
debug_exceptions();
}
#endif
assert((size_t)pos < current_exceptions.size());
std::vector<VALUE>::iterator iter = current_exceptions.end() - (pos + 1);
VALUE exc = *iter;
current_exceptions.erase(iter);
GC_RELEASE(exc);
//printf("POP (%d) %p %s\n", pos, (void *)exc, RSTRING_PTR(rb_inspect(exc)));
}
#if !__LP64__
extern "C"
void
rb_rb2oc_exc_handler(void)
{
VALUE exc = GET_VM()->current_exception();
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exc = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exc)));
#endif
if (exc != Qnil) {
id ocexc = rb_rb2oc_exception(exc);
objc_exception_throw(ocexc);
}
else {
__cxa_rethrow();
}
}
#endif
static void
prepare_exception_bt(VALUE exc)
{
// An exception's backtrace is prepared on demand, and only once.
ID bt_id = rb_intern("bt");
VALUE bt = rb_attr_get(exc, bt_id);
if (bt == Qnil) {
rb_ivar_set(exc, bt_id, rb_vm_backtrace(0));
}
}
extern "C"
void
rb_vm_raise_current_exception(void)
{
VALUE exception = GET_VM()->current_exception();
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exception = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exception)));
#endif
assert(exception != Qnil);
prepare_exception_bt(exception);
__vm_raise();
}
extern "C"
void
rb_vm_raise(VALUE exception)
{
prepare_exception_bt(exception);
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exception = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exception)));
#endif
GET_VM()->push_current_exception(exception);
__vm_raise();
}
extern "C"
VALUE
rb_rescue2(VALUE (*b_proc) (ANYARGS), VALUE data1,
VALUE (*r_proc) (ANYARGS), VALUE data2, ...)
{
try {
return (*b_proc)(data1);
}
catch (...) {
RoxorVM *vm = GET_VM();
VALUE exc = vm->current_exception();
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exc = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exc)));
#endif
if (exc != Qnil) {
va_list ar;
VALUE eclass;
bool handled = false;
va_start(ar, data2);
while ((eclass = va_arg(ar, VALUE)) != 0) {
if (rb_obj_is_kind_of(exc, eclass)) {
handled = true;
break;
}
}
va_end(ar);
if (handled) {
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): Calling pop_current_exception...\n",
__FUNCTION__, __FILE__, __LINE__);
#endif
vm->pop_current_exception();
if (r_proc != NULL) {
return (*r_proc)(data2, exc);
}
return Qnil;
}
}
throw;
}
return Qnil; // never reached
}
extern "C"
VALUE
rb_ensure(VALUE (*b_proc)(ANYARGS), VALUE data1,
VALUE (*e_proc)(ANYARGS), VALUE data2)
{
struct Finally {
VALUE (*e_proc)(ANYARGS);
VALUE data2;
Finally(VALUE (*_e_proc)(ANYARGS), VALUE _data2) {
e_proc = _e_proc;
data2 = _data2;
}
~Finally() { (*e_proc)(data2); }
} finalizer(e_proc, data2);
return (*b_proc)(data1);
}
extern "C"
void
rb_ensure_b(void (^b_block)(void), void (^e_block)(void))
{
struct Finally {
void (^e_block)(void);
Finally(void (^_e_block)(void)) {
e_block = _e_block;
}
~Finally() { e_block(); }
} finalizer(e_block);
b_block();
}
extern "C"
void
rb_vm_break(VALUE val)
{
#if 0
// XXX this doesn't work yet since break is called inside the block and
// we do not have a reference to it. This isn't very important though,
// but since 1.9 doesn't support break without Proc objects we should also
// raise a similar exception.
assert(GET_VM()->current_block != NULL);
if (GET_VM()->current_block->flags & VM_BLOCK_PROC) {
rb_raise(rb_eLocalJumpError, "break from proc-closure");
}
#endif
RoxorVM *vm = GET_VM();
if (vm->get_broken_with() != val) {
GC_RELEASE(vm->get_broken_with());
vm->set_broken_with(val);
GC_RETAIN(val);
}
}
extern "C"
VALUE
rb_vm_get_broken_value(void *vm)
{
return ((RoxorVM *)vm)->get_broken_with();
}
extern "C"
VALUE
rb_vm_pop_broken_value(void)
{
return GET_VM()->pop_broken_with();
}
extern "C"
unsigned char
rb_vm_set_has_ensure(unsigned char state)
{
RoxorVM *vm = GET_VM();
const bool old_state = vm->get_has_ensure();
vm->set_has_ensure(state);
return old_state ? 1 : 0;
}
extern "C"
void
rb_vm_return_from_block(VALUE val, int id, rb_vm_block_t *running_block)
{
RoxorVM *vm = GET_VM();
// Do not trigger a return from the calling scope if the running block
// is a lambda, to conform to the ruby 1.9 specifications.
if (running_block->flags & VM_BLOCK_LAMBDA) {
return;
}
// If we are inside an ensure block or if the running block is a Proc,
// let's implement return-from-block using a C++ exception (slow).
if (vm->get_has_ensure() || (running_block->flags & VM_BLOCK_PROC)) {
RoxorReturnFromBlockException *exc =
new RoxorReturnFromBlockException();
vm->set_special_exc(exc);
exc->val = val;
exc->id = id;
throw exc;
}
// Otherwise, let's mark the VM (fast).
vm->set_return_from_block(id);
if (vm->get_broken_with() != val) {
GC_RELEASE(vm->get_broken_with());
vm->set_broken_with(val);
GC_RETAIN(val);
}
}
extern "C"
VALUE
rb_vm_returned_from_block(void *_vm, int id)
{
RoxorVM *vm = (RoxorVM *)_vm;
if (id != -1 && vm->get_return_from_block() == id) {
vm->set_return_from_block(-1);
}
return vm->pop_broken_with();
}
extern "C"
VALUE
rb_vm_check_return_from_block_exc(RoxorReturnFromBlockException **pexc, int id)
{
RoxorVM *vm = GET_VM();
RoxorSpecialException *sexc = vm->get_special_exc();
if (sexc != NULL && sexc->type == RETURN_FROM_BLOCK_EXCEPTION) {
RoxorReturnFromBlockException *exc = *pexc;
if (id == -1 || exc->id == id) {
VALUE val = exc->val;
delete exc;
vm->set_special_exc(NULL);
return val;
}
}
return Qundef;
}
extern "C"
VALUE
rb_vm_backtrace(int skip)
{
assert(skip >= 0);
void *callstack[128];
int callstack_n = backtrace(callstack, 128);
// TODO should honor level
VALUE ary = rb_ary_new();
unsigned int interpreter_frame_idx = 0;
for (int i = 0; i < callstack_n; i++) {
char path[PATH_MAX];
char name[100];
unsigned long ln = 0;
path[0] = name[0] = '\0';
if (GET_CORE()->symbolize_call_address(callstack[i], path, sizeof path,
&ln, name, sizeof name, &interpreter_frame_idx)
&& name[0] != '\0' && path[0] != '\0') {
// Sanitize the method name to not contain trailing ':' like CRuby.
char *p = &name[strlen(name) - 1];
if (strchr(name, ':') == p) {
*p = '\0';
}
char entry[PATH_MAX];
if (ln == 0) {
snprintf(entry, sizeof entry, "%s:in `%s'",
path, name);
}
else {
snprintf(entry, sizeof entry, "%s:%ld:in `%s'",
path, ln, name);
}
rb_ary_push(ary, rb_str_new2(entry));
}
}
while (skip-- > 0) {
rb_ary_shift(ary);
}
return ary;
}
extern "C"
unsigned char
rb_vm_is_eh_active(int argc, ...)
{
assert(argc > 0);
VALUE current_exception = GET_VM()->current_exception();
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): current_exception = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(current_exception)));
#endif
if (current_exception == Qnil) {
// Not a Ruby exception...
return 0;
}
va_list ar;
unsigned char active = 0;
va_start(ar, argc);
for (int i = 0; i < argc && active == 0; ++i) {
VALUE obj = va_arg(ar, VALUE);
if (TYPE(obj) == T_ARRAY) {
for (int j = 0, count = RARRAY_LEN(obj); j < count; ++j) {
VALUE obj2 = RARRAY_AT(obj, j);
if (rb_obj_is_kind_of(current_exception, obj2)) {
active = 1;
break;
}
}
}
else {
if (rb_obj_is_kind_of(current_exception, obj)) {
active = 1;
}
}
}
va_end(ar);
return active;
}
extern "C"
void
rb_vm_pop_exception(int pos)
{
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): Calling pop_current_exception(%d)...\n",
__FUNCTION__, __FILE__, __LINE__, pos);
#endif
GET_VM()->pop_current_exception(pos);
}
extern "C"
VALUE
rb_vm_current_exception(void)
{
return GET_VM()->current_exception();
}
extern "C"
void
rb_vm_set_current_exception(VALUE exception)
{
assert(!NIL_P(exception));
#if ROXOR_VM_DEBUG
printf("%s (%s:%d): exception = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(exception)));
#endif
VALUE current = GET_VM()->current_exception();
#if ROXOR_VM_DEBUG
printf("%s: (%s:%d) current = \"%s\"\n",
__FUNCTION__, __FILE__, __LINE__, RSTRING_PTR(rb_inspect(current)));
#endif
assert(exception != current);
if (!NIL_P(current)) {
GET_VM()->pop_current_exception();
}
GET_VM()->push_current_exception(exception);
}
extern "C"
void
rb_vm_debug(void)
{
printf("rb_vm_debug\n");
}
extern "C"
void
rb_vm_print_exception(VALUE exc)
{
printf("%s", rb_str_cstr(rb_format_exception_message(exc)));
}
extern "C"
void
rb_vm_print_current_exception(void)
{
VALUE exc = GET_VM()->current_exception();
if (exc == Qnil) {
printf("uncaught Objective-C/C++ exception...\n");
std::terminate();
}
rb_vm_print_exception(exc);
}
extern "C"
bool
rb_vm_parse_in_eval(void)
{
return GET_VM()->get_parse_in_eval();
}
extern "C"
void
rb_vm_set_parse_in_eval(bool flag)
{
GET_VM()->set_parse_in_eval(flag);
}
extern "C"
int
rb_parse_in_eval(void)
{
return rb_vm_parse_in_eval() ? 1 : 0;
}
VALUE *
RoxorVM::get_binding_lvar(ID name, bool create)
{
rb_vm_binding_t *current_b = current_binding();
rb_vm_binding_t *b = current_b;
while (b != NULL) {
rb_vm_local_t **l = &b->locals;
while (*l != NULL) {
if ((*l)->name == name) {
return (*l)->value;
}
l = &(*l)->next;
}
b = b->next;
}
if (create && current_b != NULL) {
rb_vm_binding_t *b = current_b;
rb_vm_local_t **l = &b->locals;
while (*l != NULL) {
l = &(*l)->next;
}
GC_WB(l, xmalloc(sizeof(rb_vm_local_t)));
(*l)->name = name;
GC_WB(&(*l)->value, xmalloc(sizeof(VALUE *)));
(*l)->next = NULL;
return (*l)->value;
}
return NULL;
}
extern "C"
int
rb_local_define(ID id)
{
return GET_VM()->get_binding_lvar(id, true) != NULL ? 1 : 0;
}
extern "C"
int
rb_local_defined(ID id)
{
return GET_VM()->get_binding_lvar(id, false) != NULL ? 1 : 0;
}
extern "C"
int
rb_dvar_defined(ID id)
{
// TODO
return 0;
}
extern "C"
void
rb_vm_init_jit(void)