/********************************************************************** | |
compile.c - ruby node tree -> VM instruction sequence | |
$Author$ | |
created at: 04/01/01 03:42:15 JST | |
Copyright (C) 2004-2007 Koichi Sasada | |
**********************************************************************/ | |
#include "internal.h" | |
#include <math.h> | |
#define USE_INSN_STACK_INCREASE 1 | |
#include "vm_core.h" | |
#include "iseq.h" | |
#include "insns.inc" | |
#include "insns_info.inc" | |
#ifdef HAVE_DLADDR | |
# include <dlfcn.h> | |
#endif | |
#define FIXNUM_INC(n, i) ((n)+(INT2FIX(i)&~FIXNUM_FLAG)) | |
#define FIXNUM_OR(n, i) ((n)|INT2FIX(i)) | |
typedef struct iseq_link_element { | |
enum { | |
ISEQ_ELEMENT_NONE, | |
ISEQ_ELEMENT_LABEL, | |
ISEQ_ELEMENT_INSN, | |
ISEQ_ELEMENT_ADJUST | |
} type; | |
struct iseq_link_element *next; | |
struct iseq_link_element *prev; | |
} LINK_ELEMENT; | |
typedef struct iseq_link_anchor { | |
LINK_ELEMENT anchor; | |
LINK_ELEMENT *last; | |
} LINK_ANCHOR; | |
typedef struct iseq_label_data { | |
LINK_ELEMENT link; | |
int label_no; | |
int position; | |
int sc_state; | |
int set; | |
int sp; | |
} LABEL; | |
typedef struct iseq_insn_data { | |
LINK_ELEMENT link; | |
enum ruby_vminsn_type insn_id; | |
unsigned int line_no; | |
int operand_size; | |
int sc_state; | |
VALUE *operands; | |
} INSN; | |
typedef struct iseq_adjust_data { | |
LINK_ELEMENT link; | |
LABEL *label; | |
int line_no; | |
} ADJUST; | |
struct ensure_range { | |
LABEL *begin; | |
LABEL *end; | |
struct ensure_range *next; | |
}; | |
struct iseq_compile_data_ensure_node_stack { | |
NODE *ensure_node; | |
struct iseq_compile_data_ensure_node_stack *prev; | |
struct ensure_range *erange; | |
}; | |
/** | |
* debug function(macro) interface depend on CPDEBUG | |
* if it is less than 0, runtime option is in effect. | |
* | |
* debug level: | |
* 0: no debug output | |
* 1: show node type | |
* 2: show node important parameters | |
* ... | |
* 5: show other parameters | |
* 10: show every AST array | |
*/ | |
#ifndef CPDEBUG | |
#define CPDEBUG 0 | |
#endif | |
#if CPDEBUG >= 0 | |
#define compile_debug CPDEBUG | |
#else | |
#define compile_debug iseq->compile_data->option->debug_level | |
#endif | |
#if CPDEBUG | |
#define compile_debug_print_indent(level) \ | |
ruby_debug_print_indent((level), compile_debug, gl_node_level * 2) | |
#define debugp(header, value) (void) \ | |
(compile_debug_print_indent(1) && \ | |
ruby_debug_print_value(1, compile_debug, (header), (value))) | |
#define debugi(header, id) (void) \ | |
(compile_debug_print_indent(1) && \ | |
ruby_debug_print_id(1, compile_debug, (header), (id))) | |
#define debugp_param(header, value) (void) \ | |
(compile_debug_print_indent(1) && \ | |
ruby_debug_print_value(1, compile_debug, (header), (value))) | |
#define debugp_verbose(header, value) (void) \ | |
(compile_debug_print_indent(2) && \ | |
ruby_debug_print_value(2, compile_debug, (header), (value))) | |
#define debugp_verbose_node(header, value) (void) \ | |
(compile_debug_print_indent(10) && \ | |
ruby_debug_print_value(10, compile_debug, (header), (value))) | |
#define debug_node_start(node) ((void) \ | |
(compile_debug_print_indent(1) && \ | |
(ruby_debug_print_node(1, CPDEBUG, "", (NODE *)(node)), gl_node_level)), \ | |
gl_node_level++) | |
#define debug_node_end() gl_node_level -- | |
#else | |
static inline ID | |
r_id(ID id) | |
{ | |
return id; | |
} | |
static inline VALUE | |
r_value(VALUE value) | |
{ | |
return value; | |
} | |
#define debugi(header, id) r_id(id) | |
#define debugp(header, value) r_value(value) | |
#define debugp_verbose(header, value) r_value(value) | |
#define debugp_verbose_node(header, value) r_value(value) | |
#define debugp_param(header, value) r_value(value) | |
#define debug_node_start(node) ((void)0) | |
#define debug_node_end() ((void)0) | |
#endif | |
#if CPDEBUG > 1 || CPDEBUG < 0 | |
#define debugs if (compile_debug_print_indent(1)) ruby_debug_printf | |
#define debug_compile(msg, v) ((void)(compile_debug_print_indent(1) && fputs((msg), stderr)), (v)) | |
#else | |
#define debugs if(0)printf | |
#define debug_compile(msg, v) (v) | |
#endif | |
/* create new label */ | |
#define NEW_LABEL(l) new_label_body(iseq, (l)) | |
#define iseq_path(iseq) \ | |
(((rb_iseq_t*)DATA_PTR(iseq))->location.path) | |
#define iseq_absolute_path(iseq) \ | |
(((rb_iseq_t*)DATA_PTR(iseq))->location.absolute_path) | |
#define NEW_ISEQVAL(node, name, type, line_no) \ | |
new_child_iseq(iseq, (node), rb_fstring(name), 0, (type), (line_no)) | |
#define NEW_CHILD_ISEQVAL(node, name, type, line_no) \ | |
new_child_iseq(iseq, (node), rb_fstring(name), iseq->self, (type), (line_no)) | |
/* add instructions */ | |
#define ADD_SEQ(seq1, seq2) \ | |
APPEND_LIST((seq1), (seq2)) | |
/* add an instruction */ | |
#define ADD_INSN(seq, line, insn) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (line), BIN(insn), 0)) | |
/* add an instruction with some operands (1, 2, 3, 5) */ | |
#define ADD_INSN1(seq, line, insn, op1) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) \ | |
new_insn_body(iseq, (line), BIN(insn), 1, (VALUE)(op1))) | |
/* add an instruction with label operand (alias of ADD_INSN1) */ | |
#define ADD_INSNL(seq, line, insn, label) ADD_INSN1(seq, line, insn, label) | |
#define ADD_INSN2(seq, line, insn, op1, op2) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) \ | |
new_insn_body(iseq, (line), BIN(insn), 2, (VALUE)(op1), (VALUE)(op2))) | |
#define ADD_INSN3(seq, line, insn, op1, op2, op3) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) \ | |
new_insn_body(iseq, (line), BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3))) | |
/* Specific Insn factory */ | |
#define ADD_SEND(seq, line, id, argc) \ | |
ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(0), NULL) | |
#define ADD_SEND_WITH_FLAG(seq, line, id, argc, flag) \ | |
ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)(flag), NULL) | |
#define ADD_SEND_WITH_BLOCK(seq, line, id, argc, block) \ | |
ADD_SEND_R((seq), (line), (id), (argc), (VALUE)(block), (VALUE)INT2FIX(0), NULL) | |
#define ADD_CALL_RECEIVER(seq, line) \ | |
ADD_INSN((seq), (line), putself) | |
#define ADD_CALL(seq, line, id, argc) \ | |
ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(VM_CALL_FCALL), NULL) | |
#define ADD_CALL_WITH_BLOCK(seq, line, id, argc, block) \ | |
ADD_SEND_R((seq), (line), (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL), NULL) | |
#define ADD_SEND_R(seq, line, id, argc, block, flag, keywords) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, (line), (id), (VALUE)(argc), (VALUE)(block), (VALUE)(flag), (keywords))) | |
#define ADD_TRACE(seq, line, event) \ | |
do { \ | |
if ((event) == RUBY_EVENT_LINE && iseq->coverage && \ | |
(line) != iseq->compile_data->last_coverable_line) { \ | |
RARRAY_ASET(iseq->coverage, (line) - 1, INT2FIX(0)); \ | |
iseq->compile_data->last_coverable_line = (line); \ | |
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \ | |
} \ | |
if (iseq->compile_data->option->trace_instruction) { \ | |
ADD_INSN1((seq), (line), trace, INT2FIX(event)); \ | |
} \ | |
} while (0) | |
/* add label */ | |
#define ADD_LABEL(seq, label) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) (label)) | |
#define APPEND_LABEL(seq, before, label) \ | |
APPEND_ELEM((seq), (before), (LINK_ELEMENT *) (label)) | |
#define ADD_ADJUST(seq, line, label) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) new_adjust_body(iseq, (label), (line))) | |
#define ADD_ADJUST_RESTORE(seq, label) \ | |
ADD_ELEM((seq), (LINK_ELEMENT *) new_adjust_body(iseq, (label), -1)) | |
#define ADD_CATCH_ENTRY(type, ls, le, iseqv, lc) \ | |
(rb_ary_push(iseq->compile_data->catch_table_ary, \ | |
rb_ary_new3(5, (type), \ | |
(VALUE)(ls) | 1, (VALUE)(le) | 1, \ | |
(VALUE)(iseqv), (VALUE)(lc) | 1))) | |
/* compile node */ | |
#define COMPILE(anchor, desc, node) \ | |
(debug_compile("== " desc "\n", \ | |
iseq_compile_each(iseq, (anchor), (node), 0))) | |
/* compile node, this node's value will be popped */ | |
#define COMPILE_POPED(anchor, desc, node) \ | |
(debug_compile("== " desc "\n", \ | |
iseq_compile_each(iseq, (anchor), (node), 1))) | |
/* compile node, which is popped when 'poped' is true */ | |
#define COMPILE_(anchor, desc, node, poped) \ | |
(debug_compile("== " desc "\n", \ | |
iseq_compile_each(iseq, (anchor), (node), (poped)))) | |
#define COMPILE_RECV(anchor, desc, node) \ | |
(private_recv_p(node) ? \ | |
(ADD_INSN(anchor, nd_line(node), putself), VM_CALL_FCALL) : \ | |
(COMPILE(anchor, desc, node->nd_recv), 0)) | |
#define OPERAND_AT(insn, idx) \ | |
(((INSN*)(insn))->operands[(idx)]) | |
#define INSN_OF(insn) \ | |
(((INSN*)(insn))->insn_id) | |
/* error */ | |
#define COMPILE_ERROR(strs) \ | |
{ \ | |
VALUE tmp = GET_THREAD()->errinfo; \ | |
if (compile_debug) rb_compile_bug strs; \ | |
GET_THREAD()->errinfo = iseq->compile_data->err_info; \ | |
rb_compile_error strs; \ | |
RB_OBJ_WRITE(iseq->self, &iseq->compile_data->err_info, GET_THREAD()->errinfo); \ | |
GET_THREAD()->errinfo = tmp; \ | |
ret = 0; \ | |
break; \ | |
} | |
#define ERROR_ARGS ruby_sourcefile, nd_line(node), | |
#define COMPILE_OK 1 | |
#define COMPILE_NG 0 | |
/* leave name uninitialized so that compiler warn if INIT_ANCHOR is | |
* missing */ | |
#define DECL_ANCHOR(name) \ | |
LINK_ANCHOR *name, name##_body__ = {{0,},} | |
#define INIT_ANCHOR(name) \ | |
(name##_body__.last = &name##_body__.anchor, name = &name##_body__) | |
#define hide_obj(obj) do {OBJ_FREEZE(obj); RBASIC_CLEAR_CLASS(obj);} while (0) | |
#include "optinsn.inc" | |
#if OPT_INSTRUCTIONS_UNIFICATION | |
#include "optunifs.inc" | |
#endif | |
/* for debug */ | |
#if CPDEBUG < 0 | |
#define ISEQ_ARG iseq, | |
#define ISEQ_ARG_DECLARE rb_iseq_t *iseq, | |
#else | |
#define ISEQ_ARG | |
#define ISEQ_ARG_DECLARE | |
#endif | |
#if CPDEBUG | |
#define gl_node_level iseq->compile_data->node_level | |
#endif | |
static void dump_disasm_list(LINK_ELEMENT *elem); | |
static int insn_data_length(INSN *iobj); | |
static int calc_sp_depth(int depth, INSN *iobj); | |
static INSN *new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int argc, ...); | |
static LABEL *new_label_body(rb_iseq_t *iseq, long line); | |
static ADJUST *new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line); | |
static int iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * n, int); | |
static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor); | |
static int iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor); | |
static int iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor); | |
static int iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl); | |
static int iseq_set_exception_local_table(rb_iseq_t *iseq); | |
static int iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * node); | |
static int iseq_set_sequence_stackcaching(rb_iseq_t *iseq, LINK_ANCHOR *anchor); | |
static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *anchor); | |
static int iseq_set_exception_table(rb_iseq_t *iseq); | |
static int iseq_set_optargs_table(rb_iseq_t *iseq); | |
/* | |
* To make Array to LinkedList, use link_anchor | |
*/ | |
static void | |
verify_list(ISEQ_ARG_DECLARE const char *info, LINK_ANCHOR *anchor) | |
{ | |
#if CPDEBUG | |
int flag = 0; | |
LINK_ELEMENT *list, *plist; | |
if (!compile_debug) return; | |
list = anchor->anchor.next; | |
plist = &anchor->anchor; | |
while (list) { | |
if (plist != list->prev) { | |
flag += 1; | |
} | |
plist = list; | |
list = list->next; | |
} | |
if (anchor->last != plist && anchor->last != 0) { | |
flag |= 0x70000; | |
} | |
if (flag != 0) { | |
rb_bug("list verify error: %08x (%s)", flag, info); | |
} | |
#endif | |
} | |
#if CPDEBUG < 0 | |
#define verify_list(info, anchor) verify_list(iseq, (info), (anchor)) | |
#endif | |
/* | |
* elem1, elem2 => elem1, elem2, elem | |
*/ | |
static void | |
ADD_ELEM(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor, LINK_ELEMENT *elem) | |
{ | |
elem->prev = anchor->last; | |
anchor->last->next = elem; | |
anchor->last = elem; | |
verify_list("add", anchor); | |
} | |
/* | |
* elem1, before, elem2 => elem1, before, elem, elem2 | |
*/ | |
static void | |
APPEND_ELEM(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor, LINK_ELEMENT *before, LINK_ELEMENT *elem) | |
{ | |
elem->prev = before; | |
elem->next = before->next; | |
elem->next->prev = elem; | |
before->next = elem; | |
if (before == anchor->last) anchor->last = elem; | |
verify_list("add", anchor); | |
} | |
#if CPDEBUG < 0 | |
#define ADD_ELEM(anchor, elem) ADD_ELEM(iseq, (anchor), (elem)) | |
#define APPEND_ELEM(anchor, before, elem) ADD_ELEM(iseq, (anchor), (before), (elem)) | |
#endif | |
static int | |
iseq_add_mark_object(rb_iseq_t *iseq, VALUE v) | |
{ | |
if (!SPECIAL_CONST_P(v)) { | |
rb_iseq_add_mark_object(iseq, v); | |
} | |
return COMPILE_OK; | |
} | |
#define ruby_sourcefile RSTRING_PTR(iseq->location.path) | |
static int | |
iseq_add_mark_object_compile_time(rb_iseq_t *iseq, VALUE v) | |
{ | |
if (!SPECIAL_CONST_P(v)) { | |
rb_ary_push(iseq->compile_data->mark_ary, v); | |
} | |
return COMPILE_OK; | |
} | |
static int | |
validate_label(st_data_t name, st_data_t label, st_data_t arg) | |
{ | |
rb_iseq_t *iseq = (rb_iseq_t *)arg; | |
LABEL *lobj = (LABEL *)label; | |
if (!lobj->link.next) { | |
do { | |
int ret; | |
COMPILE_ERROR((ruby_sourcefile, lobj->position, | |
"%"PRIsVALUE": undefined label", | |
rb_id2str((ID)name))); | |
if (ret) break; | |
} while (0); | |
} | |
return ST_CONTINUE; | |
} | |
static void | |
validate_labels(rb_iseq_t *iseq, st_table *labels_table) | |
{ | |
st_foreach(labels_table, validate_label, (st_data_t)iseq); | |
if (!NIL_P(iseq->compile_data->err_info)) { | |
rb_exc_raise(iseq->compile_data->err_info); | |
} | |
} | |
VALUE | |
rb_iseq_compile_node(VALUE self, NODE *node) | |
{ | |
DECL_ANCHOR(ret); | |
rb_iseq_t *iseq; | |
INIT_ANCHOR(ret); | |
GetISeqPtr(self, iseq); | |
if (node == 0) { | |
COMPILE(ret, "nil", node); | |
iseq_set_local_table(iseq, 0); | |
} | |
else if (nd_type(node) == NODE_SCOPE) { | |
/* iseq type of top, method, class, block */ | |
iseq_set_local_table(iseq, node->nd_tbl); | |
iseq_set_arguments(iseq, ret, node->nd_args); | |
switch (iseq->type) { | |
case ISEQ_TYPE_BLOCK: | |
{ | |
LABEL *start = iseq->compile_data->start_label = NEW_LABEL(0); | |
LABEL *end = iseq->compile_data->end_label = NEW_LABEL(0); | |
ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_B_CALL); | |
ADD_LABEL(ret, start); | |
COMPILE(ret, "block body", node->nd_body); | |
ADD_LABEL(ret, end); | |
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_B_RETURN); | |
/* wide range catch handler must put at last */ | |
ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, 0, start); | |
ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, 0, end); | |
break; | |
} | |
case ISEQ_TYPE_CLASS: | |
{ | |
ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CLASS); | |
COMPILE(ret, "scoped node", node->nd_body); | |
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_END); | |
break; | |
} | |
case ISEQ_TYPE_METHOD: | |
{ | |
ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL); | |
COMPILE(ret, "scoped node", node->nd_body); | |
ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN); | |
break; | |
} | |
default: { | |
COMPILE(ret, "scoped node", node->nd_body); | |
break; | |
} | |
} | |
} | |
else if (nd_type(node) == NODE_IFUNC) { | |
/* user callback */ | |
(*node->nd_cfnc)(iseq, ret, node->nd_tval); | |
} | |
else { | |
switch (iseq->type) { | |
case ISEQ_TYPE_METHOD: | |
case ISEQ_TYPE_CLASS: | |
case ISEQ_TYPE_BLOCK: | |
case ISEQ_TYPE_EVAL: | |
case ISEQ_TYPE_MAIN: | |
case ISEQ_TYPE_TOP: | |
rb_compile_error(ERROR_ARGS "compile/should not be reached: %s:%d", | |
__FILE__, __LINE__); | |
break; | |
case ISEQ_TYPE_RESCUE: | |
iseq_set_exception_local_table(iseq); | |
COMPILE(ret, "rescue", node); | |
break; | |
case ISEQ_TYPE_ENSURE: | |
iseq_set_exception_local_table(iseq); | |
COMPILE_POPED(ret, "ensure", node); | |
break; | |
case ISEQ_TYPE_DEFINED_GUARD: | |
iseq_set_local_table(iseq, 0); | |
COMPILE(ret, "defined guard", node); | |
break; | |
default: | |
rb_bug("unknown scope"); | |
} | |
} | |
if (iseq->type == ISEQ_TYPE_RESCUE || iseq->type == ISEQ_TYPE_ENSURE) { | |
ADD_INSN2(ret, 0, getlocal, INT2FIX(2), INT2FIX(0)); | |
ADD_INSN1(ret, 0, throw, INT2FIX(0) /* continue throw */ ); | |
} | |
else { | |
ADD_INSN(ret, iseq->compile_data->last_line, leave); | |
} | |
#if SUPPORT_JOKE | |
if (iseq->compile_data->labels_table) { | |
validate_labels(iseq, iseq->compile_data->labels_table); | |
} | |
#endif | |
return iseq_setup(iseq, ret); | |
} | |
int | |
rb_iseq_translate_threaded_code(rb_iseq_t *iseq) | |
{ | |
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE | |
const void * const *table = rb_vm_get_insns_address_table(); | |
unsigned int i; | |
for (i = 0; i < iseq->iseq_size; /* */ ) { | |
int insn = (int)iseq->iseq_encoded[i]; | |
int len = insn_len(insn); | |
iseq->iseq_encoded[i] = (VALUE)table[insn]; | |
i += len; | |
} | |
#endif | |
return COMPILE_OK; | |
} | |
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE | |
static int | |
rb_vm_insn_addr2insn(const void *addr) /* cold path */ | |
{ | |
int insn; | |
const void * const *table = rb_vm_get_insns_address_table(); | |
for (insn = 0; insn < VM_INSTRUCTION_SIZE; insn++) { | |
if (table[insn] == addr) { | |
return insn; | |
} | |
} | |
rb_bug("rb_vm_insn_addr2insn: invalid insn address: %p", addr); | |
} | |
#endif | |
VALUE * | |
rb_iseq_original_iseq(rb_iseq_t *iseq) /* cold path */ | |
{ | |
if (iseq->iseq) return iseq->iseq; | |
iseq->iseq = ALLOC_N(VALUE, iseq->iseq_size); | |
MEMCPY(iseq->iseq, iseq->iseq_encoded, VALUE, iseq->iseq_size); | |
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE | |
{ | |
unsigned int i; | |
for (i = 0; i < iseq->iseq_size; /* */ ) { | |
const void *addr = (const void *)iseq->iseq[i]; | |
const int insn = rb_vm_insn_addr2insn(addr); | |
iseq->iseq[i] = insn; | |
i += insn_len(insn); | |
} | |
} | |
#endif | |
return iseq->iseq; | |
} | |
/*********************************************/ | |
/* definition of data structure for compiler */ | |
/*********************************************/ | |
/* | |
* On 32-bit SPARC, GCC by default generates SPARC V7 code that may require | |
* 8-byte word alignment. On the other hand, Oracle Solaris Studio seems to | |
* generate SPARCV8PLUS code with unaligned memory accesss instructions. | |
* That is why the STRICT_ALIGNMENT is defined only with GCC. | |
*/ | |
#if defined(__sparc) && SIZEOF_VOIDP == 4 && defined(__GNUC__) | |
#define STRICT_ALIGNMENT | |
#endif | |
#ifdef STRICT_ALIGNMENT | |
#if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE | |
#define ALIGNMENT_SIZE SIZEOF_LONG_LONG | |
#else | |
#define ALIGNMENT_SIZE SIZEOF_VALUE | |
#endif | |
#define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) | |
#define ALIGNMENT_SIZE_MASK PADDING_SIZE_MAX | |
/* Note: ALIGNMENT_SIZE == (2 ** N) is expected. */ | |
#else | |
#define PADDING_SIZE_MAX 0 | |
#endif /* STRICT_ALIGNMENT */ | |
#ifdef STRICT_ALIGNMENT | |
/* calculate padding size for aligned memory access */ | |
static size_t | |
calc_padding(void *ptr, size_t size) | |
{ | |
size_t mis; | |
size_t padding = 0; | |
mis = (size_t)ptr & ALIGNMENT_SIZE_MASK; | |
if (mis > 0) { | |
padding = ALIGNMENT_SIZE - mis; | |
} | |
/* | |
* On 32-bit sparc or equivalents, when a single VALUE is requested | |
* and padding == sizeof(VALUE), it is clear that no padding is needed. | |
*/ | |
#if ALIGNMENT_SIZE > SIZEOF_VALUE | |
if (size == sizeof(VALUE) && padding == sizeof(VALUE)) { | |
padding = 0; | |
} | |
#endif | |
return padding; | |
} | |
#endif /* STRICT_ALIGNMENT */ | |
static void * | |
compile_data_alloc(rb_iseq_t *iseq, size_t size) | |
{ | |
void *ptr = 0; | |
struct iseq_compile_data_storage *storage = | |
iseq->compile_data->storage_current; | |
#ifdef STRICT_ALIGNMENT | |
size_t padding = calc_padding((void *)&storage->buff[storage->pos], size); | |
#else | |
const size_t padding = 0; /* expected to be optimized by compiler */ | |
#endif /* STRICT_ALIGNMENT */ | |
if (size >= INT_MAX - padding) rb_memerror(); | |
if (storage->pos + size + padding > storage->size) { | |
unsigned int alloc_size = storage->size; | |
while (alloc_size < size + PADDING_SIZE_MAX) { | |
if (alloc_size >= INT_MAX / 2) rb_memerror(); | |
alloc_size *= 2; | |
} | |
storage->next = (void *)ALLOC_N(char, alloc_size + | |
SIZEOF_ISEQ_COMPILE_DATA_STORAGE); | |
storage = iseq->compile_data->storage_current = storage->next; | |
storage->next = 0; | |
storage->pos = 0; | |
storage->size = alloc_size; | |
#ifdef STRICT_ALIGNMENT | |
padding = calc_padding((void *)&storage->buff[storage->pos], size); | |
#endif /* STRICT_ALIGNMENT */ | |
} | |
#ifdef STRICT_ALIGNMENT | |
storage->pos += (int)padding; | |
#endif /* STRICT_ALIGNMENT */ | |
ptr = (void *)&storage->buff[storage->pos]; | |
storage->pos += (int)size; | |
return ptr; | |
} | |
static INSN * | |
compile_data_alloc_insn(rb_iseq_t *iseq) | |
{ | |
return (INSN *)compile_data_alloc(iseq, sizeof(INSN)); | |
} | |
static LABEL * | |
compile_data_alloc_label(rb_iseq_t *iseq) | |
{ | |
return (LABEL *)compile_data_alloc(iseq, sizeof(LABEL)); | |
} | |
static ADJUST * | |
compile_data_alloc_adjust(rb_iseq_t *iseq) | |
{ | |
return (ADJUST *)compile_data_alloc(iseq, sizeof(ADJUST)); | |
} | |
/* | |
* elem1, elemX => elem1, elem2, elemX | |
*/ | |
static void | |
INSERT_ELEM_NEXT(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2) | |
{ | |
elem2->next = elem1->next; | |
elem2->prev = elem1; | |
elem1->next = elem2; | |
if (elem2->next) { | |
elem2->next->prev = elem2; | |
} | |
} | |
/* | |
* elemX, elem1, elemY => elemX, elem2, elemY | |
*/ | |
static void | |
REPLACE_ELEM(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2) | |
{ | |
elem2->prev = elem1->prev; | |
elem2->next = elem1->next; | |
if (elem1->prev) { | |
elem1->prev->next = elem2; | |
} | |
if (elem1->next) { | |
elem1->next->prev = elem2; | |
} | |
} | |
static void | |
REMOVE_ELEM(LINK_ELEMENT *elem) | |
{ | |
elem->prev->next = elem->next; | |
if (elem->next) { | |
elem->next->prev = elem->prev; | |
} | |
} | |
static LINK_ELEMENT * | |
FIRST_ELEMENT(LINK_ANCHOR *anchor) | |
{ | |
return anchor->anchor.next; | |
} | |
static LINK_ELEMENT * | |
POP_ELEMENT(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor) | |
{ | |
LINK_ELEMENT *elem = anchor->last; | |
anchor->last = anchor->last->prev; | |
anchor->last->next = 0; | |
verify_list("pop", anchor); | |
return elem; | |
} | |
#if CPDEBUG < 0 | |
#define POP_ELEMENT(anchor) POP_ELEMENT(iseq, (anchor)) | |
#endif | |
static int | |
LIST_SIZE_ZERO(LINK_ANCHOR *anchor) | |
{ | |
if (anchor->anchor.next == 0) { | |
return 1; | |
} | |
else { | |
return 0; | |
} | |
} | |
/* | |
* anc1: e1, e2, e3 | |
* anc2: e4, e5 | |
*#=> | |
* anc1: e1, e2, e3, e4, e5 | |
* anc2: e4, e5 (broken) | |
*/ | |
static void | |
APPEND_LIST(ISEQ_ARG_DECLARE LINK_ANCHOR *anc1, LINK_ANCHOR *anc2) | |
{ | |
if (anc2->anchor.next) { | |
anc1->last->next = anc2->anchor.next; | |
anc2->anchor.next->prev = anc1->last; | |
anc1->last = anc2->last; | |
} | |
verify_list("append", anc1); | |
} | |
#if CPDEBUG < 0 | |
#define APPEND_LIST(anc1, anc2) APPEND_LIST(iseq, (anc1), (anc2)) | |
#endif | |
/* | |
* anc1: e1, e2, e3 | |
* anc2: e4, e5 | |
*#=> | |
* anc1: e4, e5, e1, e2, e3 | |
* anc2: e4, e5 (broken) | |
*/ | |
static void | |
INSERT_LIST(ISEQ_ARG_DECLARE LINK_ANCHOR *anc1, LINK_ANCHOR *anc2) | |
{ | |
if (anc2->anchor.next) { | |
LINK_ELEMENT *first = anc1->anchor.next; | |
anc1->anchor.next = anc2->anchor.next; | |
anc1->anchor.next->prev = &anc1->anchor; | |
anc2->last->next = first; | |
if (first) { | |
first->prev = anc2->last; | |
} | |
else { | |
anc1->last = anc2->last; | |
} | |
} | |
verify_list("append", anc1); | |
} | |
#if CPDEBUG < 0 | |
#define INSERT_LIST(anc1, anc2) INSERT_LIST(iseq, (anc1), (anc2)) | |
#endif | |
#if CPDEBUG && 0 | |
static void | |
debug_list(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor) | |
{ | |
LINK_ELEMENT *list = FIRST_ELEMENT(anchor); | |
printf("----\n"); | |
printf("anch: %p, frst: %p, last: %p\n", &anchor->anchor, | |
anchor->anchor.next, anchor->last); | |
while (list) { | |
printf("curr: %p, next: %p, prev: %p, type: %d\n", list, list->next, | |
list->prev, FIX2INT(list->type)); | |
list = list->next; | |
} | |
printf("----\n"); | |
dump_disasm_list(anchor->anchor.next); | |
verify_list("debug list", anchor); | |
} | |
#if CPDEBUG < 0 | |
#define debug_list(anc) debug_list(iseq, (anc)) | |
#endif | |
#endif | |
static LABEL * | |
new_label_body(rb_iseq_t *iseq, long line) | |
{ | |
LABEL *labelobj = compile_data_alloc_label(iseq); | |
labelobj->link.type = ISEQ_ELEMENT_LABEL; | |
labelobj->link.next = 0; | |
labelobj->label_no = iseq->compile_data->label_no++; | |
labelobj->sc_state = 0; | |
labelobj->sp = -1; | |
return labelobj; | |
} | |
static ADJUST * | |
new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line) | |
{ | |
ADJUST *adjust = compile_data_alloc_adjust(iseq); | |
adjust->link.type = ISEQ_ELEMENT_ADJUST; | |
adjust->link.next = 0; | |
adjust->label = label; | |
adjust->line_no = line; | |
return adjust; | |
} | |
static INSN * | |
new_insn_core(rb_iseq_t *iseq, int line_no, | |
int insn_id, int argc, VALUE *argv) | |
{ | |
INSN *iobj = compile_data_alloc_insn(iseq); | |
/* printf("insn_id: %d, line: %d\n", insn_id, line_no); */ | |
iobj->link.type = ISEQ_ELEMENT_INSN; | |
iobj->link.next = 0; | |
iobj->insn_id = insn_id; | |
iobj->line_no = line_no; | |
iobj->operands = argv; | |
iobj->operand_size = argc; | |
iobj->sc_state = 0; | |
return iobj; | |
} | |
static INSN * | |
new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int argc, ...) | |
{ | |
VALUE *operands = 0; | |
va_list argv; | |
if (argc > 0) { | |
int i; | |
va_init_list(argv, argc); | |
operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * argc); | |
for (i = 0; i < argc; i++) { | |
VALUE v = va_arg(argv, VALUE); | |
operands[i] = v; | |
} | |
va_end(argv); | |
} | |
return new_insn_core(iseq, line_no, insn_id, argc, operands); | |
} | |
static rb_call_info_t * | |
new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag, rb_call_info_kw_arg_t *kw_arg) | |
{ | |
rb_call_info_t *ci = (rb_call_info_t *)compile_data_alloc(iseq, sizeof(rb_call_info_t)); | |
ci->mid = mid; | |
ci->flag = flag; | |
ci->orig_argc = argc; | |
ci->argc = argc; | |
ci->kw_arg = kw_arg; | |
if (kw_arg) { | |
ci->argc += kw_arg->keyword_len; | |
ci->orig_argc += kw_arg->keyword_len; | |
} | |
if (block) { | |
GetISeqPtr(block, ci->blockiseq); | |
} | |
else { | |
ci->blockiseq = 0; | |
} | |
if (!(ci->flag & (VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG)) && | |
ci->blockiseq == NULL && ci->kw_arg == NULL) { | |
ci->flag |= VM_CALL_ARGS_SIMPLE; | |
} | |
ci->method_state = 0; | |
ci->class_serial = 0; | |
ci->blockptr = 0; | |
ci->recv = Qundef; | |
ci->call = 0; /* TODO: should set default function? */ | |
ci->aux.index = iseq->callinfo_size++; | |
return ci; | |
} | |
static INSN * | |
new_insn_send(rb_iseq_t *iseq, int line_no, ID id, VALUE argc, VALUE block, VALUE flag, rb_call_info_kw_arg_t *keywords) | |
{ | |
VALUE *operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * 1); | |
operands[0] = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), block, FIX2INT(flag), keywords); | |
return new_insn_core(iseq, line_no, BIN(send), 1, operands); | |
} | |
static VALUE | |
new_child_iseq(rb_iseq_t *iseq, NODE *node, | |
VALUE name, VALUE parent, enum iseq_type type, int line_no) | |
{ | |
VALUE ret; | |
debugs("[new_child_iseq]> ---------------------------------------\n"); | |
ret = rb_iseq_new_with_opt(node, name, | |
iseq_path(iseq->self), iseq_absolute_path(iseq->self), | |
INT2FIX(line_no), parent, type, iseq->compile_data->option); | |
debugs("[new_child_iseq]< ---------------------------------------\n"); | |
iseq_add_mark_object(iseq, ret); | |
return ret; | |
} | |
static int | |
iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor) | |
{ | |
/* debugs("[compile step 2] (iseq_array_to_linkedlist)\n"); */ | |
if (compile_debug > 5) | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
debugs("[compile step 3.1 (iseq_optimize)]\n"); | |
iseq_optimize(iseq, anchor); | |
if (compile_debug > 5) | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
if (iseq->compile_data->option->instructions_unification) { | |
debugs("[compile step 3.2 (iseq_insns_unification)]\n"); | |
iseq_insns_unification(iseq, anchor); | |
if (compile_debug > 5) | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
} | |
if (iseq->compile_data->option->stack_caching) { | |
debugs("[compile step 3.3 (iseq_set_sequence_stackcaching)]\n"); | |
iseq_set_sequence_stackcaching(iseq, anchor); | |
if (compile_debug > 5) | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
} | |
debugs("[compile step 4.1 (iseq_set_sequence)]\n"); | |
iseq_set_sequence(iseq, anchor); | |
if (compile_debug > 5) | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
debugs("[compile step 4.2 (iseq_set_exception_table)]\n"); | |
iseq_set_exception_table(iseq); | |
debugs("[compile step 4.3 (set_optargs_table)] \n"); | |
iseq_set_optargs_table(iseq); | |
debugs("[compile step 5 (iseq_translate_threaded_code)] \n"); | |
rb_iseq_translate_threaded_code(iseq); | |
if (compile_debug > 1) { | |
VALUE str = rb_iseq_disasm(iseq->self); | |
printf("%s\n", StringValueCStr(str)); | |
fflush(stdout); | |
} | |
debugs("[compile step: finish]\n"); | |
return 0; | |
} | |
static int | |
iseq_set_exception_local_table(rb_iseq_t *iseq) | |
{ | |
ID id_dollar_bang; | |
CONST_ID(id_dollar_bang, "#$!"); | |
iseq->local_table = (ID *)ALLOC_N(ID, 1); | |
iseq->local_table_size = 1; | |
iseq->local_size = iseq->local_table_size + 1; | |
iseq->local_table[0] = id_dollar_bang; | |
return COMPILE_OK; | |
} | |
static int | |
get_lvar_level(rb_iseq_t *iseq) | |
{ | |
int lev = 0; | |
while (iseq != iseq->local_iseq) { | |
lev++; | |
iseq = iseq->parent_iseq; | |
} | |
return lev; | |
} | |
static int | |
get_dyna_var_idx_at_raw(rb_iseq_t *iseq, ID id) | |
{ | |
int i; | |
for (i = 0; i < iseq->local_table_size; i++) { | |
if (iseq->local_table[i] == id) { | |
return i; | |
} | |
} | |
return -1; | |
} | |
static int | |
get_local_var_idx(rb_iseq_t *iseq, ID id) | |
{ | |
int idx = get_dyna_var_idx_at_raw(iseq->local_iseq, id); | |
if (idx < 0) { | |
rb_bug("get_local_var_idx: %d", idx); | |
} | |
return idx; | |
} | |
static int | |
get_dyna_var_idx(rb_iseq_t *iseq, ID id, int *level, int *ls) | |
{ | |
int lv = 0, idx = -1; | |
while (iseq) { | |
idx = get_dyna_var_idx_at_raw(iseq, id); | |
if (idx >= 0) { | |
break; | |
} | |
iseq = iseq->parent_iseq; | |
lv++; | |
} | |
if (idx < 0) { | |
rb_bug("get_dyna_var_idx: -1"); | |
} | |
*level = lv; | |
*ls = iseq->local_size; | |
return idx; | |
} | |
static void | |
iseq_calc_param_size(rb_iseq_t *iseq) | |
{ | |
if (iseq->param.flags.has_opt || | |
iseq->param.flags.has_post || | |
iseq->param.flags.has_rest || | |
iseq->param.flags.has_block || | |
iseq->param.flags.has_kw || | |
iseq->param.flags.has_kwrest) { | |
if (iseq->param.flags.has_block) { | |
iseq->param.size = iseq->param.block_start + 1; | |
} | |
else if (iseq->param.flags.has_kwrest) { | |
iseq->param.size = iseq->param.keyword->rest_start + 1; | |
} | |
else if (iseq->param.flags.has_kw) { | |
iseq->param.size = iseq->param.keyword->bits_start + 1; | |
} | |
else if (iseq->param.flags.has_post) { | |
iseq->param.size = iseq->param.post_start + iseq->param.post_num; | |
} | |
else if (iseq->param.flags.has_rest) { | |
iseq->param.size = iseq->param.rest_start + 1; | |
} | |
else if (iseq->param.flags.has_opt) { | |
iseq->param.size = iseq->param.lead_num + iseq->param.opt_num; | |
} | |
else { | |
rb_bug("unreachable"); | |
} | |
} | |
else { | |
iseq->param.size = iseq->param.lead_num; | |
} | |
} | |
static int | |
iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args) | |
{ | |
debugs("iseq_set_arguments: %s\n", node_args ? "" : "0"); | |
if (node_args) { | |
struct rb_args_info *args = node_args->nd_ainfo; | |
ID rest_id = 0; | |
int last_comma = 0; | |
ID block_id = 0; | |
if (nd_type(node_args) != NODE_ARGS) { | |
rb_bug("iseq_set_arguments: NODE_ARGS is expected, but %s", | |
ruby_node_name(nd_type(node_args))); | |
} | |
iseq->param.lead_num = (int)args->pre_args_num; | |
if (iseq->param.lead_num > 0) iseq->param.flags.has_lead = TRUE; | |
debugs(" - argc: %d\n", iseq->param.lead_num); | |
rest_id = args->rest_arg; | |
if (rest_id == 1) { | |
last_comma = 1; | |
rest_id = 0; | |
} | |
block_id = args->block_arg; | |
if (args->first_post_arg) { | |
iseq->param.post_start = get_dyna_var_idx_at_raw(iseq, args->first_post_arg); | |
iseq->param.post_num = args->post_args_num; | |
iseq->param.flags.has_post = TRUE; | |
} | |
if (args->opt_args) { | |
NODE *node = args->opt_args; | |
LABEL *label; | |
VALUE labels = rb_ary_tmp_new(1); | |
int i = 0, j; | |
while (node) { | |
label = NEW_LABEL(nd_line(node)); | |
rb_ary_push(labels, (VALUE)label | 1); | |
ADD_LABEL(optargs, label); | |
COMPILE_POPED(optargs, "optarg", node->nd_body); | |
node = node->nd_next; | |
i += 1; | |
} | |
/* last label */ | |
label = NEW_LABEL(nd_line(node_args)); | |
rb_ary_push(labels, (VALUE)label | 1); | |
ADD_LABEL(optargs, label); | |
iseq->param.opt_num = i; | |
iseq->param.opt_table = ALLOC_N(VALUE, i+1); | |
MEMCPY(iseq->param.opt_table, RARRAY_CONST_PTR(labels), VALUE, i+1); | |
for (j = 0; j < i+1; j++) { | |
iseq->param.opt_table[j] &= ~1; | |
} | |
rb_ary_clear(labels); | |
iseq->param.flags.has_opt = TRUE; | |
} | |
if (args->kw_args) { | |
NODE *node = args->kw_args; | |
const VALUE default_values = rb_ary_tmp_new(1); | |
const VALUE complex_mark = rb_str_tmp_new(0); | |
int kw = 0, rkw = 0, di = 0, i; | |
iseq->param.flags.has_kw = TRUE; | |
iseq->param.keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1); | |
iseq->param.keyword->bits_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); | |
while (node) { | |
NODE *val_node = node->nd_body->nd_value; | |
VALUE dv; | |
if (val_node == (NODE *)-1) { | |
++rkw; | |
} | |
else { | |
switch (nd_type(val_node)) { | |
case NODE_LIT: | |
dv = val_node->nd_lit; | |
iseq_add_mark_object(iseq, dv); | |
break; | |
case NODE_NIL: | |
dv = Qnil; | |
break; | |
case NODE_TRUE: | |
dv = Qtrue; | |
break; | |
case NODE_FALSE: | |
dv = Qfalse; | |
break; | |
default: | |
COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */ | |
dv = complex_mark; | |
} | |
iseq->param.keyword->num = ++di; | |
rb_ary_push(default_values, dv); | |
} | |
kw++; | |
node = node->nd_next; | |
} | |
iseq->param.keyword->num = kw; | |
if (args->kw_rest_arg->nd_cflag != 0) { | |
iseq->param.keyword->rest_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_cflag); | |
iseq->param.flags.has_kwrest = TRUE; | |
} | |
iseq->param.keyword->required_num = rkw; | |
iseq->param.keyword->table = &iseq->local_table[iseq->param.keyword->bits_start - iseq->param.keyword->num]; | |
iseq->param.keyword->default_values = ALLOC_N(VALUE, RARRAY_LEN(default_values)); | |
for (i = 0; i < RARRAY_LEN(default_values); i++) { | |
VALUE dv = RARRAY_AREF(default_values, i); | |
if (dv == complex_mark) dv = Qundef; | |
iseq->param.keyword->default_values[i] = dv; | |
} | |
} | |
else if (args->kw_rest_arg) { | |
iseq->param.flags.has_kwrest = TRUE; | |
iseq->param.keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1); | |
iseq->param.keyword->rest_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid); | |
} | |
if (args->pre_init) { /* m_init */ | |
COMPILE_POPED(optargs, "init arguments (m)", args->pre_init); | |
} | |
if (args->post_init) { /* p_init */ | |
COMPILE_POPED(optargs, "init arguments (p)", args->post_init); | |
} | |
if (rest_id) { | |
iseq->param.rest_start = get_dyna_var_idx_at_raw(iseq, rest_id); | |
iseq->param.flags.has_rest = TRUE; | |
assert(iseq->param.rest_start != -1); | |
if (iseq->param.post_start == 0) { /* TODO: why that? */ | |
iseq->param.post_start = iseq->param.rest_start + 1; | |
} | |
} | |
if (block_id) { | |
iseq->param.block_start = get_dyna_var_idx_at_raw(iseq, block_id); | |
iseq->param.flags.has_block = TRUE; | |
} | |
iseq_calc_param_size(iseq); | |
if (iseq->type == ISEQ_TYPE_BLOCK) { | |
if (iseq->param.flags.has_opt == FALSE && | |
iseq->param.flags.has_post == FALSE && | |
iseq->param.flags.has_rest == FALSE && | |
iseq->param.flags.has_kw == FALSE && | |
iseq->param.flags.has_kwrest == FALSE) { | |
if (iseq->param.lead_num == 1 && last_comma == 0) { | |
/* {|a|} */ | |
iseq->param.flags.ambiguous_param0 = TRUE; | |
} | |
} | |
} | |
} | |
return COMPILE_OK; | |
} | |
static int | |
iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl) | |
{ | |
int size; | |
if (tbl) { | |
size = (int)*tbl; | |
tbl++; | |
} | |
else { | |
size = 0; | |
} | |
if (size > 0) { | |
iseq->local_table = (ID *)ALLOC_N(ID, size); | |
MEMCPY(iseq->local_table, tbl, ID, size); | |
} | |
iseq->local_size = iseq->local_table_size = size; | |
iseq->local_size += 1; | |
/* | |
if (lfp == dfp ) { // top, class, method | |
dfp[-1]: svar | |
else { // block | |
dfp[-1]: cref | |
} | |
*/ | |
debugs("iseq_set_local_table: %d, %d\n", iseq->local_size, iseq->local_table_size); | |
return COMPILE_OK; | |
} | |
static int | |
cdhash_cmp(VALUE val, VALUE lit) | |
{ | |
if (val == lit) return 0; | |
if (SPECIAL_CONST_P(lit)) { | |
return val != lit; | |
} | |
if (SPECIAL_CONST_P(val) || BUILTIN_TYPE(val) != BUILTIN_TYPE(lit)) { | |
return -1; | |
} | |
if (BUILTIN_TYPE(lit) == T_STRING) { | |
return rb_str_hash_cmp(lit, val); | |
} | |
return !rb_eql(lit, val); | |
} | |
static st_index_t | |
cdhash_hash(VALUE a) | |
{ | |
if (SPECIAL_CONST_P(a)) return (st_index_t)a; | |
if (RB_TYPE_P(a, T_STRING)) return rb_str_hash(a); | |
{ | |
VALUE hval = rb_hash(a); | |
return (st_index_t)FIX2LONG(hval); | |
} | |
} | |
static const struct st_hash_type cdhash_type = { | |
cdhash_cmp, | |
cdhash_hash, | |
}; | |
struct cdhash_set_label_struct { | |
VALUE hash; | |
int pos; | |
int len; | |
}; | |
static int | |
cdhash_set_label_i(VALUE key, VALUE val, void *ptr) | |
{ | |
struct cdhash_set_label_struct *data = (struct cdhash_set_label_struct *)ptr; | |
LABEL *lobj = (LABEL *)(val & ~1); | |
rb_hash_aset(data->hash, key, INT2FIX(lobj->position - (data->pos+data->len))); | |
return ST_CONTINUE; | |
} | |
/** | |
ruby insn object list -> raw instruction sequence | |
*/ | |
static int | |
iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *anchor) | |
{ | |
LABEL *lobj; | |
INSN *iobj; | |
struct iseq_line_info_entry *line_info_table; | |
unsigned int last_line = 0; | |
LINK_ELEMENT *list; | |
VALUE *generated_iseq; | |
int k, pos, sp, stack_max = 0, line = 0; | |
/* set label position */ | |
list = FIRST_ELEMENT(anchor); | |
k = pos = 0; | |
while (list) { | |
switch (list->type) { | |
case ISEQ_ELEMENT_INSN: | |
{ | |
iobj = (INSN *)list; | |
line = iobj->line_no; | |
pos += insn_data_length(iobj); | |
k++; | |
break; | |
} | |
case ISEQ_ELEMENT_LABEL: | |
{ | |
lobj = (LABEL *)list; | |
lobj->position = pos; | |
lobj->set = TRUE; | |
break; | |
} | |
case ISEQ_ELEMENT_NONE: | |
{ | |
/* ignore */ | |
break; | |
} | |
case ISEQ_ELEMENT_ADJUST: | |
{ | |
ADJUST *adjust = (ADJUST *)list; | |
if (adjust->line_no != -1) { | |
pos += 2 /* insn + 1 operand */; | |
k++; | |
} | |
break; | |
} | |
default: | |
dump_disasm_list(FIRST_ELEMENT(anchor)); | |
dump_disasm_list(list); | |
rb_compile_error(RSTRING_PTR(iseq->location.path), line, | |
"error: set_sequence"); | |
break; | |
} | |
list = list->next; | |
} | |
/* make instruction sequence */ | |
generated_iseq = ALLOC_N(VALUE, pos); | |
line_info_table = ALLOC_N(struct iseq_line_info_entry, k); | |
iseq->is_entries = ZALLOC_N(union iseq_inline_storage_entry, iseq->is_size); | |
iseq->callinfo_entries = ALLOC_N(rb_call_info_t, iseq->callinfo_size); | |
/* MEMZERO(iseq->callinfo_entries, rb_call_info_t, iseq->callinfo_size); */ | |
list = FIRST_ELEMENT(anchor); | |
k = pos = sp = 0; | |
while (list) { | |
switch (list->type) { | |
case ISEQ_ELEMENT_INSN: | |
{ | |
int j, len, insn; | |
const char *types; | |
VALUE *operands; | |
iobj = (INSN *)list; | |
/* update sp */ | |
sp = calc_sp_depth(sp, iobj); | |
if (sp > stack_max) { | |
stack_max = sp; | |
} | |
/* fprintf(stderr, "insn: %-16s, sp: %d\n", insn_name(iobj->insn_id), sp); */ | |
operands = iobj->operands; | |
insn = iobj->insn_id; | |
generated_iseq[pos] = insn; | |
types = insn_op_types(insn); | |
len = insn_len(insn); | |
/* operand check */ | |
if (iobj->operand_size != len - 1) { | |
/* printf("operand size miss! (%d, %d)\n", iobj->operand_size, len); */ | |
dump_disasm_list(list); | |
rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no, | |
"operand size miss! (%d for %d)", | |
iobj->operand_size, len - 1); | |
xfree(generated_iseq); | |
xfree(line_info_table); | |
return 0; | |
} | |
for (j = 0; types[j]; j++) { | |
char type = types[j]; | |
/* printf("--> [%c - (%d-%d)]\n", type, k, j); */ | |
switch (type) { | |
case TS_OFFSET: | |
{ | |
/* label(destination position) */ | |
lobj = (LABEL *)operands[j]; | |
if (!lobj->set) { | |
rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no, | |
"unknown label"); | |
} | |
if (lobj->sp == -1) { | |
lobj->sp = sp; | |
} | |
generated_iseq[pos + 1 + j] = lobj->position - (pos + len); | |
break; | |
} | |
case TS_CDHASH: | |
{ | |
VALUE map = operands[j]; | |
struct cdhash_set_label_struct data; | |
data.hash = map; | |
data.pos = pos; | |
data.len = len; | |
rb_hash_foreach(map, cdhash_set_label_i, (VALUE)&data); | |
hide_obj(map); | |
generated_iseq[pos + 1 + j] = map; | |
break; | |
} | |
case TS_LINDEX: | |
case TS_NUM: /* ulong */ | |
generated_iseq[pos + 1 + j] = FIX2INT(operands[j]); | |
break; | |
case TS_ISEQ: /* iseq */ | |
{ | |
VALUE v = operands[j]; | |
rb_iseq_t *block = 0; | |
if (v) { | |
GetISeqPtr(v, block); | |
} | |
generated_iseq[pos + 1 + j] = (VALUE)block; | |
break; | |
} | |
case TS_VALUE: /* VALUE */ | |
{ | |
VALUE v = operands[j]; | |
generated_iseq[pos + 1 + j] = v; | |
/* to mark ruby object */ | |
iseq_add_mark_object(iseq, v); | |
break; | |
} | |
case TS_IC: /* inline cache */ | |
{ | |
int ic_index = FIX2INT(operands[j]); | |
IC ic = (IC)&iseq->is_entries[ic_index]; | |
if (UNLIKELY(ic_index >= iseq->is_size)) { | |
rb_bug("iseq_set_sequence: ic_index overflow: index: %d, size: %d", ic_index, iseq->is_size); | |
} | |
generated_iseq[pos + 1 + j] = (VALUE)ic; | |
break; | |
} | |
case TS_CALLINFO: /* call info */ | |
{ | |
rb_call_info_t *base_ci = (rb_call_info_t *)operands[j]; | |
rb_call_info_t *ci = &iseq->callinfo_entries[base_ci->aux.index]; | |
*ci = *base_ci; | |
if (UNLIKELY(base_ci->aux.index >= iseq->callinfo_size)) { | |
rb_bug("iseq_set_sequence: ci_index overflow: index: %d, size: %d", base_ci->argc, iseq->callinfo_size); | |
} | |
generated_iseq[pos + 1 + j] = (VALUE)ci; | |
break; | |
} | |
case TS_ID: /* ID */ | |
generated_iseq[pos + 1 + j] = SYM2ID(operands[j]); | |
break; | |
case TS_GENTRY: | |
{ | |
struct rb_global_entry *entry = | |
(struct rb_global_entry *)(operands[j] & (~1)); | |
generated_iseq[pos + 1 + j] = (VALUE)entry; | |
} | |
break; | |
case TS_FUNCPTR: | |
generated_iseq[pos + 1 + j] = operands[j]; | |
break; | |
default: | |
rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no, | |
"unknown operand type: %c", type); | |
xfree(generated_iseq); | |
xfree(line_info_table); | |
return 0; | |
} | |
} | |
if (last_line != iobj->line_no) { | |
line_info_table[k].line_no = last_line = iobj->line_no; | |
line_info_table[k].position = pos; | |
k++; | |
} | |
pos += len; | |
break; | |
} | |
case ISEQ_ELEMENT_LABEL: | |
{ | |
lobj = (LABEL *)list; | |
if (lobj->sp == -1) { | |
lobj->sp = sp; | |
} | |
else { | |
sp = lobj->sp; | |
} | |
break; | |
} | |
case ISEQ_ELEMENT_ADJUST: | |
{ | |
ADJUST *adjust = (ADJUST *)list; | |
int orig_sp = sp; | |
if (adjust->label) { | |
sp = adjust->label->sp; | |
} | |
else { | |
sp = 0; | |
} | |
if (adjust->line_no != -1) { | |
if (orig_sp - sp > 0) { | |
if (last_line != (unsigned int)adjust->line_no) { | |
line_info_table[k].line_no = last_line = adjust->line_no; | |
line_info_table[k].position = pos; | |
k++; | |
} | |
generated_iseq[pos++] = BIN(adjuststack); | |
generated_iseq[pos++] = orig_sp - sp; | |
} | |
else if (orig_sp - sp == 0) { | |
/* jump to next insn */ | |
if (last_line != (unsigned int)adjust->line_no) { | |
line_info_table[k].line_no = last_line = adjust->line_no; | |
line_info_table[k].position = pos; | |
k++; | |
} | |
generated_iseq[pos++] = BIN(nop); | |
generated_iseq[pos++] = BIN(nop); | |
} | |
else { | |
rb_bug("iseq_set_sequence: adjust bug"); | |
} | |
} | |
break; | |
} | |
default: | |
/* ignore */ | |
break; | |
} | |
list = list->next; | |
} | |
iseq->iseq_encoded = (void *)generated_iseq; | |
iseq->iseq_size = pos; | |
iseq->stack_max = stack_max; | |
REALLOC_N(line_info_table, struct iseq_line_info_entry, k); | |
iseq->line_info_table = line_info_table; | |
iseq->line_info_size = k; | |
return COMPILE_OK; | |
} | |
static int | |
label_get_position(LABEL *lobj) | |
{ | |
return lobj->position; | |
} | |
static int | |
label_get_sp(LABEL *lobj) | |
{ | |
return lobj->sp; | |
} | |
static int | |
iseq_set_exception_table(rb_iseq_t *iseq) | |
{ | |
const VALUE *tptr, *ptr; | |
int tlen, i; | |
struct iseq_catch_table_entry *entry; | |
tlen = (int)RARRAY_LEN(iseq->compile_data->catch_table_ary); | |
tptr = RARRAY_CONST_PTR(iseq->compile_data->catch_table_ary); | |
iseq->catch_table = 0; | |
if (tlen > 0) { | |
iseq->catch_table = xmalloc(iseq_catch_table_bytes(tlen)); | |
iseq->catch_table->size = tlen; | |
} | |
if (iseq->catch_table) for (i = 0; i < iseq->catch_table->size; i++) { | |
ptr = RARRAY_CONST_PTR(tptr[i]); | |
entry = &iseq->catch_table->entries[i]; | |
entry->type = (enum catch_type)(ptr[0] & 0xffff); | |
entry->start = label_get_position((LABEL *)(ptr[1] & ~1)); | |
entry->end = label_get_position((LABEL *)(ptr[2] & ~1)); | |
entry->iseq = ptr[3]; | |
/* register iseq as mark object */ | |
if (entry->iseq != 0) { | |
iseq_add_mark_object(iseq, entry->iseq); | |
} | |
/* stack depth */ | |
if (ptr[4]) { | |
LABEL *lobj = (LABEL *)(ptr[4] & ~1); | |
entry->cont = label_get_position(lobj); | |
entry->sp = label_get_sp(lobj); | |
/* TODO: Dirty Hack! Fix me */ | |
if (entry->type == CATCH_TYPE_RESCUE || | |
entry->type == CATCH_TYPE_BREAK || | |
entry->type == CATCH_TYPE_NEXT) { | |
entry->sp--; | |
} | |
} | |
else { | |
entry->cont = 0; | |
} | |
} | |
RB_OBJ_WRITE(iseq->self, &iseq->compile_data->catch_table_ary, 0); /* free */ | |
return COMPILE_OK; | |
} | |
/* | |
* set optional argument table | |
* def foo(a, b=expr1, c=expr2) | |
* => | |
* b: | |
* expr1 | |
* c: | |
* expr2 | |
*/ | |
static int | |
iseq_set_optargs_table(rb_iseq_t *iseq) | |
{ | |
int i; | |
if (iseq->param.flags.has_opt) { | |
for (i = 0; i < iseq->param.opt_num + 1; i++) { | |
iseq->param.opt_table[i] = label_get_position((LABEL *)iseq->param.opt_table[i]); | |
} | |
} | |
return COMPILE_OK; | |
} | |
static LINK_ELEMENT * | |
get_destination_insn(INSN *iobj) | |
{ | |
LABEL *lobj = (LABEL *)OPERAND_AT(iobj, 0); | |
LINK_ELEMENT *list; | |
list = lobj->link.next; | |
while (list) { | |
if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) { | |
break; | |
} | |
list = list->next; | |
} | |
return list; | |
} | |
static LINK_ELEMENT * | |
get_next_insn(INSN *iobj) | |
{ | |
LINK_ELEMENT *list = iobj->link.next; | |
while (list) { | |
if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) { | |
return list; | |
} | |
list = list->next; | |
} | |
return 0; | |
} | |
static LINK_ELEMENT * | |
get_prev_insn(INSN *iobj) | |
{ | |
LINK_ELEMENT *list = iobj->link.prev; | |
while (list) { | |
if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) { | |
return list; | |
} | |
list = list->prev; | |
} | |
return 0; | |
} | |
static int | |
iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt) | |
{ | |
INSN *iobj = (INSN *)list; | |
again: | |
if (iobj->insn_id == BIN(jump)) { | |
INSN *niobj, *diobj, *piobj; | |
/* | |
* useless jump elimination: | |
* jump LABEL1 | |
* ... | |
* LABEL1: | |
* jump LABEL2 | |
* | |
* => in this case, first jump instruction should jump to | |
* LABEL2 directly | |
*/ | |
diobj = (INSN *)get_destination_insn(iobj); | |
niobj = (INSN *)get_next_insn(iobj); | |
if (diobj == niobj) { | |
/* | |
* jump LABEL | |
* LABEL: | |
* => | |
* LABEL: | |
*/ | |
REMOVE_ELEM(&iobj->link); | |
} | |
else if (iobj != diobj && diobj->insn_id == BIN(jump)) { | |
if (OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0)) { | |
OPERAND_AT(iobj, 0) = OPERAND_AT(diobj, 0); | |
goto again; | |
} | |
} | |
else if (diobj->insn_id == BIN(leave)) { | |
/* | |
* jump LABEL | |
* ... | |
* LABEL: | |
* leave | |
* => | |
* leave | |
* ... | |
* LABEL: | |
* leave | |
*/ | |
INSN *eiobj = new_insn_core(iseq, iobj->line_no, BIN(leave), | |
diobj->operand_size, diobj->operands); | |
INSN *popiobj = new_insn_core(iseq, iobj->line_no, | |
BIN(pop), 0, 0); | |
/* replace */ | |
REPLACE_ELEM((LINK_ELEMENT *)iobj, (LINK_ELEMENT *)eiobj); | |
INSERT_ELEM_NEXT((LINK_ELEMENT *)eiobj, (LINK_ELEMENT *)popiobj); | |
iobj = popiobj; | |
} | |
/* | |
* useless jump elimination (if/unless destination): | |
* if L1 | |
* jump L2 | |
* L1: | |
* ... | |
* L2: | |
* | |
* ==> | |
* unless L2 | |
* L1: | |
* ... | |
* L2: | |
*/ | |
else if ((piobj = (INSN *)get_prev_insn(iobj)) != 0 && | |
(piobj->insn_id == BIN(branchif) || | |
piobj->insn_id == BIN(branchunless))) { | |
if (niobj == (INSN *)get_destination_insn(piobj)) { | |
piobj->insn_id = (piobj->insn_id == BIN(branchif)) | |
? BIN(branchunless) : BIN(branchif); | |
OPERAND_AT(piobj, 0) = OPERAND_AT(iobj, 0); | |
REMOVE_ELEM(&iobj->link); | |
} | |
} | |
} | |
if (iobj->insn_id == BIN(branchif) || | |
iobj->insn_id == BIN(branchunless)) { | |
/* | |
* if L1 | |
* ... | |
* L1: | |
* jump L2 | |
* => | |
* if L2 | |
*/ | |
INSN *nobj = (INSN *)get_destination_insn(iobj); | |
if (nobj->insn_id == BIN(jump)) { | |
OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0); | |
} | |
} | |
if (do_tailcallopt && iobj->insn_id == BIN(leave)) { | |
/* | |
* send ... | |
* leave | |
* => | |
* send ..., ... | VM_CALL_TAILCALL, ... | |
* leave # unreachable | |
*/ | |
INSN *piobj = (INSN *)get_prev_insn((INSN *)list); | |
enum ruby_vminsn_type previ = piobj->insn_id; | |
if (previ == BIN(send) || previ == BIN(opt_send_without_block) || previ == BIN(invokesuper)) { | |
rb_call_info_t *ci = (rb_call_info_t *)piobj->operands[0]; | |
if (ci->blockiseq == 0) { | |
ci->flag |= VM_CALL_TAILCALL; | |
} | |
} | |
} | |
return COMPILE_OK; | |
} | |
static int | |
insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id) | |
{ | |
int old_opsize = iobj->operand_size; | |
iobj->insn_id = insn_id; | |
iobj->operand_size = insn_len(insn_id) - 1; | |
if (iobj->operand_size > old_opsize) { | |
VALUE *old_operands = iobj->operands; | |
if (insn_id != BIN(opt_neq)) { | |
rb_bug("insn_set_specialized_instruction: unknown insn: %d", insn_id); | |
} | |
iobj->operands = (VALUE *)compile_data_alloc(iseq, iobj->operand_size * sizeof(VALUE)); | |
iobj->operands[0] = old_operands[0]; | |
iobj->operands[1] = (VALUE)new_callinfo(iseq, idEq, 1, 0, 0, NULL); | |
} | |
return COMPILE_OK; | |
} | |
static int | |
iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj) | |
{ | |
if (iobj->insn_id == BIN(send)) { | |
rb_call_info_t *ci = (rb_call_info_t *)OPERAND_AT(iobj, 0); | |
#define SP_INSN(opt) insn_set_specialized_instruction(iseq, iobj, BIN(opt_##opt)) | |
if (ci->flag & VM_CALL_ARGS_SIMPLE) { | |
switch (ci->orig_argc) { | |
case 0: | |
switch (ci->mid) { | |
case idLength: SP_INSN(length); return COMPILE_OK; | |
case idSize: SP_INSN(size); return COMPILE_OK; | |
case idEmptyP: SP_INSN(empty_p);return COMPILE_OK; | |
case idSucc: SP_INSN(succ); return COMPILE_OK; | |
case idNot: SP_INSN(not); return COMPILE_OK; | |
} | |
break; | |
case 1: | |
switch (ci->mid) { | |
case idPLUS: SP_INSN(plus); return COMPILE_OK; | |
case idMINUS: SP_INSN(minus); return COMPILE_OK; | |
case idMULT: SP_INSN(mult); return COMPILE_OK; | |
case idDIV: SP_INSN(div); return COMPILE_OK; | |
case idMOD: SP_INSN(mod); return COMPILE_OK; | |
case idEq: SP_INSN(eq); return COMPILE_OK; | |
case idNeq: SP_INSN(neq); return COMPILE_OK; | |
case idLT: SP_INSN(lt); return COMPILE_OK; | |
case idLE: SP_INSN(le); return COMPILE_OK; | |
case idGT: SP_INSN(gt); return COMPILE_OK; | |
case idGE: SP_INSN(ge); return COMPILE_OK; | |
case idLTLT: SP_INSN(ltlt); return COMPILE_OK; | |
case idAREF: SP_INSN(aref); return COMPILE_OK; | |
} | |
break; | |
case 2: | |
switch (ci->mid) { | |
case idASET: SP_INSN(aset); return COMPILE_OK; | |
} | |
break; | |
} | |
} | |
if ((ci->flag & VM_CALL_ARGS_BLOCKARG) == 0 && ci->blockiseq == NULL) { | |
iobj->insn_id = BIN(opt_send_without_block); | |
} | |
} | |
#undef SP_INSN | |
return COMPILE_OK; | |
} | |
static int | |
iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor) | |
{ | |
LINK_ELEMENT *list; | |
const int do_peepholeopt = iseq->compile_data->option->peephole_optimization; | |
const int do_tailcallopt = iseq->compile_data->option->tailcall_optimization; | |
const int do_si = iseq->compile_data->option->specialized_instruction; | |
const int do_ou = iseq->compile_data->option->operands_unification; | |
list = FIRST_ELEMENT(anchor); | |
while (list) { | |
if (list->type == ISEQ_ELEMENT_INSN) { | |
if (do_peepholeopt) { | |
iseq_peephole_optimize(iseq, list, do_tailcallopt); | |
} | |
if (do_si) { | |
iseq_specialized_instruction(iseq, (INSN *)list); | |
} | |
if (do_ou) { | |
insn_operands_unification((INSN *)list); | |
} | |
} | |
list = list->next; | |
} | |
return COMPILE_OK; | |
} | |
#if OPT_INSTRUCTIONS_UNIFICATION | |
static INSN * | |
new_unified_insn(rb_iseq_t *iseq, | |
int insn_id, int size, LINK_ELEMENT *seq_list) | |
{ | |
INSN *iobj = 0; | |
LINK_ELEMENT *list = seq_list; | |
int i, argc = 0; | |
VALUE *operands = 0, *ptr = 0; | |
/* count argc */ | |
for (i = 0; i < size; i++) { | |
iobj = (INSN *)list; | |
argc += iobj->operand_size; | |
list = list->next; | |
} | |
if (argc > 0) { | |
ptr = operands = | |
(VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * argc); | |
} | |
/* copy operands */ | |
list = seq_list; | |
for (i = 0; i < size; i++) { | |
iobj = (INSN *)list; | |
MEMCPY(ptr, iobj->operands, VALUE, iobj->operand_size); | |
ptr += iobj->operand_size; | |
list = list->next; | |
} | |
return new_insn_core(iseq, iobj->line_no, insn_id, argc, operands); | |
} | |
#endif | |
/* | |
* This scheme can get more performance if do this optimize with | |
* label address resolving. | |
* It's future work (if compile time was bottle neck). | |
*/ | |
static int | |
iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor) | |
{ | |
#if OPT_INSTRUCTIONS_UNIFICATION | |
LINK_ELEMENT *list; | |
INSN *iobj, *niobj; | |
int id, k; | |
intptr_t j; | |
list = FIRST_ELEMENT(anchor); | |
while (list) { | |
if (list->type == ISEQ_ELEMENT_INSN) { | |
iobj = (INSN *)list; | |
id = iobj->insn_id; | |
if (unified_insns_data[id] != 0) { | |
const int *const *entry = unified_insns_data[id]; | |
for (j = 1; j < (intptr_t)entry[0]; j++) { | |
const int *unified = entry[j]; | |
LINK_ELEMENT *li = list->next; | |
for (k = 2; k < unified[1]; k++) { | |
if (li->type != ISEQ_ELEMENT_INSN || | |
((INSN *)li)->insn_id != unified[k]) { | |
goto miss; | |
} | |
li = li->next; | |
} | |
/* matched */ | |
niobj = | |
new_unified_insn(iseq, unified[0], unified[1] - 1, | |
list); | |
/* insert to list */ | |
niobj->link.prev = (LINK_ELEMENT *)iobj->link.prev; | |
niobj->link.next = li; | |
if (li) { | |
li->prev = (LINK_ELEMENT *)niobj; | |
} | |
list->prev->next = (LINK_ELEMENT *)niobj; | |
list = (LINK_ELEMENT *)niobj; | |
break; | |
miss:; | |
} | |
} | |
} | |
list = list->next; | |
} | |
#endif | |
return COMPILE_OK; | |
} | |
#if OPT_STACK_CACHING | |
#define SC_INSN(insn, stat) sc_insn_info[(insn)][(stat)] | |
#define SC_NEXT(insn) sc_insn_next[(insn)] | |
#include "opt_sc.inc" | |
static int | |
insn_set_sc_state(rb_iseq_t *iseq, INSN *iobj, int state) | |
{ | |
int nstate; | |
int insn_id; | |
insn_id = iobj->insn_id; | |
iobj->insn_id = SC_INSN(insn_id, state); | |
nstate = SC_NEXT(iobj->insn_id); | |
if (insn_id == BIN(jump) || | |
insn_id == BIN(branchif) || insn_id == BIN(branchunless)) { | |
LABEL *lobj = (LABEL *)OPERAND_AT(iobj, 0); | |
if (lobj->sc_state != 0) { | |
if (lobj->sc_state != nstate) { | |
dump_disasm_list((LINK_ELEMENT *)iobj); | |
dump_disasm_list((LINK_ELEMENT *)lobj); | |
printf("\n-- %d, %d\n", lobj->sc_state, nstate); | |
rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no, | |
"insn_set_sc_state error\n"); | |
return 0; | |
} | |
} | |
else { | |
lobj->sc_state = nstate; | |
} | |
if (insn_id == BIN(jump)) { | |
nstate = SCS_XX; | |
} | |
} | |
else if (insn_id == BIN(leave)) { | |
nstate = SCS_XX; | |
} | |
return nstate; | |
} | |
static int | |
label_set_sc_state(LABEL *lobj, int state) | |
{ | |
if (lobj->sc_state != 0) { | |
if (lobj->sc_state != state) { | |
state = lobj->sc_state; | |
} | |
} | |
else { | |
lobj->sc_state = state; | |
} | |
return state; | |
} | |
#endif | |
static int | |
iseq_set_sequence_stackcaching(rb_iseq_t *iseq, LINK_ANCHOR *anchor) | |
{ | |
#if OPT_STACK_CACHING | |
LINK_ELEMENT *list; | |
int state, insn_id; | |
/* initialize */ | |
state = SCS_XX; | |
list = FIRST_ELEMENT(anchor); | |
/* dump_disasm_list(list); */ | |
/* for each list element */ | |
while (list) { | |
redo_point: | |
switch (list->type) { | |
case ISEQ_ELEMENT_INSN: | |
{ | |
INSN *iobj = (INSN *)list; | |
insn_id = iobj->insn_id; | |
/* dump_disasm_list(list); */ | |
switch (insn_id) { | |
case BIN(nop): | |
{ | |
/* exception merge point */ | |
if (state != SCS_AX) { | |
INSN *rpobj = | |
new_insn_body(iseq, 0, BIN(reput), 0); | |
/* replace this insn */ | |
REPLACE_ELEM(list, (LINK_ELEMENT *)rpobj); | |
list = (LINK_ELEMENT *)rpobj; | |
goto redo_point; | |
} | |
break; | |
} | |
case BIN(swap): | |
{ | |
if (state == SCS_AB || state == SCS_BA) { | |
state = (state == SCS_AB ? SCS_BA : SCS_AB); | |
REMOVE_ELEM(list); | |
list = list->next; | |
goto redo_point; | |
} | |
break; | |
} | |
case BIN(pop): | |
{ | |
switch (state) { | |
case SCS_AX: | |
case SCS_BX: | |
state = SCS_XX; | |
break; | |
case SCS_AB: | |
state = SCS_AX; | |
break; | |
case SCS_BA: | |
state = SCS_BX; | |
break; | |
case SCS_XX: | |
goto normal_insn; | |
default: | |
rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no, | |
"unreachable"); | |
} | |
/* remove useless pop */ | |
REMOVE_ELEM(list); | |
list = list->next; | |
goto redo_point; | |
} | |
default:; | |
/* none */ | |
} /* end of switch */ | |
normal_insn: | |
state = insn_set_sc_state(iseq, iobj, state); | |
break; | |
} | |
case ISEQ_ELEMENT_LABEL: | |
{ | |
LABEL *lobj; | |
lobj = (LABEL *)list; | |
state = label_set_sc_state(lobj, state); | |
} | |
default: | |
break; | |
} | |
list = list->next; | |
} | |
#endif | |
return COMPILE_OK; | |
} | |
static int | |
compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *node, int *cntp) | |
{ | |
NODE *list = node->nd_next; | |
VALUE lit = node->nd_lit; | |
int cnt = 0; | |
debugp_param("nd_lit", lit); | |
if (!NIL_P(lit)) { | |
cnt++; | |
if (RB_TYPE_P(lit, T_STRING)) | |
lit = node->nd_lit = rb_fstring(node->nd_lit); | |
ADD_INSN1(ret, nd_line(node), putobject, lit); | |
} | |
while (list) { | |
node = list->nd_head; | |
if (nd_type(node) == NODE_STR) { | |
node->nd_lit = rb_fstring(node->nd_lit); | |
ADD_INSN1(ret, nd_line(node), putobject, node->nd_lit); | |
} | |
else { | |
COMPILE(ret, "each string", node); | |
} | |
cnt++; | |
list = list->nd_next; | |
} | |
*cntp = cnt; | |
return COMPILE_OK; | |
} | |
static int | |
compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node) | |
{ | |
int cnt; | |
compile_dstr_fragments(iseq, ret, node, &cnt); | |
ADD_INSN1(ret, nd_line(node), concatstrings, INT2FIX(cnt)); | |
return COMPILE_OK; | |
} | |
static int | |
compile_dregx(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node) | |
{ | |
int cnt; | |
compile_dstr_fragments(iseq, ret, node, &cnt); | |
ADD_INSN2(ret, nd_line(node), toregexp, INT2FIX(node->nd_cflag), INT2FIX(cnt)); | |
return COMPILE_OK; | |
} | |
static int | |
compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * cond, | |
LABEL *then_label, LABEL *else_label) | |
{ | |
switch (nd_type(cond)) { | |
case NODE_AND: | |
{ | |
LABEL *label = NEW_LABEL(nd_line(cond)); | |
compile_branch_condition(iseq, ret, cond->nd_1st, label, | |
else_label); | |
ADD_LABEL(ret, label); | |
compile_branch_condition(iseq, ret, cond->nd_2nd, then_label, | |
else_label); | |
break; | |
} | |
case NODE_OR: | |
{ | |
LABEL *label = NEW_LABEL(nd_line(cond)); | |
compile_branch_condition(iseq, ret, cond->nd_1st, then_label, | |
label); | |
ADD_LABEL(ret, label); | |
compile_branch_condition(iseq, ret, cond->nd_2nd, then_label, | |
else_label); | |
break; | |
} | |
case NODE_LIT: /* NODE_LIT is always not true */ | |
case NODE_TRUE: | |
case NODE_STR: | |
/* printf("useless condition eliminate (%s)\n", ruby_node_name(nd_type(cond))); */ | |
ADD_INSNL(ret, nd_line(cond), jump, then_label); | |
break; | |
case NODE_FALSE: | |
case NODE_NIL: | |
/* printf("useless condition eliminate (%s)\n", ruby_node_name(nd_type(cond))); */ | |
ADD_INSNL(ret, nd_line(cond), jump, else_label); | |
break; | |
default: | |
COMPILE(ret, "branch condition", cond); | |
ADD_INSNL(ret, nd_line(cond), branchunless, else_label); | |
ADD_INSNL(ret, nd_line(cond), jump, then_label); | |
break; | |
} | |
return COMPILE_OK; | |
} | |
static int | |
compile_array_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *ret, const NODE * const root_node, rb_call_info_kw_arg_t ** const kw_arg_ptr) | |
{ | |
if (kw_arg_ptr == NULL) return FALSE; | |
if (nd_type(root_node) == NODE_HASH && root_node->nd_head && nd_type(root_node->nd_head) == NODE_ARRAY) { | |
NODE *node = root_node->nd_head; | |
while (node) { | |
NODE *key_node = node->nd_head; | |
assert(nd_type(node) == NODE_ARRAY); | |
if (key_node && nd_type(key_node) == NODE_LIT && RB_TYPE_P(key_node->nd_lit, T_SYMBOL)) { | |
/* can be keywords */ | |
} | |
else { | |
return FALSE; | |
} | |
node = node->nd_next; /* skip value node */ | |
node = node->nd_next; | |
} | |
/* may be keywords */ | |
node = root_node->nd_head; | |
{ | |
int len = (int)node->nd_alen / 2; | |
rb_call_info_kw_arg_t *kw_arg = (rb_call_info_kw_arg_t *)ruby_xmalloc(sizeof(rb_call_info_kw_arg_t) + sizeof(VALUE) * (len - 1)); | |
ID *keywords = kw_arg->keywords; | |
int i = 0; | |
kw_arg->keyword_len = len; | |
*kw_arg_ptr = kw_arg; | |
for (i=0; node != NULL; i++, node = node->nd_next->nd_next) { | |
NODE *key_node = node->nd_head; | |
NODE *val_node = node->nd_next->nd_head; | |
keywords[i] = SYM2ID(key_node->nd_lit); | |
COMPILE(ret, "keyword values", val_node); | |
} | |
assert(i == len); | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
enum compile_array_type_t { | |
COMPILE_ARRAY_TYPE_ARRAY, | |
COMPILE_ARRAY_TYPE_HASH, | |
COMPILE_ARRAY_TYPE_ARGS | |
}; | |
static int | |
compile_array_(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, | |
enum compile_array_type_t type, rb_call_info_kw_arg_t **keywords_ptr, int poped) | |
{ | |
NODE *node = node_root; | |
int line = (int)nd_line(node); | |
int len = 0; | |
if (nd_type(node) == NODE_ZARRAY) { | |
if (!poped) { | |
switch (type) { | |
case COMPILE_ARRAY_TYPE_ARRAY: ADD_INSN1(ret, line, newarray, INT2FIX(0)); break; | |
case COMPILE_ARRAY_TYPE_HASH: ADD_INSN1(ret, line, newhash, INT2FIX(0)); break; | |
case COMPILE_ARRAY_TYPE_ARGS: /* do nothing */ break; | |
} | |
} | |
} | |
else { | |
int opt_p = 1; | |
int first = 1, i; | |
while (node) { | |
NODE *start_node = node, *end_node; | |
NODE *kw = 0; | |
const int max = 0x100; | |
DECL_ANCHOR(anchor); | |
INIT_ANCHOR(anchor); | |
for (i=0; i<max && node; i++, len++, node = node->nd_next) { | |
if (CPDEBUG > 0 && nd_type(node) != NODE_ARRAY) { | |
rb_bug("compile_array: This node is not NODE_ARRAY, but %s", ruby_node_name(nd_type(node))); | |
} | |
if (type == COMPILE_ARRAY_TYPE_HASH && !node->nd_head) { | |
opt_p = 0; | |
kw = node->nd_next; | |
node = kw->nd_next; | |
kw = kw->nd_head; | |
break; | |
} | |
if (opt_p && nd_type(node->nd_head) != NODE_LIT) { | |
opt_p = 0; | |
} | |
if (type == COMPILE_ARRAY_TYPE_ARGS && node->nd_next == NULL /* last node */ && compile_array_keyword_arg(iseq, anchor, node->nd_head, keywords_ptr)) { | |
len--; | |
} | |
else { | |
COMPILE_(anchor, "array element", node->nd_head, poped); | |
} | |
} | |
if (opt_p && type != COMPILE_ARRAY_TYPE_ARGS) { | |
if (!poped) { | |
VALUE ary = rb_ary_tmp_new(i); | |
end_node = node; | |
node = start_node; | |
while (node != end_node) { | |
rb_ary_push(ary, node->nd_head->nd_lit); | |
node = node->nd_next; | |
} | |
while (node && nd_type(node->nd_head) == NODE_LIT && | |
node->nd_next && nd_type(node->nd_next->nd_head) == NODE_LIT) { | |
rb_ary_push(ary, node->nd_head->nd_lit); | |
node = node->nd_next; | |
rb_ary_push(ary, node->nd_head->nd_lit); | |
node = node->nd_next; | |
len++; | |
} | |
OBJ_FREEZE(ary); | |
iseq_add_mark_object_compile_time(iseq, ary); | |
if (first) { | |
first = 0; | |
if (type == COMPILE_ARRAY_TYPE_ARRAY) { | |
ADD_INSN1(ret, line, duparray, ary); | |
} | |
else { /* COMPILE_ARRAY_TYPE_HASH */ | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putobject, ary); | |
ADD_SEND(ret, line, id_core_hash_from_ary, INT2FIX(1)); | |
} | |
} | |
else { | |
if (type == COMPILE_ARRAY_TYPE_ARRAY) { | |
ADD_INSN1(ret, line, putobject, ary); | |
ADD_INSN(ret, line, concatarray); | |
} | |
else { | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putobject, ary); | |
ADD_SEND(ret, line, id_core_hash_merge_ary, INT2FIX(1)); | |
} | |
} | |
} | |
} | |
else { | |
if (!poped) { | |
switch (type) { | |
case COMPILE_ARRAY_TYPE_ARRAY: | |
ADD_INSN1(anchor, line, newarray, INT2FIX(i)); | |
if (first) { | |
first = 0; | |
} | |
else { | |
ADD_INSN(anchor, line, concatarray); | |
} | |
APPEND_LIST(ret, anchor); | |
break; | |
case COMPILE_ARRAY_TYPE_HASH: | |
if (i > 0) { | |
if (first) { | |
ADD_INSN1(anchor, line, newhash, INT2FIX(i)); | |
APPEND_LIST(ret, anchor); | |
} | |
else { | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN(ret, line, swap); | |
APPEND_LIST(ret, anchor); | |
ADD_SEND(ret, line, id_core_hash_merge_ptr, INT2FIX(i + 1)); | |
} | |
} | |
if (kw) { | |
VALUE nhash = (i > 0 || !first) ? INT2FIX(2) : INT2FIX(1); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
if (i > 0 || !first) ADD_INSN(ret, line, swap); | |
COMPILE(ret, "keyword splat", kw); | |
ADD_SEND(ret, line, id_core_hash_merge_kwd, nhash); | |
if (nhash == INT2FIX(1)) ADD_SEND(ret, line, rb_intern("dup"), INT2FIX(0)); | |
} | |
first = 0; | |
break; | |
case COMPILE_ARRAY_TYPE_ARGS: | |
APPEND_LIST(ret, anchor); | |
break; | |
} | |
} | |
else { | |
/* poped */ | |
APPEND_LIST(ret, anchor); | |
} | |
} | |
} | |
} | |
return len; | |
} | |
static VALUE | |
compile_array(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE* node_root, enum compile_array_type_t type) | |
{ | |
return compile_array_(iseq, ret, node_root, type, NULL, 0); | |
} | |
static VALUE | |
case_when_optimizable_literal(NODE * node) | |
{ | |
switch (nd_type(node)) { | |
case NODE_LIT: { | |
VALUE v = node->nd_lit; | |
double ival; | |
if (RB_TYPE_P(v, T_FLOAT) && | |
modf(RFLOAT_VALUE(v), &ival) == 0.0) { | |
return FIXABLE(ival) ? LONG2FIX((long)ival) : rb_dbl2big(ival); | |
} | |
if (SYMBOL_P(v) || rb_obj_is_kind_of(v, rb_cNumeric)) { | |
return v; | |
} | |
break; | |
} | |
case NODE_STR: | |
return node->nd_lit = rb_fstring(node->nd_lit); | |
} | |
return Qundef; | |
} | |
static int | |
when_vals(rb_iseq_t *iseq, LINK_ANCHOR *cond_seq, NODE *vals, LABEL *l1, int only_special_literals, VALUE literals) | |
{ | |
while (vals) { | |
NODE* val = vals->nd_head; | |
VALUE lit = case_when_optimizable_literal(val); | |
if (lit == Qundef) { | |
only_special_literals = 0; | |
} | |
else { | |
if (rb_hash_lookup(literals, lit) != Qnil) { | |
rb_compile_warning(RSTRING_PTR(iseq->location.path), nd_line(val), "duplicated when clause is ignored"); | |
} | |
else { | |
rb_hash_aset(literals, lit, (VALUE)(l1) | 1); | |
} | |
} | |
ADD_INSN(cond_seq, nd_line(val), dup); /* dup target */ | |
if (nd_type(val) == NODE_STR) { | |
val->nd_lit = rb_fstring(val->nd_lit); | |
debugp_param("nd_lit", val->nd_lit); | |
ADD_INSN1(cond_seq, nd_line(val), putobject, val->nd_lit); | |
} | |
else { | |
COMPILE(cond_seq, "when cond", val); | |
} | |
ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE)); | |
ADD_INSNL(cond_seq, nd_line(val), branchif, l1); | |
vals = vals->nd_next; | |
} | |
return only_special_literals; | |
} | |
static int | |
compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *node) | |
{ | |
switch (nd_type(node)) { | |
case NODE_ATTRASGN: { | |
INSN *iobj; | |
rb_call_info_t *ci; | |
VALUE dupidx; | |
COMPILE_POPED(ret, "masgn lhs (NODE_ATTRASGN)", node); | |
POP_ELEMENT(ret); /* pop pop insn */ | |
iobj = (INSN *)POP_ELEMENT(ret); /* pop send insn */ | |
ci = (rb_call_info_t *)iobj->operands[0]; | |
ci->orig_argc += 1; ci->argc = ci->orig_argc; | |
dupidx = INT2FIX(ci->orig_argc); | |
ADD_INSN1(ret, nd_line(node), topn, dupidx); | |
ADD_ELEM(ret, (LINK_ELEMENT *)iobj); | |
ADD_INSN(ret, nd_line(node), pop); /* result */ | |
ADD_INSN(ret, nd_line(node), pop); /* rhs */ | |
break; | |
} | |
case NODE_MASGN: { | |
DECL_ANCHOR(anchor); | |
INIT_ANCHOR(anchor); | |
COMPILE_POPED(anchor, "nest masgn lhs", node); | |
REMOVE_ELEM(FIRST_ELEMENT(anchor)); | |
ADD_SEQ(ret, anchor); | |
break; | |
} | |
default: { | |
DECL_ANCHOR(anchor); | |
INIT_ANCHOR(anchor); | |
COMPILE_POPED(anchor, "masgn lhs", node); | |
REMOVE_ELEM(FIRST_ELEMENT(anchor)); | |
ADD_SEQ(ret, anchor); | |
} | |
} | |
return COMPILE_OK; | |
} | |
static void | |
compile_massign_opt_lhs(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *lhsn) | |
{ | |
if (lhsn) { | |
compile_massign_opt_lhs(iseq, ret, lhsn->nd_next); | |
compile_massign_lhs(iseq, ret, lhsn->nd_head); | |
} | |
} | |
static int | |
compile_massign_opt(rb_iseq_t *iseq, LINK_ANCHOR *ret, | |
NODE *rhsn, NODE *orig_lhsn) | |
{ | |
VALUE mem[64]; | |
const int memsize = numberof(mem); | |
int memindex = 0; | |
int llen = 0, rlen = 0; | |
int i; | |
NODE *lhsn = orig_lhsn; | |
#define MEMORY(v) { \ | |
int i; \ | |
if (memindex == memsize) return 0; \ | |
for (i=0; i<memindex; i++) { \ | |
if (mem[i] == (v)) return 0; \ | |
} \ | |
mem[memindex++] = (v); \ | |
} | |
if (rhsn == 0 || nd_type(rhsn) != NODE_ARRAY) { | |
return 0; | |
} | |
while (lhsn) { | |
NODE *ln = lhsn->nd_head; | |
switch (nd_type(ln)) { | |
case NODE_LASGN: | |
MEMORY(ln->nd_vid); | |
break; | |
case NODE_DASGN: | |
case NODE_DASGN_CURR: | |
case NODE_IASGN: | |
case NODE_IASGN2: | |
case NODE_CVASGN: | |
MEMORY(ln->nd_vid); | |
break; | |
default: | |
return 0; | |
} | |
lhsn = lhsn->nd_next; | |
llen++; | |
} | |
while (rhsn) { | |
if (llen <= rlen) { | |
COMPILE_POPED(ret, "masgn val (popped)", rhsn->nd_head); | |
} | |
else { | |
COMPILE(ret, "masgn val", rhsn->nd_head); | |
} | |
rhsn = rhsn->nd_next; | |
rlen++; | |
} | |
if (llen > rlen) { | |
for (i=0; i<llen-rlen; i++) { | |
ADD_INSN(ret, nd_line(orig_lhsn), putnil); | |
} | |
} | |
compile_massign_opt_lhs(iseq, ret, orig_lhsn); | |
return 1; | |
} | |
static int | |
compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *node, int poped) | |
{ | |
NODE *rhsn = node->nd_value; | |
NODE *splatn = node->nd_args; | |
NODE *lhsn = node->nd_head; | |
int lhs_splat = (splatn && (VALUE)splatn != (VALUE)-1) ? 1 : 0; | |
if (!poped || splatn || !compile_massign_opt(iseq, ret, rhsn, lhsn)) { | |
int llen = 0; | |
DECL_ANCHOR(lhsseq); | |
INIT_ANCHOR(lhsseq); | |
while (lhsn) { | |
compile_massign_lhs(iseq, lhsseq, lhsn->nd_head); | |
llen += 1; | |
lhsn = lhsn->nd_next; | |
} | |
COMPILE(ret, "normal masgn rhs", rhsn); | |
if (!poped) { | |
ADD_INSN(ret, nd_line(node), dup); | |
} | |
ADD_INSN2(ret, nd_line(node), expandarray, | |
INT2FIX(llen), INT2FIX(lhs_splat)); | |
ADD_SEQ(ret, lhsseq); | |
if (lhs_splat) { | |
if (nd_type(splatn) == NODE_POSTARG) { | |
/*a, b, *r, p1, p2 */ | |
NODE *postn = splatn->nd_2nd; | |
NODE *restn = splatn->nd_1st; | |
int num = (int)postn->nd_alen; | |
int flag = 0x02 | (((VALUE)restn == (VALUE)-1) ? 0x00 : 0x01); | |
ADD_INSN2(ret, nd_line(splatn), expandarray, | |
INT2FIX(num), INT2FIX(flag)); | |
if ((VALUE)restn != (VALUE)-1) { | |
compile_massign_lhs(iseq, ret, restn); | |
} | |
while (postn) { | |
compile_massign_lhs(iseq, ret, postn->nd_head); | |
postn = postn->nd_next; | |
} | |
} | |
else { | |
/* a, b, *r */ | |
compile_massign_lhs(iseq, ret, splatn); | |
} | |
} | |
} | |
return COMPILE_OK; | |
} | |
static int | |
compile_colon2(rb_iseq_t *iseq, NODE * node, | |
LINK_ANCHOR *pref, LINK_ANCHOR *body) | |
{ | |
switch (nd_type(node)) { | |
case NODE_CONST: | |
debugi("compile_colon2 - colon", node->nd_vid); | |
ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_vid)); | |
break; | |
case NODE_COLON3: | |
debugi("compile_colon2 - colon3", node->nd_mid); | |
ADD_INSN(body, nd_line(node), pop); | |
ADD_INSN1(body, nd_line(node), putobject, rb_cObject); | |
ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_mid)); | |
break; | |
case NODE_COLON2: | |
compile_colon2(iseq, node->nd_head, pref, body); | |
debugi("compile_colon2 - colon2", node->nd_mid); | |
ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_mid)); | |
break; | |
default: | |
COMPILE(pref, "const colon2 prefix", node); | |
break; | |
} | |
return COMPILE_OK; | |
} | |
static VALUE | |
compile_cpath(LINK_ANCHOR *ret, rb_iseq_t *iseq, NODE *cpath) | |
{ | |
if (nd_type(cpath) == NODE_COLON3) { | |
/* toplevel class ::Foo */ | |
ADD_INSN1(ret, nd_line(cpath), putobject, rb_cObject); | |
return Qfalse; | |
} | |
else if (cpath->nd_head) { | |
/* Bar::Foo */ | |
COMPILE(ret, "nd_else->nd_head", cpath->nd_head); | |
return Qfalse; | |
} | |
else { | |
/* class at cbase Foo */ | |
ADD_INSN1(ret, nd_line(cpath), putspecialobject, | |
INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); | |
return Qtrue; | |
} | |
} | |
#define private_recv_p(node) (nd_type((node)->nd_recv) == NODE_SELF) | |
#define defined_expr defined_expr0 | |
static int | |
defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *ret, | |
NODE *node, LABEL **lfinish, VALUE needstr) | |
{ | |
enum defined_type expr_type = 0; | |
enum node_type type; | |
switch (type = nd_type(node)) { | |
/* easy literals */ | |
case NODE_NIL: | |
expr_type = DEFINED_NIL; | |
break; | |
case NODE_SELF: | |
expr_type = DEFINED_SELF; | |
break; | |
case NODE_TRUE: | |
expr_type = DEFINED_TRUE; | |
break; | |
case NODE_FALSE: | |
expr_type = DEFINED_FALSE; | |
break; | |
case NODE_ARRAY:{ | |
NODE *vals = node; | |
do { | |
defined_expr(iseq, ret, vals->nd_head, lfinish, Qfalse); | |
if (!lfinish[1]) { | |
lfinish[1] = NEW_LABEL(nd_line(node)); | |
} | |
ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]); | |
} while ((vals = vals->nd_next) != NULL); | |
} | |
case NODE_STR: | |
case NODE_LIT: | |
case NODE_ZARRAY: | |
case NODE_AND: | |
case NODE_OR: | |
default: | |
expr_type = DEFINED_EXPR; | |
break; | |
/* variables */ | |
case NODE_LVAR: | |
case NODE_DVAR: | |
expr_type = DEFINED_LVAR; | |
break; | |
case NODE_IVAR: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_IVAR), | |
ID2SYM(node->nd_vid), needstr); | |
return 1; | |
case NODE_GVAR: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_GVAR), | |
ID2SYM(node->nd_entry->id), needstr); | |
return 1; | |
case NODE_CVAR: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CVAR), | |
ID2SYM(node->nd_vid), needstr); | |
return 1; | |
case NODE_CONST: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CONST), | |
ID2SYM(node->nd_vid), needstr); | |
return 1; | |
case NODE_COLON2: | |
if (!lfinish[1]) { | |
lfinish[1] = NEW_LABEL(nd_line(node)); | |
} | |
defined_expr(iseq, ret, node->nd_head, lfinish, Qfalse); | |
ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]); | |
if (rb_is_const_id(node->nd_mid)) { | |
COMPILE(ret, "defined/colon2#nd_head", node->nd_head); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CONST), | |
ID2SYM(node->nd_mid), needstr); | |
} | |
else { | |
COMPILE(ret, "defined/colon2#nd_head", node->nd_head); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_METHOD), | |
ID2SYM(node->nd_mid), needstr); | |
} | |
return 1; | |
case NODE_COLON3: | |
ADD_INSN1(ret, nd_line(node), putobject, rb_cObject); | |
ADD_INSN3(ret, nd_line(node), defined, | |
INT2FIX(DEFINED_CONST), ID2SYM(node->nd_mid), needstr); | |
return 1; | |
/* method dispatch */ | |
case NODE_CALL: | |
case NODE_VCALL: | |
case NODE_FCALL: | |
case NODE_ATTRASGN:{ | |
const int explicit_receiver = | |
(type == NODE_CALL || | |
(type == NODE_ATTRASGN && !private_recv_p(node))); | |
if (!lfinish[1]) { | |
lfinish[1] = NEW_LABEL(nd_line(node)); | |
} | |
if (node->nd_args) { | |
defined_expr(iseq, ret, node->nd_args, lfinish, Qfalse); | |
ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]); | |
} | |
if (explicit_receiver) { | |
defined_expr(iseq, ret, node->nd_recv, lfinish, Qfalse); | |
ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]); | |
COMPILE(ret, "defined/recv", node->nd_recv); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_METHOD), | |
ID2SYM(node->nd_mid), needstr); | |
} | |
else { | |
ADD_INSN(ret, nd_line(node), putself); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_FUNC), | |
ID2SYM(node->nd_mid), needstr); | |
} | |
return 1; | |
} | |
case NODE_YIELD: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_YIELD), 0, | |
needstr); | |
return 1; | |
case NODE_BACK_REF: | |
case NODE_NTH_REF: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_REF), | |
INT2FIX((node->nd_nth << 1) | (type == NODE_BACK_REF)), | |
needstr); | |
return 1; | |
case NODE_SUPER: | |
case NODE_ZSUPER: | |
ADD_INSN(ret, nd_line(node), putnil); | |
ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_ZSUPER), 0, | |
needstr); | |
return 1; | |
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: | |
expr_type = DEFINED_ASGN; | |
break; | |
} | |
if (expr_type) { | |
if (needstr != Qfalse) { | |
VALUE str = rb_iseq_defined_string(expr_type); | |
ADD_INSN1(ret, nd_line(node), putobject, str); | |
} | |
else { | |
ADD_INSN1(ret, nd_line(node), putobject, Qtrue); | |
} | |
return 1; | |
} | |
return 0; | |
} | |
#undef defined_expr | |
static int | |
defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *ret, | |
NODE *node, LABEL **lfinish, VALUE needstr) | |
{ | |
LINK_ELEMENT *lcur = ret->last; | |
int done = defined_expr0(iseq, ret, node, lfinish, needstr); | |
if (lfinish[1]) { | |
int line = nd_line(node); | |
LABEL *lstart = NEW_LABEL(line); | |
LABEL *lend = NEW_LABEL(line); | |
VALUE rescue = NEW_CHILD_ISEQVAL(NEW_NIL(), | |
rb_str_concat(rb_str_new2 | |
("defined guard in "), | |
iseq->location.label), | |
ISEQ_TYPE_DEFINED_GUARD, 0); | |
APPEND_LABEL(ret, lcur, lstart); | |
ADD_LABEL(ret, lend); | |
ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]); | |
} | |
return done; | |
} | |
static VALUE | |
make_name_for_block(rb_iseq_t *iseq) | |
{ | |
int level = 1; | |
rb_iseq_t *ip = iseq; | |
if (iseq->parent_iseq != 0) { | |
while (ip->local_iseq != ip) { | |
if (ip->type == ISEQ_TYPE_BLOCK) { | |
level++; | |
} | |
ip = ip->parent_iseq; | |
} | |
} | |
if (level == 1) { | |
return rb_sprintf("block in %"PRIsVALUE, ip->location.label); | |
} | |
else { | |
return rb_sprintf("block (%d levels) in %"PRIsVALUE, level, ip->location.label); | |
} | |
} | |
static void | |
push_ensure_entry(rb_iseq_t *iseq, | |
struct iseq_compile_data_ensure_node_stack *enl, | |
struct ensure_range *er, NODE *node) | |
{ | |
enl->ensure_node = node; | |
enl->prev = iseq->compile_data->ensure_node_stack; /* prev */ | |
enl->erange = er; | |
iseq->compile_data->ensure_node_stack = enl; | |
} | |
static void | |
add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange, | |
LABEL *lstart, LABEL *lend) | |
{ | |
struct ensure_range *ne = | |
compile_data_alloc(iseq, sizeof(struct ensure_range)); | |
while (erange->next != 0) { | |
erange = erange->next; | |
} | |
ne->next = 0; | |
ne->begin = lend; | |
ne->end = erange->end; | |
erange->end = lstart; | |
erange->next = ne; | |
} | |
static void | |
add_ensure_iseq(LINK_ANCHOR *ret, rb_iseq_t *iseq, int is_return) | |
{ | |
struct iseq_compile_data_ensure_node_stack *enlp = | |
iseq->compile_data->ensure_node_stack; | |
struct iseq_compile_data_ensure_node_stack *prev_enlp = enlp; | |
DECL_ANCHOR(ensure); | |
INIT_ANCHOR(ensure); | |
while (enlp) { | |
if (enlp->erange != 0) { | |
DECL_ANCHOR(ensure_part); | |
LABEL *lstart = NEW_LABEL(0); | |
LABEL *lend = NEW_LABEL(0); | |
INIT_ANCHOR(ensure_part); | |
add_ensure_range(iseq, enlp->erange, lstart, lend); | |
iseq->compile_data->ensure_node_stack = enlp->prev; | |
ADD_LABEL(ensure_part, lstart); | |
COMPILE_POPED(ensure_part, "ensure part", enlp->ensure_node); | |
ADD_LABEL(ensure_part, lend); | |
ADD_SEQ(ensure, ensure_part); | |
} | |
else { | |
if (!is_return) { | |
break; | |
} | |
} | |
enlp = enlp->prev; | |
} | |
iseq->compile_data->ensure_node_stack = prev_enlp; | |
ADD_SEQ(ret, ensure); | |
} | |
static VALUE | |
setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag, rb_call_info_kw_arg_t **keywords) | |
{ | |
VALUE argc = INT2FIX(0); | |
int nsplat = 0; | |
DECL_ANCHOR(arg_block); | |
DECL_ANCHOR(args_splat); | |
INIT_ANCHOR(arg_block); | |
INIT_ANCHOR(args_splat); | |
if (argn && nd_type(argn) == NODE_BLOCK_PASS) { | |
COMPILE(arg_block, "block", argn->nd_body); | |
*flag |= VM_CALL_ARGS_BLOCKARG; | |
argn = argn->nd_head; | |
} | |
setup_argn: | |
if (argn) { | |
switch (nd_type(argn)) { | |
case NODE_SPLAT: { | |
COMPILE(args, "args (splat)", argn->nd_head); | |
ADD_INSN1(args, nd_line(argn), splatarray, Qfalse); | |
argc = INT2FIX(1); | |
nsplat++; | |
*flag |= VM_CALL_ARGS_SPLAT; | |
break; | |
} | |
case NODE_ARGSCAT: | |
case NODE_ARGSPUSH: { | |
int next_is_array = (nd_type(argn->nd_head) == NODE_ARRAY); | |
DECL_ANCHOR(tmp); | |
INIT_ANCHOR(tmp); | |
COMPILE(tmp, "args (cat: splat)", argn->nd_body); | |
if (nd_type(argn) == NODE_ARGSCAT) { | |
ADD_INSN1(tmp, nd_line(argn), splatarray, Qfalse); | |
} | |
else { | |
ADD_INSN1(tmp, nd_line(argn), newarray, INT2FIX(1)); | |
} | |
INSERT_LIST(args_splat, tmp); | |
nsplat++; | |
*flag |= VM_CALL_ARGS_SPLAT; | |
if (next_is_array) { | |
argc = INT2FIX(compile_array(iseq, args, argn->nd_head, COMPILE_ARRAY_TYPE_ARGS) + 1); | |
} | |
else { | |
argn = argn->nd_head; | |
goto setup_argn; | |
} | |
break; | |
} | |
case NODE_ARRAY: | |
{ | |
argc = INT2FIX(compile_array_(iseq, args, argn, COMPILE_ARRAY_TYPE_ARGS, keywords, FALSE)); | |
break; | |
} | |
default: { | |
rb_bug("setup_arg: unknown node: %s\n", ruby_node_name(nd_type(argn))); | |
} | |
} | |
} | |
if (nsplat > 1) { | |
int i; | |
for (i=1; i<nsplat; i++) { | |
ADD_INSN(args_splat, nd_line(args), concatarray); | |
} | |
} | |
if (!LIST_SIZE_ZERO(args_splat)) { | |
ADD_SEQ(args, args_splat); | |
} | |
if (*flag & VM_CALL_ARGS_BLOCKARG) { | |
ADD_SEQ(args, arg_block); | |
} | |
return argc; | |
} | |
static VALUE | |
build_postexe_iseq(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *body) | |
{ | |
int line = nd_line(body); | |
VALUE argc = INT2FIX(0); | |
VALUE block = NEW_CHILD_ISEQVAL(body, make_name_for_block(iseq->parent_iseq), ISEQ_TYPE_BLOCK, line); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_CALL_WITH_BLOCK(ret, line, id_core_set_postexe, argc, block); | |
iseq_set_local_table(iseq, 0); | |
return Qnil; | |
} | |
/** | |
compile each node | |
self: InstructionSequence | |
node: Ruby compiled node | |
poped: This node will be poped | |
*/ | |
static int | |
iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) | |
{ | |
enum node_type type; | |
LINK_ELEMENT *saved_last_element = 0; | |
int line; | |
if (node == 0) { | |
if (!poped) { | |
debugs("node: NODE_NIL(implicit)\n"); | |
ADD_INSN(ret, iseq->compile_data->last_line, putnil); | |
} | |
return COMPILE_OK; | |
} | |
line = (int)nd_line(node); | |
if (iseq->compile_data->last_line == line) { | |
/* ignore */ | |
} | |
else { | |
if (node->flags & NODE_FL_NEWLINE) { | |
iseq->compile_data->last_line = line; | |
ADD_TRACE(ret, line, RUBY_EVENT_LINE); | |
saved_last_element = ret->last; | |
} | |
} | |
debug_node_start(node); | |
type = nd_type(node); | |
switch (type) { | |
case NODE_BLOCK:{ | |
while (node && nd_type(node) == NODE_BLOCK) { | |
COMPILE_(ret, "BLOCK body", node->nd_head, | |
(node->nd_next == 0 && poped == 0) ? 0 : 1); | |
node = node->nd_next; | |
} | |
if (node) { | |
COMPILE_(ret, "BLOCK next", node->nd_next, poped); | |
} | |
break; | |
} | |
case NODE_IF:{ | |
DECL_ANCHOR(cond_seq); | |
DECL_ANCHOR(then_seq); | |
DECL_ANCHOR(else_seq); | |
LABEL *then_label, *else_label, *end_label; | |
INIT_ANCHOR(cond_seq); | |
INIT_ANCHOR(then_seq); | |
INIT_ANCHOR(else_seq); | |
then_label = NEW_LABEL(line); | |
else_label = NEW_LABEL(line); | |
end_label = NEW_LABEL(line); | |
compile_branch_condition(iseq, cond_seq, node->nd_cond, | |
then_label, else_label); | |
COMPILE_(then_seq, "then", node->nd_body, poped); | |
COMPILE_(else_seq, "else", node->nd_else, poped); | |
ADD_SEQ(ret, cond_seq); | |
ADD_LABEL(ret, then_label); | |
ADD_SEQ(ret, then_seq); | |
ADD_INSNL(ret, line, jump, end_label); | |
ADD_LABEL(ret, else_label); | |
ADD_SEQ(ret, else_seq); | |
ADD_LABEL(ret, end_label); | |
break; | |
} | |
case NODE_CASE:{ | |
NODE *vals; | |
NODE *tempnode = node; | |
LABEL *endlabel, *elselabel; | |
DECL_ANCHOR(head); | |
DECL_ANCHOR(body_seq); | |
DECL_ANCHOR(cond_seq); | |
int only_special_literals = 1; | |
VALUE literals = rb_hash_new(); | |
INIT_ANCHOR(head); | |
INIT_ANCHOR(body_seq); | |
INIT_ANCHOR(cond_seq); | |
rb_hash_tbl_raw(literals)->type = &cdhash_type; | |
if (node->nd_head == 0) { | |
COMPILE_(ret, "when", node->nd_body, poped); | |
break; | |
} | |
COMPILE(head, "case base", node->nd_head); | |
node = node->nd_body; | |
type = nd_type(node); | |
line = nd_line(node); | |
if (type != NODE_WHEN) { | |
COMPILE_ERROR((ERROR_ARGS "NODE_CASE: unexpected node. must be NODE_WHEN, but %s", ruby_node_name(type))); | |
} | |
endlabel = NEW_LABEL(line); | |
elselabel = NEW_LABEL(line); | |
ADD_SEQ(ret, head); /* case VAL */ | |
while (type == NODE_WHEN) { | |
LABEL *l1; | |
l1 = NEW_LABEL(line); | |
ADD_LABEL(body_seq, l1); | |
ADD_INSN(body_seq, line, pop); | |
COMPILE_(body_seq, "when body", node->nd_body, poped); | |
ADD_INSNL(body_seq, line, jump, endlabel); | |
vals = node->nd_head; | |
if (vals) { | |
switch (nd_type(vals)) { | |
case NODE_ARRAY: | |
only_special_literals = when_vals(iseq, cond_seq, vals, l1, only_special_literals, literals); | |
break; | |
case NODE_SPLAT: | |
case NODE_ARGSCAT: | |
case NODE_ARGSPUSH: | |
only_special_literals = 0; | |
ADD_INSN (cond_seq, nd_line(vals), dup); | |
COMPILE(cond_seq, "when/cond splat", vals); | |
ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY)); | |
ADD_INSNL(cond_seq, nd_line(vals), branchif, l1); | |
break; | |
default: | |
rb_bug("NODE_CASE: unknown node (%s)", | |
ruby_node_name(nd_type(vals))); | |
} | |
} | |
else { | |
rb_bug("NODE_CASE: must be NODE_ARRAY, but 0"); | |
} | |
node = node->nd_next; | |
if (!node) { | |
break; | |
} | |
type = nd_type(node); | |
line = nd_line(node); | |
} | |
/* else */ | |
if (node) { | |
ADD_LABEL(cond_seq, elselabel); | |
ADD_INSN(cond_seq, line, pop); | |
COMPILE_(cond_seq, "else", node, poped); | |
ADD_INSNL(cond_seq, line, jump, endlabel); | |
} | |
else { | |
debugs("== else (implicit)\n"); | |
ADD_LABEL(cond_seq, elselabel); | |
ADD_INSN(cond_seq, nd_line(tempnode), pop); | |
if (!poped) { | |
ADD_INSN(cond_seq, nd_line(tempnode), putnil); | |
} | |
ADD_INSNL(cond_seq, nd_line(tempnode), jump, endlabel); | |
} | |
if (only_special_literals) { | |
iseq_add_mark_object(iseq, literals); | |
ADD_INSN(ret, nd_line(tempnode), dup); | |
ADD_INSN2(ret, nd_line(tempnode), opt_case_dispatch, literals, elselabel); | |
} | |
ADD_SEQ(ret, cond_seq); | |
ADD_SEQ(ret, body_seq); | |
ADD_LABEL(ret, endlabel); | |
break; | |
} | |
case NODE_WHEN:{ | |
NODE *vals; | |
NODE *val; | |
NODE *orig_node = node; | |
LABEL *endlabel; | |
DECL_ANCHOR(body_seq); | |
INIT_ANCHOR(body_seq); | |
endlabel = NEW_LABEL(line); | |
while (node && nd_type(node) == NODE_WHEN) { | |
LABEL *l1 = NEW_LABEL(line = nd_line(node)); | |
ADD_LABEL(body_seq, l1); | |
COMPILE_(body_seq, "when", node->nd_body, poped); | |
ADD_INSNL(body_seq, line, jump, endlabel); | |
vals = node->nd_head; | |
if (!vals) { | |
rb_bug("NODE_WHEN: must be NODE_ARRAY, but 0"); | |
} | |
switch (nd_type(vals)) { | |
case NODE_ARRAY: | |
while (vals) { | |
val = vals->nd_head; | |
COMPILE(ret, "when2", val); | |
ADD_INSNL(ret, nd_line(val), branchif, l1); | |
vals = vals->nd_next; | |
} | |
break; | |
case NODE_SPLAT: | |
case NODE_ARGSCAT: | |
case NODE_ARGSPUSH: | |
ADD_INSN(ret, nd_line(vals), putnil); | |
COMPILE(ret, "when2/cond splat", vals); | |
ADD_INSN1(ret, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY)); | |
ADD_INSNL(ret, nd_line(vals), branchif, l1); | |
break; | |
default: | |
rb_bug("NODE_WHEN: unknown node (%s)", | |
ruby_node_name(nd_type(vals))); | |
} | |
node = node->nd_next; | |
} | |
/* else */ | |
COMPILE_(ret, "else", node, poped); | |
ADD_INSNL(ret, nd_line(orig_node), jump, endlabel); | |
ADD_SEQ(ret, body_seq); | |
ADD_LABEL(ret, endlabel); | |
break; | |
} | |
case NODE_OPT_N: | |
case NODE_WHILE: | |
case NODE_UNTIL:{ | |
LABEL *prev_start_label = iseq->compile_data->start_label; | |
LABEL *prev_end_label = iseq->compile_data->end_label; | |
LABEL *prev_redo_label = iseq->compile_data->redo_label; | |
int prev_loopval_popped = iseq->compile_data->loopval_popped; | |
struct iseq_compile_data_ensure_node_stack enl; | |
LABEL *next_label = iseq->compile_data->start_label = NEW_LABEL(line); /* next */ | |
LABEL *redo_label = iseq->compile_data->redo_label = NEW_LABEL(line); /* redo */ | |
LABEL *break_label = iseq->compile_data->end_label = NEW_LABEL(line); /* break */ | |
LABEL *end_label = NEW_LABEL(line); | |
LABEL *next_catch_label = NEW_LABEL(line); | |
LABEL *tmp_label = NULL; | |
iseq->compile_data->loopval_popped = 0; | |
push_ensure_entry(iseq, &enl, 0, 0); | |
if (type == NODE_OPT_N || node->nd_state == 1) { | |
ADD_INSNL(ret, line, jump, next_label); | |
} | |
else { | |
tmp_label = NEW_LABEL(line); | |
ADD_INSNL(ret, line, jump, tmp_label); | |
} | |
ADD_INSN(ret, line, putnil); | |
ADD_LABEL(ret, next_catch_label); | |
ADD_INSN(ret, line, pop); | |
ADD_INSNL(ret, line, jump, next_label); | |
if (tmp_label) ADD_LABEL(ret, tmp_label); | |
ADD_LABEL(ret, redo_label); | |
COMPILE_POPED(ret, "while body", node->nd_body); | |
ADD_LABEL(ret, next_label); /* next */ | |
if (type == NODE_WHILE) { | |
compile_branch_condition(iseq, ret, node->nd_cond, | |
redo_label, end_label); | |
} | |
else if (type == NODE_UNTIL) { | |
/* until */ | |
compile_branch_condition(iseq, ret, node->nd_cond, | |
end_label, redo_label); | |
} | |
else { | |
ADD_CALL_RECEIVER(ret, line); | |
ADD_CALL(ret, line, idGets, INT2FIX(0)); | |
ADD_INSNL(ret, line, branchif, redo_label); | |
/* opt_n */ | |
} | |
ADD_LABEL(ret, end_label); | |
if (node->nd_state == Qundef) { | |
/* ADD_INSN(ret, line, putundef); */ | |
rb_bug("unsupported: putundef"); | |
} | |
else { | |
ADD_INSN(ret, line, putnil); | |
} | |
ADD_LABEL(ret, break_label); /* break */ | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label, | |
0, break_label); | |
ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, redo_label, break_label, 0, | |
next_catch_label); | |
ADD_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, 0, | |
iseq->compile_data->redo_label); | |
iseq->compile_data->start_label = prev_start_label; | |
iseq->compile_data->end_label = prev_end_label; | |
iseq->compile_data->redo_label = prev_redo_label; | |
iseq->compile_data->loopval_popped = prev_loopval_popped; | |
iseq->compile_data->ensure_node_stack = iseq->compile_data->ensure_node_stack->prev; | |
break; | |
} | |
case NODE_ITER: | |
case NODE_FOR:{ | |
VALUE prevblock = iseq->compile_data->current_block; | |
LABEL *retry_label = NEW_LABEL(line); | |
LABEL *retry_end_l = NEW_LABEL(line); | |
ADD_LABEL(ret, retry_label); | |
if (nd_type(node) == NODE_FOR) { | |
COMPILE(ret, "iter caller (for)", node->nd_iter); | |
iseq->compile_data->current_block = | |
NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq), | |
ISEQ_TYPE_BLOCK, line); | |
ADD_SEND_WITH_BLOCK(ret, line, idEach, INT2FIX(0), iseq->compile_data->current_block); | |
} | |
else { | |
iseq->compile_data->current_block = | |
NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq), | |
ISEQ_TYPE_BLOCK, line); | |
COMPILE(ret, "iter caller", node->nd_iter); | |
} | |
ADD_LABEL(ret, retry_end_l); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
iseq->compile_data->current_block = prevblock; | |
ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, 0, retry_end_l); | |
break; | |
} | |
case NODE_BREAK:{ | |
unsigned long level = 0; | |
if (iseq->compile_data->redo_label != 0) { | |
/* while/until */ | |
LABEL *splabel = NEW_LABEL(0); | |
ADD_LABEL(ret, splabel); | |
ADD_ADJUST(ret, line, iseq->compile_data->redo_label); | |
COMPILE_(ret, "break val (while/until)", node->nd_stts, iseq->compile_data->loopval_popped); | |
add_ensure_iseq(ret, iseq, 0); | |
ADD_INSNL(ret, line, jump, iseq->compile_data->end_label); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else if (iseq->type == ISEQ_TYPE_BLOCK) { | |
break_by_insn: | |
/* escape from block */ | |
COMPILE(ret, "break val (block)", node->nd_stts); | |
ADD_INSN1(ret, line, throw, INT2FIX(level | 0x02) /* TAG_BREAK */ ); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
else if (iseq->type == ISEQ_TYPE_EVAL) { | |
break_in_eval: | |
COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with break")); | |
} | |
else { | |
rb_iseq_t *ip = iseq->parent_iseq; | |
while (ip) { | |
if (!ip->compile_data) { | |
ip = 0; | |
break; | |
} | |
level++; | |
if (ip->compile_data->redo_label != 0) { | |
level = 0x8000; | |
if (ip->compile_data->loopval_popped == 0) { | |
/* need value */ | |
level |= 0x4000; | |
} | |
goto break_by_insn; | |
} | |
else if (ip->type == ISEQ_TYPE_BLOCK) { | |
level <<= 16; | |
goto break_by_insn; | |
} | |
else if (ip->type == ISEQ_TYPE_EVAL) { | |
goto break_in_eval; | |
} | |
ip = ip->parent_iseq; | |
} | |
COMPILE_ERROR((ERROR_ARGS "Invalid break")); | |
} | |
break; | |
} | |
case NODE_NEXT:{ | |
unsigned long level = 0; | |
if (iseq->compile_data->redo_label != 0) { | |
LABEL *splabel = NEW_LABEL(0); | |
debugs("next in while loop\n"); | |
ADD_LABEL(ret, splabel); | |
COMPILE(ret, "next val/valid syntax?", node->nd_stts); | |
add_ensure_iseq(ret, iseq, 0); | |
ADD_ADJUST(ret, line, iseq->compile_data->redo_label); | |
ADD_INSNL(ret, line, jump, iseq->compile_data->start_label); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else if (iseq->compile_data->end_label) { | |
LABEL *splabel = NEW_LABEL(0); | |
debugs("next in block\n"); | |
ADD_LABEL(ret, splabel); | |
ADD_ADJUST(ret, line, iseq->compile_data->start_label); | |
COMPILE(ret, "next val", node->nd_stts); | |
add_ensure_iseq(ret, iseq, 0); | |
ADD_INSNL(ret, line, jump, iseq->compile_data->end_label); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else if (iseq->type == ISEQ_TYPE_EVAL) { | |
next_in_eval: | |
COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with next")); | |
} | |
else { | |
rb_iseq_t *ip; | |
ip = iseq; | |
while (ip) { | |
if (!ip->compile_data) { | |
ip = 0; | |
break; | |
} | |
level = 0x8000 | 0x4000; | |
if (ip->compile_data->redo_label != 0) { | |
/* while loop */ | |
break; | |
} | |
else if (ip->type == ISEQ_TYPE_BLOCK) { | |
break; | |
} | |
else if (ip->type == ISEQ_TYPE_EVAL) { | |
goto next_in_eval; | |
} | |
ip = ip->parent_iseq; | |
} | |
if (ip != 0) { | |
COMPILE(ret, "next val", node->nd_stts); | |
ADD_INSN1(ret, line, throw, INT2FIX(level | 0x03) /* TAG_NEXT */ ); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
else { | |
COMPILE_ERROR((ERROR_ARGS "Invalid next")); | |
} | |
} | |
break; | |
} | |
case NODE_REDO:{ | |
if (iseq->compile_data->redo_label) { | |
LABEL *splabel = NEW_LABEL(0); | |
debugs("redo in while"); | |
ADD_LABEL(ret, splabel); | |
ADD_ADJUST(ret, line, iseq->compile_data->redo_label); | |
add_ensure_iseq(ret, iseq, 0); | |
ADD_INSNL(ret, line, jump, iseq->compile_data->redo_label); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else if (iseq->type == ISEQ_TYPE_EVAL) { | |
redo_in_eval: | |
COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with redo")); | |
} | |
else if (iseq->compile_data->start_label) { | |
LABEL *splabel = NEW_LABEL(0); | |
debugs("redo in block"); | |
ADD_LABEL(ret, splabel); | |
add_ensure_iseq(ret, iseq, 0); | |
ADD_ADJUST(ret, line, iseq->compile_data->start_label); | |
ADD_INSNL(ret, line, jump, iseq->compile_data->start_label); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else { | |
rb_iseq_t *ip; | |
unsigned long level; | |
level = 0x8000 | 0x4000; | |
ip = iseq; | |
while (ip) { | |
if (!ip->compile_data) { | |
ip = 0; | |
break; | |
} | |
if (ip->compile_data->redo_label != 0) { | |
break; | |
} | |
else if (ip->type == ISEQ_TYPE_BLOCK) { | |
break; | |
} | |
else if (ip->type == ISEQ_TYPE_EVAL) { | |
goto redo_in_eval; | |
} | |
ip = ip->parent_iseq; | |
} | |
if (ip != 0) { | |
ADD_INSN(ret, line, putnil); | |
ADD_INSN1(ret, line, throw, INT2FIX(level | 0x05) /* TAG_REDO */ ); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
else { | |
COMPILE_ERROR((ERROR_ARGS "Invalid redo")); | |
} | |
} | |
break; | |
} | |
case NODE_RETRY:{ | |
if (iseq->type == ISEQ_TYPE_RESCUE) { | |
ADD_INSN(ret, line, putnil); | |
ADD_INSN1(ret, line, throw, INT2FIX(0x04) /* TAG_RETRY */ ); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
else { | |
COMPILE_ERROR((ERROR_ARGS "Invalid retry")); | |
} | |
break; | |
} | |
case NODE_BEGIN:{ | |
COMPILE_(ret, "NODE_BEGIN", node->nd_body, poped); | |
break; | |
} | |
case NODE_RESCUE:{ | |
LABEL *lstart = NEW_LABEL(line); | |
LABEL *lend = NEW_LABEL(line); | |
LABEL *lcont = NEW_LABEL(line); | |
VALUE rescue = NEW_CHILD_ISEQVAL( | |
node->nd_resq, | |
rb_str_concat(rb_str_new2("rescue in "), iseq->location.label), | |
ISEQ_TYPE_RESCUE, line); | |
ADD_LABEL(ret, lstart); | |
COMPILE(ret, "rescue head", node->nd_head); | |
ADD_LABEL(ret, lend); | |
if (node->nd_else) { | |
ADD_INSN(ret, line, pop); | |
COMPILE(ret, "rescue else", node->nd_else); | |
} | |
ADD_INSN(ret, line, nop); | |
ADD_LABEL(ret, lcont); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
/* register catch entry */ | |
ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lcont); | |
ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, 0, lstart); | |
break; | |
} | |
case NODE_RESBODY:{ | |
NODE *resq = node; | |
NODE *narg; | |
LABEL *label_miss, *label_hit; | |
while (resq) { | |
label_miss = NEW_LABEL(line); | |
label_hit = NEW_LABEL(line); | |
narg = resq->nd_args; | |
if (narg) { | |
switch (nd_type(narg)) { | |
case NODE_ARRAY: | |
while (narg) { | |
ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0)); | |
COMPILE(ret, "rescue arg", narg->nd_head); | |
ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); | |
ADD_INSNL(ret, line, branchif, label_hit); | |
narg = narg->nd_next; | |
} | |
break; | |
case NODE_SPLAT: | |
case NODE_ARGSCAT: | |
case NODE_ARGSPUSH: | |
ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0)); | |
COMPILE(ret, "rescue/cond splat", narg); | |
ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE | VM_CHECKMATCH_ARRAY)); | |
ADD_INSNL(ret, line, branchif, label_hit); | |
break; | |
default: | |
rb_bug("NODE_RESBODY: unknown node (%s)", | |
ruby_node_name(nd_type(narg))); | |
} | |
} | |
else { | |
ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0)); | |
ADD_INSN1(ret, line, putobject, rb_eStandardError); | |
ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE)); | |
ADD_INSNL(ret, line, branchif, label_hit); | |
} | |
ADD_INSNL(ret, line, jump, label_miss); | |
ADD_LABEL(ret, label_hit); | |
COMPILE(ret, "resbody body", resq->nd_body); | |
if (iseq->compile_data->option->tailcall_optimization) { | |
ADD_INSN(ret, line, nop); | |
} | |
ADD_INSN(ret, line, leave); | |
ADD_LABEL(ret, label_miss); | |
resq = resq->nd_head; | |
} | |
break; | |
} | |
case NODE_ENSURE:{ | |
DECL_ANCHOR(ensr); | |
VALUE ensure = NEW_CHILD_ISEQVAL(node->nd_ensr, | |
rb_str_concat(rb_str_new2 | |
("ensure in "), | |
iseq->location.label), | |
ISEQ_TYPE_ENSURE, line); | |
LABEL *lstart = NEW_LABEL(line); | |
LABEL *lend = NEW_LABEL(line); | |
LABEL *lcont = NEW_LABEL(line); | |
struct ensure_range er; | |
struct iseq_compile_data_ensure_node_stack enl; | |
struct ensure_range *erange; | |
INIT_ANCHOR(ensr); | |
COMPILE_POPED(ensr, "ensure ensr", node->nd_ensr); | |
er.begin = lstart; | |
er.end = lend; | |
er.next = 0; | |
push_ensure_entry(iseq, &enl, &er, node->nd_ensr); | |
ADD_LABEL(ret, lstart); | |
COMPILE_(ret, "ensure head", node->nd_head, poped); | |
ADD_LABEL(ret, lend); | |
if (ensr->anchor.next == 0) { | |
ADD_INSN(ret, line, nop); | |
} | |
else { | |
ADD_SEQ(ret, ensr); | |
} | |
ADD_LABEL(ret, lcont); | |
erange = iseq->compile_data->ensure_node_stack->erange; | |
while (erange) { | |
ADD_CATCH_ENTRY(CATCH_TYPE_ENSURE, erange->begin, erange->end, | |
ensure, lcont); | |
erange = erange->next; | |
} | |
iseq->compile_data->ensure_node_stack = enl.prev; | |
break; | |
} | |
case NODE_AND: | |
case NODE_OR:{ | |
LABEL *end_label = NEW_LABEL(line); | |
COMPILE(ret, "nd_1st", node->nd_1st); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
if (type == NODE_AND) { | |
ADD_INSNL(ret, line, branchunless, end_label); | |
} | |
else { | |
ADD_INSNL(ret, line, branchif, end_label); | |
} | |
if (!poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
COMPILE_(ret, "nd_2nd", node->nd_2nd, poped); | |
ADD_LABEL(ret, end_label); | |
break; | |
} | |
case NODE_MASGN:{ | |
compile_massign(iseq, ret, node, poped); | |
break; | |
} | |
case NODE_LASGN:{ | |
ID id = node->nd_vid; | |
int idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id); | |
debugs("lvar: %"PRIsVALUE" idx: %d\n", rb_id2str(id), idx); | |
COMPILE(ret, "rvalue", node->nd_value); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
ADD_INSN2(ret, line, setlocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq))); | |
break; | |
} | |
case NODE_DASGN: | |
case NODE_DASGN_CURR:{ | |
int idx, lv, ls; | |
COMPILE(ret, "dvalue", node->nd_value); | |
debugi("dassn id", rb_id2str(node->nd_vid) ? node->nd_vid : '*'); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
idx = get_dyna_var_idx(iseq, node->nd_vid, &lv, &ls); | |
if (idx < 0) { | |
rb_bug("NODE_DASGN(_CURR): unknown id (%"PRIsVALUE")", rb_id2str(node->nd_vid)); | |
} | |
ADD_INSN2(ret, line, setlocal, INT2FIX(ls - idx), INT2FIX(lv)); | |
break; | |
} | |
case NODE_GASGN:{ | |
COMPILE(ret, "lvalue", node->nd_value); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
ADD_INSN1(ret, line, setglobal, | |
((VALUE)node->nd_entry | 1)); | |
break; | |
} | |
case NODE_IASGN: | |
case NODE_IASGN2:{ | |
COMPILE(ret, "lvalue", node->nd_value); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
ADD_INSN2(ret, line, setinstancevariable, | |
ID2SYM(node->nd_vid), INT2FIX(iseq->is_size++)); | |
break; | |
} | |
case NODE_CDECL:{ | |
COMPILE(ret, "lvalue", node->nd_value); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
if (node->nd_vid) { | |
ADD_INSN1(ret, line, putspecialobject, | |
INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE)); | |
ADD_INSN1(ret, line, setconstant, ID2SYM(node->nd_vid)); | |
} | |
else { | |
compile_cpath(ret, iseq, node->nd_else); | |
ADD_INSN1(ret, line, setconstant, ID2SYM(node->nd_else->nd_mid)); | |
} | |
break; | |
} | |
case NODE_CVASGN:{ | |
COMPILE(ret, "cvasgn val", node->nd_value); | |
if (!poped) { | |
ADD_INSN(ret, line, dup); | |
} | |
ADD_INSN1(ret, line, setclassvariable, | |
ID2SYM(node->nd_vid)); | |
break; | |
} | |
case NODE_OP_ASGN1: { | |
DECL_ANCHOR(args); | |
VALUE argc; | |
unsigned int flag = 0; | |
unsigned int asgnflag = 0; | |
ID id = node->nd_mid; | |
int boff = 0; | |
/* | |
* a[x] (op)= y | |
* | |
* nil # nil | |
* eval a # nil a | |
* eval x # nil a x | |
* dupn 2 # nil a x a x | |
* send :[] # nil a x a[x] | |
* eval y # nil a x a[x] y | |
* send op # nil a x ret | |
* setn 3 # ret a x ret | |
* send []= # ret ? | |
* pop # ret | |
*/ | |
/* | |
* nd_recv[nd_args->nd_body] (nd_mid)= nd_args->nd_head; | |
* NODE_OP_ASGN nd_recv | |
* nd_args->nd_head | |
* nd_args->nd_body | |
* nd_mid | |
*/ | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN1 recv", node); | |
switch (nd_type(node->nd_args->nd_head)) { | |
case NODE_ZARRAY: | |
argc = INT2FIX(0); | |
break; | |
case NODE_BLOCK_PASS: | |
boff = 1; | |
default: | |
INIT_ANCHOR(args); | |
argc = setup_args(iseq, args, node->nd_args->nd_head, &flag, NULL); | |
ADD_SEQ(ret, args); | |
} | |
ADD_INSN1(ret, line, dupn, FIXNUM_INC(argc, 1 + boff)); | |
flag |= asgnflag; | |
ADD_SEND_WITH_FLAG(ret, line, idAREF, argc, INT2FIX(flag)); | |
if (id == 0 || id == 1) { | |
/* 0: or, 1: and | |
a[x] ||= y | |
unless/if a[x] | |
a[x]= y | |
else | |
nil | |
end | |
*/ | |
LABEL *label = NEW_LABEL(line); | |
LABEL *lfin = NEW_LABEL(line); | |
ADD_INSN(ret, line, dup); | |
if (id == 0) { | |
/* or */ | |
ADD_INSNL(ret, line, branchif, label); | |
} | |
else { | |
/* and */ | |
ADD_INSNL(ret, line, branchunless, label); | |
} | |
ADD_INSN(ret, line, pop); | |
COMPILE(ret, "NODE_OP_ASGN1 args->body: ", node->nd_args->nd_body); | |
if (!poped) { | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff)); | |
} | |
if (flag & VM_CALL_ARGS_SPLAT) { | |
ADD_INSN1(ret, line, newarray, INT2FIX(1)); | |
if (boff > 0) { | |
ADD_INSN1(ret, line, dupn, INT2FIX(3)); | |
ADD_INSN(ret, line, swap); | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_INSN(ret, line, concatarray); | |
if (boff > 0) { | |
ADD_INSN1(ret, line, setn, INT2FIX(3)); | |
ADD_INSN(ret, line, pop); | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag)); | |
} | |
else { | |
if (boff > 0) | |
ADD_INSN(ret, line, swap); | |
ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); | |
} | |
ADD_INSN(ret, line, pop); | |
ADD_INSNL(ret, line, jump, lfin); | |
ADD_LABEL(ret, label); | |
if (!poped) { | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff)); | |
} | |
ADD_INSN1(ret, line, adjuststack, FIXNUM_INC(argc, 2+boff)); | |
ADD_LABEL(ret, lfin); | |
} | |
else { | |
COMPILE(ret, "NODE_OP_ASGN1 args->body: ", node->nd_args->nd_body); | |
ADD_SEND(ret, line, id, INT2FIX(1)); | |
if (!poped) { | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff)); | |
} | |
if (flag & VM_CALL_ARGS_SPLAT) { | |
ADD_INSN1(ret, line, newarray, INT2FIX(1)); | |
if (boff > 0) { | |
ADD_INSN1(ret, line, dupn, INT2FIX(3)); | |
ADD_INSN(ret, line, swap); | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_INSN(ret, line, concatarray); | |
if (boff > 0) { | |
ADD_INSN1(ret, line, setn, INT2FIX(3)); | |
ADD_INSN(ret, line, pop); | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag)); | |
} | |
else { | |
if (boff > 0) | |
ADD_INSN(ret, line, swap); | |
ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag)); | |
} | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_OP_ASGN2:{ | |
ID atype = node->nd_next->nd_mid; | |
VALUE asgnflag; | |
LABEL *lfin = NEW_LABEL(line); | |
LABEL *lcfin = NEW_LABEL(line); | |
/* | |
class C; attr_accessor :c; end | |
r = C.new | |
r.a &&= v # asgn2 | |
eval r # r | |
dup # r r | |
eval r.a # r o | |
# or | |
dup # r o o | |
if lcfin # r o | |
pop # r | |
eval v # r v | |
swap # v r | |
topn 1 # v r v | |
send a= # v ? | |
jump lfin # v ? | |
lcfin: # r o | |
swap # o r | |
lfin: # o ? | |
pop # o | |
# and | |
dup # r o o | |
unless lcfin | |
pop # r | |
eval v # r v | |
swap # v r | |
topn 1 # v r v | |
send a= # v ? | |
jump lfin # v ? | |
# others | |
eval v # r o v | |
send ?? # r w | |
send a= # w | |
*/ | |
asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node); | |
ADD_INSN(ret, line, dup); | |
ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_vid, INT2FIX(0), INT2FIX(asgnflag)); | |
if (atype == 0 || atype == 1) { /* 0: OR or 1: AND */ | |
ADD_INSN(ret, line, dup); | |
if (atype == 0) { | |
ADD_INSNL(ret, line, branchif, lcfin); | |
} | |
else { | |
ADD_INSNL(ret, line, branchunless, lcfin); | |
} | |
ADD_INSN(ret, line, pop); | |
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value); | |
ADD_INSN(ret, line, swap); | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); | |
ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag)); | |
ADD_INSNL(ret, line, jump, lfin); | |
ADD_LABEL(ret, lcfin); | |
ADD_INSN(ret, line, swap); | |
ADD_LABEL(ret, lfin); | |
ADD_INSN(ret, line, pop); | |
if (poped) { | |
/* we can apply more optimize */ | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
else { | |
COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value); | |
ADD_SEND(ret, line, node->nd_next->nd_mid, | |
INT2FIX(1)); | |
if (!poped) { | |
ADD_INSN(ret, line, swap); | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); | |
} | |
ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag)); | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_OP_CDECL: { | |
LABEL *lfin = 0; | |
LABEL *lassign = 0; | |
ID mid; | |
switch (nd_type(node->nd_head)) { | |
case NODE_COLON3: | |
ADD_INSN1(ret, line, putobject, rb_cObject); | |
break; | |
case NODE_COLON2: | |
COMPILE(ret, "NODE_OP_CDECL/colon2#nd_head", node->nd_head->nd_head); | |
break; | |
default: | |
do { | |
COMPILE_ERROR((ERROR_ARGS "%s: invalid node in NODE_OP_CDECL", | |
ruby_node_name(nd_type(node->nd_head)))); | |
} while (0); | |
return COMPILE_NG; | |
} | |
mid = node->nd_head->nd_mid; | |
/* cref */ | |
if (node->nd_aid == 0) { | |
lassign = NEW_LABEL(line); | |
ADD_INSN(ret, line, dup); /* cref cref */ | |
ADD_INSN3(ret, line, defined, INT2FIX(DEFINED_CONST), | |
ID2SYM(mid), Qfalse); /* cref bool */ | |
ADD_INSNL(ret, line, branchunless, lassign); /* cref */ | |
} | |
ADD_INSN(ret, line, dup); /* cref cref */ | |
ADD_INSN1(ret, line, getconstant, ID2SYM(mid)); /* cref obj */ | |
if (node->nd_aid == 0 || node->nd_aid == 1) { | |
lfin = NEW_LABEL(line); | |
if (!poped) ADD_INSN(ret, line, dup); /* cref [obj] obj */ | |
if (node->nd_aid == 0) | |
ADD_INSNL(ret, line, branchif, lfin); | |
else | |
ADD_INSNL(ret, line, branchunless, lfin); | |
/* cref [obj] */ | |
if (!poped) ADD_INSN(ret, line, pop); /* cref */ | |
if (lassign) ADD_LABEL(ret, lassign); | |
COMPILE(ret, "NODE_OP_CDECL#nd_value", node->nd_value); | |
/* cref value */ | |
if (poped) | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); /* cref value cref */ | |
else { | |
ADD_INSN1(ret, line, dupn, INT2FIX(2)); /* cref value cref value */ | |
ADD_INSN(ret, line, swap); /* cref value value cref */ | |
} | |
ADD_INSN1(ret, line, setconstant, ID2SYM(mid)); /* cref [value] */ | |
ADD_LABEL(ret, lfin); /* cref [value] */ | |
if (!poped) ADD_INSN(ret, line, swap); /* [value] cref */ | |
ADD_INSN(ret, line, pop); /* [value] */ | |
} | |
else { | |
COMPILE(ret, "NODE_OP_CDECL#nd_value", node->nd_value); | |
/* cref obj value */ | |
ADD_CALL(ret, line, node->nd_aid, INT2FIX(1)); | |
/* cref value */ | |
ADD_INSN(ret, line, swap); /* value cref */ | |
if (!poped) { | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); /* value cref value */ | |
ADD_INSN(ret, line, swap); /* value value cref */ | |
} | |
ADD_INSN1(ret, line, setconstant, ID2SYM(mid)); | |
} | |
break; | |
} | |
case NODE_OP_ASGN_AND: | |
case NODE_OP_ASGN_OR:{ | |
LABEL *lfin = NEW_LABEL(line); | |
LABEL *lassign; | |
if (nd_type(node) == NODE_OP_ASGN_OR) { | |
LABEL *lfinish[2]; | |
lfinish[0] = lfin; | |
lfinish[1] = 0; | |
defined_expr(iseq, ret, node->nd_head, lfinish, Qfalse); | |
lassign = lfinish[1]; | |
if (!lassign) { | |
lassign = NEW_LABEL(line); | |
} | |
ADD_INSNL(ret, line, branchunless, lassign); | |
} | |
else { | |
lassign = NEW_LABEL(line); | |
} | |
COMPILE(ret, "NODE_OP_ASGN_AND/OR#nd_head", node->nd_head); | |
ADD_INSN(ret, line, dup); | |
if (nd_type(node) == NODE_OP_ASGN_AND) { | |
ADD_INSNL(ret, line, branchunless, lfin); | |
} | |
else { | |
ADD_INSNL(ret, line, branchif, lfin); | |
} | |
ADD_INSN(ret, line, pop); | |
ADD_LABEL(ret, lassign); | |
COMPILE(ret, "NODE_OP_ASGN_AND/OR#nd_value", node->nd_value); | |
ADD_LABEL(ret, lfin); | |
if (poped) { | |
/* we can apply more optimize */ | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_CALL: | |
/* optimization shortcut | |
* "literal".freeze -> opt_str_freeze("literal") | |
*/ | |
if (node->nd_recv && nd_type(node->nd_recv) == NODE_STR && | |
node->nd_mid == idFreeze && node->nd_args == NULL && | |
iseq->compile_data->current_block == Qfalse && | |
iseq->compile_data->option->specialized_instruction) { | |
VALUE str = rb_fstring(node->nd_recv->nd_lit); | |
iseq_add_mark_object(iseq, str); | |
ADD_INSN1(ret, line, opt_str_freeze, str); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
/* optimization shortcut | |
* obj["literal"] -> opt_aref_with(obj, "literal") | |
*/ | |
if (node->nd_mid == idAREF && !private_recv_p(node) && node->nd_args && | |
nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 1 && | |
nd_type(node->nd_args->nd_head) == NODE_STR && | |
iseq->compile_data->current_block == Qfalse && | |
iseq->compile_data->option->specialized_instruction) { | |
VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit); | |
node->nd_args->nd_head->nd_lit = str; | |
COMPILE(ret, "recv", node->nd_recv); | |
ADD_INSN2(ret, line, opt_aref_with, | |
new_callinfo(iseq, idAREF, 1, 0, 0, NULL), str); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_FCALL: | |
case NODE_VCALL:{ /* VCALL: variable or call */ | |
/* | |
call: obj.method(...) | |
fcall: func(...) | |
vcall: func | |
*/ | |
DECL_ANCHOR(recv); | |
DECL_ANCHOR(args); | |
ID mid = node->nd_mid; | |
VALUE argc; | |
unsigned int flag = 0; | |
rb_call_info_kw_arg_t *keywords = NULL; | |
VALUE parent_block = iseq->compile_data->current_block; | |
iseq->compile_data->current_block = Qfalse; | |
INIT_ANCHOR(recv); | |
INIT_ANCHOR(args); | |
#if SUPPORT_JOKE | |
if (nd_type(node) == NODE_VCALL) { | |
ID id_bitblt; | |
ID id_answer; | |
CONST_ID(id_bitblt, "bitblt"); | |
CONST_ID(id_answer, "the_answer_to_life_the_universe_and_everything"); | |
if (mid == id_bitblt) { | |
ADD_INSN(ret, line, bitblt); | |
break; | |
} | |
else if (mid == id_answer) { | |
ADD_INSN(ret, line, answer); | |
break; | |
} | |
} | |
/* only joke */ | |
{ | |
ID goto_id; | |
ID label_id; | |
CONST_ID(goto_id, "__goto__"); | |
CONST_ID(label_id, "__label__"); | |
if (nd_type(node) == NODE_FCALL && | |
(mid == goto_id || mid == label_id)) { | |
LABEL *label; | |
st_data_t data; | |
st_table *labels_table = iseq->compile_data->labels_table; | |
ID label_name; | |
if (!labels_table) { | |
labels_table = st_init_numtable(); | |
iseq->compile_data->labels_table = labels_table; | |
} | |
if (nd_type(node->nd_args->nd_head) == NODE_LIT && | |
SYMBOL_P(node->nd_args->nd_head->nd_lit)) { | |
label_name = SYM2ID(node->nd_args->nd_head->nd_lit); | |
if (!st_lookup(labels_table, (st_data_t)label_name, &data)) { | |
label = NEW_LABEL(line); | |
label->position = line; | |
st_insert(labels_table, (st_data_t)label_name, (st_data_t)label); | |
} | |
else { | |
label = (LABEL *)data; | |
} | |
} | |
else { | |
COMPILE_ERROR((ERROR_ARGS "invalid goto/label format")); | |
} | |
if (mid == goto_id) { | |
ADD_INSNL(ret, line, jump, label); | |
} | |
else { | |
ADD_LABEL(ret, label); | |
} | |
break; | |
} | |
} | |
#endif | |
/* receiver */ | |
if (type == NODE_CALL) { | |
COMPILE(recv, "recv", node->nd_recv); | |
} | |
else if (type == NODE_FCALL || type == NODE_VCALL) { | |
ADD_CALL_RECEIVER(recv, line); | |
} | |
/* args */ | |
if (nd_type(node) != NODE_VCALL) { | |
argc = setup_args(iseq, args, node->nd_args, &flag, &keywords); | |
} | |
else { | |
argc = INT2FIX(0); | |
} | |
ADD_SEQ(ret, recv); | |
ADD_SEQ(ret, args); | |
debugp_param("call args argc", argc); | |
debugp_param("call method", ID2SYM(mid)); | |
switch (nd_type(node)) { | |
case NODE_VCALL: | |
flag |= VM_CALL_VCALL; | |
/* VCALL is funcall, so fall through */ | |
case NODE_FCALL: | |
flag |= VM_CALL_FCALL; | |
} | |
ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_SUPER: | |
case NODE_ZSUPER:{ | |
DECL_ANCHOR(args); | |
int argc; | |
unsigned int flag = 0; | |
rb_call_info_kw_arg_t *keywords = NULL; | |
VALUE parent_block = iseq->compile_data->current_block; | |
INIT_ANCHOR(args); | |
iseq->compile_data->current_block = Qfalse; | |
if (nd_type(node) == NODE_SUPER) { | |
VALUE vargc = setup_args(iseq, args, node->nd_args, &flag, &keywords); | |
argc = FIX2INT(vargc); | |
} | |
else { | |
/* NODE_ZSUPER */ | |
int i; | |
rb_iseq_t *liseq = iseq->local_iseq; | |
int lvar_level = get_lvar_level(iseq); | |
argc = liseq->param.lead_num; | |
/* normal arguments */ | |
for (i = 0; i < liseq->param.lead_num; i++) { | |
int idx = liseq->local_size - i; | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
} | |
if (liseq->param.flags.has_opt) { | |
/* optional arguments */ | |
int j; | |
for (j = 0; j < liseq->param.opt_num; j++) { | |
int idx = liseq->local_size - (i + j); | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
} | |
i += j; | |
argc = i; | |
} | |
if (liseq->param.flags.has_rest) { | |
/* rest argument */ | |
int idx = liseq->local_size - liseq->param.rest_start; | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
argc = liseq->param.rest_start + 1; | |
flag |= VM_CALL_ARGS_SPLAT; | |
} | |
if (liseq->param.flags.has_post) { | |
/* post arguments */ | |
int post_len = liseq->param.post_num; | |
int post_start = liseq->param.post_start; | |
if (liseq->param.flags.has_rest) { | |
int j; | |
for (j=0; j<post_len; j++) { | |
int idx = liseq->local_size - (post_start + j); | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
} | |
ADD_INSN1(args, line, newarray, INT2FIX(j)); | |
ADD_INSN (args, line, concatarray); | |
/* argc is settled at above */ | |
} | |
else { | |
int j; | |
for (j=0; j<post_len; j++) { | |
int idx = liseq->local_size - (post_start + j); | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
} | |
argc = post_len + post_start; | |
} | |
} | |
if (liseq->param.flags.has_kw) { /* TODO: support keywords */ | |
int local_size = liseq->local_size; | |
argc++; | |
ADD_INSN1(args, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
if (liseq->param.flags.has_kwrest) { | |
ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->param.keyword->rest_start), INT2FIX(lvar_level)); | |
ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0)); | |
} | |
else { | |
ADD_INSN1(args, line, newhash, INT2FIX(0)); | |
} | |
for (i = 0; i < liseq->param.keyword->num; ++i) { | |
ID id = liseq->param.keyword->table[i]; | |
int idx = local_size - get_local_var_idx(liseq, id); | |
ADD_INSN1(args, line, putobject, ID2SYM(id)); | |
ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level)); | |
} | |
ADD_SEND(args, line, id_core_hash_merge_ptr, INT2FIX(i * 2 + 1)); | |
if (liseq->param.flags.has_rest) { | |
ADD_INSN1(args, line, newarray, INT2FIX(1)); | |
ADD_INSN (args, line, concatarray); | |
--argc; | |
} | |
} | |
else if (liseq->param.flags.has_kwrest) { | |
ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->param.keyword->rest_start), INT2FIX(lvar_level)); | |
ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0)); | |
if (liseq->param.flags.has_rest) { | |
ADD_INSN1(args, line, newarray, INT2FIX(1)); | |
ADD_INSN (args, line, concatarray); | |
} | |
else { | |
argc++; | |
} | |
} | |
} | |
/* dummy receiver */ | |
ADD_INSN1(ret, line, putobject, nd_type(node) == NODE_ZSUPER ? Qfalse : Qtrue); | |
ADD_SEQ(ret, args); | |
ADD_INSN1(ret, line, invokesuper, new_callinfo(iseq, 0, argc, parent_block, | |
flag | VM_CALL_SUPER | VM_CALL_FCALL, keywords)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_ARRAY:{ | |
compile_array_(iseq, ret, node, COMPILE_ARRAY_TYPE_ARRAY, NULL, poped); | |
break; | |
} | |
case NODE_ZARRAY:{ | |
if (!poped) { | |
ADD_INSN1(ret, line, newarray, INT2FIX(0)); | |
} | |
break; | |
} | |
case NODE_VALUES:{ | |
NODE *n = node; | |
while (n) { | |
COMPILE(ret, "values item", n->nd_head); | |
n = n->nd_next; | |
} | |
ADD_INSN1(ret, line, newarray, INT2FIX(node->nd_alen)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_HASH:{ | |
DECL_ANCHOR(list); | |
int type = node->nd_head ? nd_type(node->nd_head) : NODE_ZARRAY; | |
INIT_ANCHOR(list); | |
switch (type) { | |
case NODE_ARRAY: | |
compile_array(iseq, list, node->nd_head, COMPILE_ARRAY_TYPE_HASH); | |
ADD_SEQ(ret, list); | |
break; | |
case NODE_ZARRAY: | |
ADD_INSN1(ret, line, newhash, INT2FIX(0)); | |
break; | |
default: | |
rb_bug("can't make hash with this node: %s", ruby_node_name(type)); | |
} | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_RETURN:{ | |
rb_iseq_t *is = iseq; | |
if (is) { | |
if (is->type == ISEQ_TYPE_TOP) { | |
COMPILE_ERROR((ERROR_ARGS "Invalid return")); | |
} | |
else { | |
LABEL *splabel = 0; | |
if (is->type == ISEQ_TYPE_METHOD) { | |
splabel = NEW_LABEL(0); | |
ADD_LABEL(ret, splabel); | |
ADD_ADJUST(ret, line, 0); | |
} | |
COMPILE(ret, "return nd_stts (return val)", node->nd_stts); | |
if (is->type == ISEQ_TYPE_METHOD) { | |
add_ensure_iseq(ret, iseq, 1); | |
ADD_TRACE(ret, line, RUBY_EVENT_RETURN); | |
ADD_INSN(ret, line, leave); | |
ADD_ADJUST_RESTORE(ret, splabel); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
else { | |
ADD_INSN1(ret, line, throw, INT2FIX(0x01) /* TAG_RETURN */ ); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
} | |
} | |
} | |
break; | |
} | |
case NODE_YIELD:{ | |
DECL_ANCHOR(args); | |
VALUE argc; | |
unsigned int flag = 0; | |
rb_call_info_kw_arg_t *keywords = NULL; | |
INIT_ANCHOR(args); | |
if (iseq->type == ISEQ_TYPE_TOP) { | |
COMPILE_ERROR((ERROR_ARGS "Invalid yield")); | |
} | |
if (node->nd_head) { | |
argc = setup_args(iseq, args, node->nd_head, &flag, &keywords); | |
} | |
else { | |
argc = INT2FIX(0); | |
} | |
ADD_SEQ(ret, args); | |
ADD_INSN1(ret, line, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), 0, flag, keywords)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_LVAR:{ | |
if (!poped) { | |
ID id = node->nd_vid; | |
int idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id); | |
debugs("id: %"PRIsVALUE" idx: %d\n", rb_id2str(id), idx); | |
ADD_INSN2(ret, line, getlocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq))); | |
} | |
break; | |
} | |
case NODE_DVAR:{ | |
int lv, idx, ls; | |
debugi("nd_vid", node->nd_vid); | |
if (!poped) { | |
idx = get_dyna_var_idx(iseq, node->nd_vid, &lv, &ls); | |
if (idx < 0) { | |
rb_bug("unknown dvar (%"PRIsVALUE")", rb_id2str(node->nd_vid)); | |
} | |
ADD_INSN2(ret, line, getlocal, INT2FIX(ls - idx), INT2FIX(lv)); | |
} | |
break; | |
} | |
case NODE_GVAR:{ | |
ADD_INSN1(ret, line, getglobal, | |
((VALUE)node->nd_entry | 1)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_IVAR:{ | |
debugi("nd_vid", node->nd_vid); | |
if (!poped) { | |
ADD_INSN2(ret, line, getinstancevariable, | |
ID2SYM(node->nd_vid), INT2FIX(iseq->is_size++)); | |
} | |
break; | |
} | |
case NODE_CONST:{ | |
debugi("nd_vid", node->nd_vid); | |
if (iseq->compile_data->option->inline_const_cache) { | |
LABEL *lend = NEW_LABEL(line); | |
int ic_index = iseq->is_size++; | |
ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index)); | |
ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_vid)); | |
ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index)); | |
ADD_LABEL(ret, lend); | |
} | |
else { | |
ADD_INSN(ret, line, putnil); | |
ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_vid)); | |
} | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_CVAR:{ | |
if (!poped) { | |
ADD_INSN1(ret, line, getclassvariable, | |
ID2SYM(node->nd_vid)); | |
} | |
break; | |
} | |
case NODE_NTH_REF:{ | |
if (!poped) { | |
ADD_INSN2(ret, line, getspecial, INT2FIX(1) /* '~' */, | |
INT2FIX(node->nd_nth << 1)); | |
} | |
break; | |
} | |
case NODE_BACK_REF:{ | |
if (!poped) { | |
ADD_INSN2(ret, line, getspecial, INT2FIX(1) /* '~' */, | |
INT2FIX(0x01 | (node->nd_nth << 1))); | |
} | |
break; | |
} | |
case NODE_MATCH: | |
case NODE_MATCH2: | |
case NODE_MATCH3:{ | |
DECL_ANCHOR(recv); | |
DECL_ANCHOR(val); | |
INIT_ANCHOR(recv); | |
INIT_ANCHOR(val); | |
switch (nd_type(node)) { | |
case NODE_MATCH: | |
ADD_INSN1(recv, line, putobject, node->nd_lit); | |
ADD_INSN2(val, line, getspecial, INT2FIX(0), | |
INT2FIX(0)); | |
break; | |
case NODE_MATCH2: | |
COMPILE(recv, "receiver", node->nd_recv); | |
COMPILE(val, "value", node->nd_value); | |
break; | |
case NODE_MATCH3: | |
COMPILE(recv, "receiver", node->nd_value); | |
COMPILE(val, "value", node->nd_recv); | |
break; | |
} | |
if (iseq->compile_data->option->specialized_instruction) { | |
/* TODO: detect by node */ | |
if (recv->last == recv->anchor.next && | |
INSN_OF(recv->last) == BIN(putobject) && | |
nd_type(node) == NODE_MATCH2) { | |
ADD_SEQ(ret, val); | |
ADD_INSN1(ret, line, opt_regexpmatch1, | |
OPERAND_AT(recv->last, 0)); | |
} | |
else { | |
ADD_SEQ(ret, recv); | |
ADD_SEQ(ret, val); | |
ADD_INSN1(ret, line, opt_regexpmatch2, new_callinfo(iseq, idEqTilde, 1, 0, 0, NULL)); | |
} | |
} | |
else { | |
ADD_SEQ(ret, recv); | |
ADD_SEQ(ret, val); | |
ADD_SEND(ret, line, idEqTilde, INT2FIX(1)); | |
} | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_LIT:{ | |
debugp_param("lit", node->nd_lit); | |
if (!poped) { | |
ADD_INSN1(ret, line, putobject, node->nd_lit); | |
} | |
break; | |
} | |
case NODE_STR:{ | |
node->nd_lit = rb_fstring(node->nd_lit); | |
debugp_param("nd_lit", node->nd_lit); | |
if (!poped) { | |
ADD_INSN1(ret, line, putstring, node->nd_lit); | |
} | |
break; | |
} | |
case NODE_DSTR:{ | |
compile_dstr(iseq, ret, node); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_XSTR:{ | |
node->nd_lit = rb_fstring(node->nd_lit); | |
ADD_CALL_RECEIVER(ret, line); | |
ADD_INSN1(ret, line, putobject, node->nd_lit); | |
ADD_CALL(ret, line, idBackquote, INT2FIX(1)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_DXSTR:{ | |
ADD_CALL_RECEIVER(ret, line); | |
compile_dstr(iseq, ret, node); | |
ADD_CALL(ret, line, idBackquote, INT2FIX(1)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_EVSTR:{ | |
COMPILE(ret, "nd_body", node->nd_body); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
else { | |
ADD_INSN(ret, line, tostring); | |
} | |
break; | |
} | |
case NODE_DREGX:{ | |
compile_dregx(iseq, ret, node); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_DREGX_ONCE:{ | |
int ic_index = iseq->is_size++; | |
NODE *dregx_node = NEW_NODE(NODE_DREGX, node->u1.value, node->u2.value, node->u3.value); | |
NODE *block_node = NEW_NODE(NODE_SCOPE, 0, dregx_node, 0); | |
VALUE block_iseq = NEW_CHILD_ISEQVAL(block_node, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line); | |
ADD_INSN2(ret, line, once, block_iseq, INT2FIX(ic_index)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_ARGSCAT:{ | |
if (poped) { | |
COMPILE(ret, "argscat head", node->nd_head); | |
ADD_INSN1(ret, line, splatarray, Qfalse); | |
ADD_INSN(ret, line, pop); | |
COMPILE(ret, "argscat body", node->nd_body); | |
ADD_INSN1(ret, line, splatarray, Qfalse); | |
ADD_INSN(ret, line, pop); | |
} | |
else { | |
COMPILE(ret, "argscat head", node->nd_head); | |
COMPILE(ret, "argscat body", node->nd_body); | |
ADD_INSN(ret, line, concatarray); | |
} | |
break; | |
} | |
case NODE_ARGSPUSH:{ | |
if (poped) { | |
COMPILE(ret, "arsgpush head", node->nd_head); | |
ADD_INSN1(ret, line, splatarray, Qfalse); | |
ADD_INSN(ret, line, pop); | |
COMPILE_(ret, "argspush body", node->nd_body, poped); | |
} | |
else { | |
COMPILE(ret, "arsgpush head", node->nd_head); | |
COMPILE_(ret, "argspush body", node->nd_body, poped); | |
ADD_INSN1(ret, line, newarray, INT2FIX(1)); | |
ADD_INSN(ret, line, concatarray); | |
} | |
break; | |
} | |
case NODE_SPLAT:{ | |
COMPILE(ret, "splat", node->nd_head); | |
ADD_INSN1(ret, line, splatarray, Qtrue); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_DEFN:{ | |
VALUE iseqval = NEW_ISEQVAL(node->nd_defn, | |
rb_id2str(node->nd_mid), | |
ISEQ_TYPE_METHOD, line); | |
debugp_param("defn/iseq", iseqval); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE)); | |
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid)); | |
ADD_INSN1(ret, line, putiseq, iseqval); | |
ADD_SEND (ret, line, id_core_define_method, INT2FIX(3)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
debugp_param("defn", iseqval); | |
break; | |
} | |
case NODE_DEFS:{ | |
VALUE iseqval = NEW_ISEQVAL(node->nd_defn, | |
rb_id2str(node->nd_mid), | |
ISEQ_TYPE_METHOD, line); | |
debugp_param("defs/iseq", iseqval); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
COMPILE(ret, "defs: recv", node->nd_recv); | |
ADD_INSN1(ret, line, putobject, ID2SYM(node->nd_mid)); | |
ADD_INSN1(ret, line, putiseq, iseqval); | |
ADD_SEND (ret, line, id_core_define_singleton_method, INT2FIX(3)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_ALIAS:{ | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE)); | |
COMPILE(ret, "alias arg1", node->u1.node); | |
COMPILE(ret, "alias arg2", node->u2.node); | |
ADD_SEND(ret, line, id_core_set_method_alias, INT2FIX(3)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_VALIAS:{ | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putobject, ID2SYM(node->u1.id)); | |
ADD_INSN1(ret, line, putobject, ID2SYM(node->u2.id)); | |
ADD_SEND(ret, line, id_core_set_variable_alias, INT2FIX(2)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_UNDEF:{ | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_CBASE)); | |
COMPILE(ret, "undef arg", node->u2.node); | |
ADD_SEND(ret, line, id_core_undef_method, INT2FIX(2)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_CLASS:{ | |
VALUE iseqval = | |
NEW_CHILD_ISEQVAL( | |
node->nd_body, | |
rb_sprintf("<class:%"PRIsVALUE">", rb_id2str(node->nd_cpath->nd_mid)), | |
ISEQ_TYPE_CLASS, line); | |
VALUE noscope = compile_cpath(ret, iseq, node->nd_cpath); | |
int flags = VM_DEFINECLASS_TYPE_CLASS; | |
if (!noscope) flags |= VM_DEFINECLASS_FLAG_SCOPED; | |
if (node->nd_super) flags |= VM_DEFINECLASS_FLAG_HAS_SUPERCLASS; | |
COMPILE(ret, "super", node->nd_super); | |
ADD_INSN3(ret, line, defineclass, | |
ID2SYM(node->nd_cpath->nd_mid), iseqval, INT2FIX(flags)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_MODULE:{ | |
VALUE iseqval = NEW_CHILD_ISEQVAL( | |
node->nd_body, | |
rb_sprintf("<module:%"PRIsVALUE">", rb_id2str(node->nd_cpath->nd_mid)), | |
ISEQ_TYPE_CLASS, line); | |
VALUE noscope = compile_cpath(ret, iseq, node->nd_cpath); | |
int flags = VM_DEFINECLASS_TYPE_MODULE; | |
if (!noscope) flags |= VM_DEFINECLASS_FLAG_SCOPED; | |
ADD_INSN (ret, line, putnil); /* dummy */ | |
ADD_INSN3(ret, line, defineclass, | |
ID2SYM(node->nd_cpath->nd_mid), iseqval, INT2FIX(flags)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_SCLASS:{ | |
ID singletonclass; | |
VALUE iseqval = | |
NEW_ISEQVAL(node->nd_body, rb_str_new2("singleton class"), | |
ISEQ_TYPE_CLASS, line); | |
COMPILE(ret, "sclass#recv", node->nd_recv); | |
ADD_INSN (ret, line, putnil); | |
CONST_ID(singletonclass, "singletonclass"); | |
ADD_INSN3(ret, line, defineclass, | |
ID2SYM(singletonclass), iseqval, | |
INT2FIX(VM_DEFINECLASS_TYPE_SINGLETON_CLASS)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_COLON2:{ | |
if (rb_is_const_id(node->nd_mid)) { | |
/* constant */ | |
LABEL *lend = NEW_LABEL(line); | |
int ic_index = iseq->is_size++; | |
DECL_ANCHOR(pref); | |
DECL_ANCHOR(body); | |
INIT_ANCHOR(pref); | |
INIT_ANCHOR(body); | |
compile_colon2(iseq, node, pref, body); | |
if (LIST_SIZE_ZERO(pref)) { | |
if (iseq->compile_data->option->inline_const_cache) { | |
ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index)); | |
} | |
else { | |
ADD_INSN(ret, line, putnil); | |
} | |
ADD_SEQ(ret, body); | |
if (iseq->compile_data->option->inline_const_cache) { | |
ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index)); | |
ADD_LABEL(ret, lend); | |
} | |
} | |
else { | |
ADD_SEQ(ret, pref); | |
ADD_SEQ(ret, body); | |
} | |
} | |
else { | |
/* function call */ | |
ADD_CALL_RECEIVER(ret, line); | |
COMPILE(ret, "colon2#nd_head", node->nd_head); | |
ADD_CALL(ret, line, node->nd_mid, INT2FIX(1)); | |
} | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_COLON3:{ | |
LABEL *lend = NEW_LABEL(line); | |
int ic_index = iseq->is_size++; | |
debugi("colon3#nd_mid", node->nd_mid); | |
/* add cache insn */ | |
if (iseq->compile_data->option->inline_const_cache) { | |
ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index)); | |
ADD_INSN(ret, line, pop); | |
} | |
ADD_INSN1(ret, line, putobject, rb_cObject); | |
ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_mid)); | |
if (iseq->compile_data->option->inline_const_cache) { | |
ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index)); | |
ADD_LABEL(ret, lend); | |
} | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_DOT2: | |
case NODE_DOT3:{ | |
VALUE flag = type == NODE_DOT2 ? INT2FIX(0) : INT2FIX(1); | |
COMPILE(ret, "min", (NODE *) node->nd_beg); | |
COMPILE(ret, "max", (NODE *) node->nd_end); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
ADD_INSN(ret, line, pop); | |
} | |
else { | |
ADD_INSN1(ret, line, newrange, flag); | |
} | |
break; | |
} | |
case NODE_FLIP2: | |
case NODE_FLIP3:{ | |
LABEL *lend = NEW_LABEL(line); | |
LABEL *lfin = NEW_LABEL(line); | |
LABEL *ltrue = NEW_LABEL(line); | |
rb_iseq_t *local_iseq = iseq->local_iseq; | |
rb_num_t cnt; | |
VALUE key; | |
cnt = local_iseq->flip_cnt++ + DEFAULT_SPECIAL_VAR_COUNT; | |
key = INT2FIX(cnt); | |
ADD_INSN2(ret, line, getspecial, key, INT2FIX(0)); | |
ADD_INSNL(ret, line, branchif, lend); | |
/* *flip == 0 */ | |
COMPILE(ret, "flip2 beg", node->nd_beg); | |
ADD_INSN(ret, line, dup); | |
ADD_INSNL(ret, line, branchunless, lfin); | |
if (nd_type(node) == NODE_FLIP3) { | |
ADD_INSN(ret, line, dup); | |
ADD_INSN1(ret, line, setspecial, key); | |
ADD_INSNL(ret, line, jump, lfin); | |
} | |
else { | |
ADD_INSN1(ret, line, setspecial, key); | |
} | |
/* *flip == 1 */ | |
ADD_LABEL(ret, lend); | |
COMPILE(ret, "flip2 end", node->nd_end); | |
ADD_INSNL(ret, line, branchunless, ltrue); | |
ADD_INSN1(ret, line, putobject, Qfalse); | |
ADD_INSN1(ret, line, setspecial, key); | |
ADD_LABEL(ret, ltrue); | |
ADD_INSN1(ret, line, putobject, Qtrue); | |
ADD_LABEL(ret, lfin); | |
break; | |
} | |
case NODE_SELF:{ | |
if (!poped) { | |
ADD_INSN(ret, line, putself); | |
} | |
break; | |
} | |
case NODE_NIL:{ | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
} | |
break; | |
} | |
case NODE_TRUE:{ | |
if (!poped) { | |
ADD_INSN1(ret, line, putobject, Qtrue); | |
} | |
break; | |
} | |
case NODE_FALSE:{ | |
if (!poped) { | |
ADD_INSN1(ret, line, putobject, Qfalse); | |
} | |
break; | |
} | |
case NODE_ERRINFO:{ | |
if (!poped) { | |
if (iseq->type == ISEQ_TYPE_RESCUE) { | |
ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0)); | |
} | |
else { | |
rb_iseq_t *ip = iseq; | |
int level = 0; | |
while (ip) { | |
if (ip->type == ISEQ_TYPE_RESCUE) { | |
break; | |
} | |
ip = ip->parent_iseq; | |
level++; | |
} | |
if (ip) { | |
ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(level)); | |
} | |
else { | |
ADD_INSN(ret, line, putnil); | |
} | |
} | |
} | |
break; | |
} | |
case NODE_DEFINED:{ | |
if (poped) break; | |
if (!node->nd_head) { | |
VALUE str = rb_iseq_defined_string(DEFINED_NIL); | |
ADD_INSN1(ret, nd_line(node), putobject, str); | |
} | |
else { | |
LABEL *lfinish[2]; | |
lfinish[0] = NEW_LABEL(line); | |
lfinish[1] = 0; | |
ADD_INSN(ret, line, putnil); | |
defined_expr(iseq, ret, node->nd_head, lfinish, Qtrue); | |
ADD_INSN(ret, line, swap); | |
ADD_INSN(ret, line, pop); | |
if (lfinish[1]) { | |
ADD_LABEL(ret, lfinish[1]); | |
} | |
ADD_LABEL(ret, lfinish[0]); | |
} | |
break; | |
} | |
case NODE_POSTEXE:{ | |
/* compiled to: | |
* ONCE{ rb_mRubyVMFrozenCore::core#set_postexe{ ... } } | |
*/ | |
int is_index = iseq->is_size++; | |
VALUE once_iseq = NEW_CHILD_ISEQVAL( | |
NEW_IFUNC(build_postexe_iseq, node->nd_body), | |
make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line); | |
ADD_INSN2(ret, line, once, once_iseq, INT2FIX(is_index)); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_KW_ARG: | |
{ | |
LABEL *end_label = NEW_LABEL(nd_line(node)); | |
NODE *default_value = node->nd_body->nd_value; | |
if (default_value == (NODE *)-1) { | |
/* required argument. do nothing */ | |
rb_bug("unreachable"); | |
} | |
else if (nd_type(default_value) == NODE_LIT || | |
nd_type(default_value) == NODE_NIL || | |
nd_type(default_value) == NODE_TRUE || | |
nd_type(default_value) == NODE_FALSE) { | |
rb_bug("unreachable"); | |
} | |
else { | |
/* if keywordcheck(_kw_bits, nth_keyword) | |
* kw = default_value | |
* end | |
*/ | |
int kw_bits_idx = iseq->local_size - iseq->param.keyword->bits_start; | |
int keyword_idx = iseq->param.keyword->num; | |
ADD_INSN2(ret, line, checkkeyword, INT2FIX(kw_bits_idx), INT2FIX(keyword_idx)); | |
ADD_INSNL(ret, line, branchif, end_label); | |
COMPILE_POPED(ret, "keyword default argument", node->nd_body); | |
ADD_LABEL(ret, end_label); | |
} | |
break; | |
} | |
case NODE_DSYM:{ | |
compile_dstr(iseq, ret, node); | |
if (!poped) { | |
ADD_SEND(ret, line, idIntern, INT2FIX(0)); | |
} | |
else { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
case NODE_ATTRASGN:{ | |
DECL_ANCHOR(recv); | |
DECL_ANCHOR(args); | |
unsigned int flag = 0; | |
VALUE argc; | |
int asgnflag; | |
/* optimization shortcut | |
* obj["literal"] = value -> opt_aset_with(obj, "literal", value) | |
*/ | |
if (node->nd_mid == idASET && !private_recv_p(node) && node->nd_args && | |
nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 2 && | |
nd_type(node->nd_args->nd_head) == NODE_STR && | |
iseq->compile_data->current_block == Qfalse && | |
iseq->compile_data->option->specialized_instruction) | |
{ | |
VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit); | |
node->nd_args->nd_head->nd_lit = str; | |
iseq_add_mark_object(iseq, str); | |
COMPILE(ret, "recv", node->nd_recv); | |
COMPILE(ret, "value", node->nd_args->nd_next->nd_head); | |
if (!poped) { | |
ADD_INSN(ret, line, swap); | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); | |
} | |
ADD_INSN2(ret, line, opt_aset_with, | |
new_callinfo(iseq, idASET, 2, 0, 0, NULL), str); | |
ADD_INSN(ret, line, pop); | |
break; | |
} | |
INIT_ANCHOR(recv); | |
INIT_ANCHOR(args); | |
argc = setup_args(iseq, args, node->nd_args, &flag, NULL); | |
flag |= (asgnflag = COMPILE_RECV(recv, "recv", node)); | |
debugp_param("argc", argc); | |
debugp_param("nd_mid", ID2SYM(node->nd_mid)); | |
if (!poped) { | |
ADD_INSN(ret, line, putnil); | |
ADD_SEQ(ret, recv); | |
ADD_SEQ(ret, args); | |
if (flag & VM_CALL_ARGS_BLOCKARG) { | |
ADD_INSN1(ret, line, topn, INT2FIX(1)); | |
if (flag & VM_CALL_ARGS_SPLAT) { | |
ADD_INSN1(ret, line, putobject, INT2FIX(-1)); | |
ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag)); | |
} | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 3)); | |
ADD_INSN (ret, line, pop); | |
} | |
else if (flag & VM_CALL_ARGS_SPLAT) { | |
ADD_INSN(ret, line, dup); | |
ADD_INSN1(ret, line, putobject, INT2FIX(-1)); | |
ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag)); | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2)); | |
ADD_INSN (ret, line, pop); | |
} | |
else { | |
ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 1)); | |
} | |
} | |
else { | |
ADD_SEQ(ret, recv); | |
ADD_SEQ(ret, args); | |
} | |
ADD_SEND_WITH_FLAG(ret, line, node->nd_mid, argc, INT2FIX(flag)); | |
ADD_INSN(ret, line, pop); | |
break; | |
} | |
case NODE_PRELUDE:{ | |
COMPILE_POPED(ret, "prelude", node->nd_head); | |
COMPILE_(ret, "body", node->nd_body, poped); | |
break; | |
} | |
case NODE_LAMBDA:{ | |
/* compile same as lambda{...} */ | |
VALUE block = NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line); | |
VALUE argc = INT2FIX(0); | |
ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE)); | |
ADD_CALL_WITH_BLOCK(ret, line, idLambda, argc, block); | |
if (poped) { | |
ADD_INSN(ret, line, pop); | |
} | |
break; | |
} | |
default: | |
rb_bug("iseq_compile_each: unknown node: %s", ruby_node_name(type)); | |
return COMPILE_NG; | |
} | |
/* check & remove redundant trace(line) */ | |
if (saved_last_element && ret /* ret can be 0 when error */ && | |
ret->last == saved_last_element && | |
((INSN *)saved_last_element)->insn_id == BIN(trace)) { | |
POP_ELEMENT(ret); | |
} | |
debug_node_end(); | |
return COMPILE_OK; | |
} | |
/***************************/ | |
/* instruction information */ | |
/***************************/ | |
static int | |
insn_data_length(INSN *iobj) | |
{ | |
return insn_len(iobj->insn_id); | |
} | |
static int | |
calc_sp_depth(int depth, INSN *insn) | |
{ | |
return insn_stack_increase(depth, insn->insn_id, insn->operands); | |
} | |
static VALUE | |
opobj_inspect(VALUE obj) | |
{ | |
struct RBasic *r = (struct RBasic *) obj; | |
if (!SPECIAL_CONST_P(r) && r->klass == 0) { | |
switch (BUILTIN_TYPE(r)) { | |
case T_STRING: | |
obj = rb_str_new_cstr(RSTRING_PTR(obj)); | |
break; | |
case T_ARRAY: | |
obj = rb_ary_dup(obj); | |
break; | |
} | |
} | |
return rb_inspect(obj); | |
} | |
static VALUE | |
insn_data_to_s_detail(INSN *iobj) | |
{ | |
VALUE str = rb_sprintf("%-20s ", insn_name(iobj->insn_id)); | |
if (iobj->operands) { | |
const char *types = insn_op_types(iobj->insn_id); | |
int j; | |
for (j = 0; types[j]; j++) { | |
char type = types[j]; | |
switch (type) { | |
case TS_OFFSET: /* label(destination position) */ | |
{ | |
LABEL *lobj = (LABEL *)OPERAND_AT(iobj, j); | |
rb_str_catf(str, "<L%03d>", lobj->label_no); | |
break; | |
} | |
break; | |
case TS_ISEQ: /* iseq */ | |
{ | |
rb_iseq_t *iseq = (rb_iseq_t *)OPERAND_AT(iobj, j); | |
VALUE val = Qnil; | |
if (0 && iseq) { /* TODO: invalidate now */ | |
val = iseq->self; | |
} | |
rb_str_concat(str, opobj_inspect(val)); | |
} | |
break; | |
case TS_LINDEX: | |
case TS_NUM: /* ulong */ | |
case TS_VALUE: /* VALUE */ | |
{ | |
VALUE v = OPERAND_AT(iobj, j); | |
rb_str_concat(str, opobj_inspect(v)); | |
break; | |
} | |
case TS_ID: /* ID */ | |
rb_str_concat(str, opobj_inspect(OPERAND_AT(iobj, j))); | |
break; | |
case TS_GENTRY: | |
{ | |
struct rb_global_entry *entry = (struct rb_global_entry *) | |
(OPERAND_AT(iobj, j) & (~1)); | |
rb_str_append(str, rb_id2str(entry->id)); | |
break; | |
} | |
case TS_IC: /* inline cache */ | |
rb_str_catf(str, "<ic:%d>", FIX2INT(OPERAND_AT(iobj, j))); | |
break; | |
case TS_CALLINFO: /* call info */ | |
{ | |
rb_call_info_t *ci = (rb_call_info_t *)OPERAND_AT(iobj, j); | |
rb_str_cat2(str, "<callinfo:"); | |
if (ci->mid) | |
rb_str_catf(str, "%"PRIsVALUE, rb_id2str(ci->mid)); | |
rb_str_catf(str, ", %d>", ci->orig_argc); | |
break; | |
} | |
case TS_CDHASH: /* case/when condition cache */ | |
rb_str_cat2(str, "<ch>"); | |
break; | |
case TS_FUNCPTR: | |
{ | |
rb_insn_func_t func = (rb_insn_func_t)OPERAND_AT(iobj, j); | |
#ifdef HAVE_DLADDR | |
Dl_info info; | |
if (dladdr(func, &info) && info.dli_sname) { | |
rb_str_cat2(str, info.dli_sname); | |
break; | |
} | |
#endif | |
rb_str_catf(str, "<%p>", func); | |
} | |
break; | |
default:{ | |
rb_raise(rb_eSyntaxError, "unknown operand type: %c", type); | |
} | |
} | |
if (types[j + 1]) { | |
rb_str_cat2(str, ", "); | |
} | |
} | |
} | |
return str; | |
} | |
static void | |
dump_disasm_list(struct iseq_link_element *link) | |
{ | |
int pos = 0; | |
INSN *iobj; | |
LABEL *lobj; | |
VALUE str; | |
printf("-- raw disasm--------\n"); | |
while (link) { | |
switch (link->type) { | |
case ISEQ_ELEMENT_INSN: | |
{ | |
iobj = (INSN *)link; | |
str = insn_data_to_s_detail(iobj); | |
printf("%04d %-65s(%4u)\n", pos, StringValueCStr(str), iobj->line_no); | |
pos += insn_data_length(iobj); | |
break; | |
} | |
case ISEQ_ELEMENT_LABEL: | |
{ | |
lobj = (LABEL *)link; | |
printf("<L%03d>\n", lobj->label_no); | |
break; | |
} | |
case ISEQ_ELEMENT_NONE: | |
{ | |
printf("[none]\n"); | |
break; | |
} | |
case ISEQ_ELEMENT_ADJUST: | |
{ | |
ADJUST *adjust = (ADJUST *)link; | |
printf("adjust: [label: %d]\n", adjust->label ? adjust->label->label_no : -1); | |
break; | |
} | |
default: | |
/* ignore */ | |
rb_raise(rb_eSyntaxError, "dump_disasm_list error: %ld\n", FIX2LONG(link->type)); | |
} | |
link = link->next; | |
} | |
printf("---------------------\n"); | |
} | |
const char * | |
rb_insns_name(int i) | |
{ | |
return insn_name_info[i]; | |
} | |
VALUE | |
rb_insns_name_array(void) | |
{ | |
VALUE ary = rb_ary_new(); | |
int i; | |
for (i = 0; i < numberof(insn_name_info); i++) { | |
rb_ary_push(ary, rb_fstring(rb_str_new2(insn_name_info[i]))); | |
} | |
return rb_obj_freeze(ary); | |
} | |
static LABEL * | |
register_label(rb_iseq_t *iseq, struct st_table *labels_table, VALUE obj) | |
{ | |
LABEL *label = 0; | |
st_data_t tmp; | |
obj = rb_convert_type(obj, T_SYMBOL, "Symbol", "to_sym"); | |
if (st_lookup(labels_table, obj, &tmp) == 0) { | |
label = NEW_LABEL(0); | |
st_insert(labels_table, obj, (st_data_t)label); | |
} | |
else { | |
label = (LABEL *)tmp; | |
} | |
return label; | |
} | |
static VALUE | |
get_exception_sym2type(VALUE sym) | |
{ | |
#undef rb_intern | |
#define rb_intern(str) rb_intern_const(str) | |
VALUE sym_inspect; | |
static VALUE symRescue, symEnsure, symRetry; | |
static VALUE symBreak, symRedo, symNext; | |
if (symRescue == 0) { | |
symRescue = ID2SYM(rb_intern("rescue")); | |
symEnsure = ID2SYM(rb_intern("ensure")); | |
symRetry = ID2SYM(rb_intern("retry")); | |
symBreak = ID2SYM(rb_intern("break")); | |
symRedo = ID2SYM(rb_intern("redo")); | |
symNext = ID2SYM(rb_intern("next")); | |
} | |
if (sym == symRescue) return CATCH_TYPE_RESCUE; | |
if (sym == symEnsure) return CATCH_TYPE_ENSURE; | |
if (sym == symRetry) return CATCH_TYPE_RETRY; | |
if (sym == symBreak) return CATCH_TYPE_BREAK; | |
if (sym == symRedo) return CATCH_TYPE_REDO; | |
if (sym == symNext) return CATCH_TYPE_NEXT; | |
sym_inspect = rb_inspect(sym); | |
rb_raise(rb_eSyntaxError, "invalid exception symbol: %s", | |
StringValuePtr(sym_inspect)); | |
return 0; | |
} | |
static int | |
iseq_build_from_ary_exception(rb_iseq_t *iseq, struct st_table *labels_table, | |
VALUE exception) | |
{ | |
int i; | |
for (i=0; i<RARRAY_LEN(exception); i++) { | |
VALUE v, type, eiseqval; | |
const VALUE *ptr; | |
LABEL *lstart, *lend, *lcont; | |
unsigned int sp; | |
v = rb_convert_type(RARRAY_AREF(exception, i), T_ARRAY, | |
"Array", "to_ary"); | |
if (RARRAY_LEN(v) != 6) { | |
rb_raise(rb_eSyntaxError, "wrong exception entry"); | |
} | |
ptr = RARRAY_CONST_PTR(v); | |
type = get_exception_sym2type(ptr[0]); | |
if (ptr[1] == Qnil) { | |
eiseqval = 0; | |
} | |
else { | |
eiseqval = rb_iseq_load(ptr[1], iseq->self, Qnil); | |
} | |
lstart = register_label(iseq, labels_table, ptr[2]); | |
lend = register_label(iseq, labels_table, ptr[3]); | |
lcont = register_label(iseq, labels_table, ptr[4]); | |
sp = NUM2UINT(ptr[5]); | |
(void)sp; | |
ADD_CATCH_ENTRY(type, lstart, lend, eiseqval, lcont); | |
RB_GC_GUARD(v); | |
} | |
return COMPILE_OK; | |
} | |
static struct st_table * | |
insn_make_insn_table(void) | |
{ | |
struct st_table *table; | |
int i; | |
table = st_init_numtable(); | |
for (i=0; i<VM_INSTRUCTION_SIZE; i++) { | |
st_insert(table, ID2SYM(rb_intern(insn_name(i))), i); | |
} | |
return table; | |
} | |
static VALUE | |
iseq_build_load_iseq(rb_iseq_t *iseq, VALUE op) | |
{ | |
VALUE iseqval; | |
if (RB_TYPE_P(op, T_ARRAY)) { | |
iseqval = rb_iseq_load(op, iseq->self, Qnil); | |
} | |
else if (CLASS_OF(op) == rb_cISeq) { | |
iseqval = op; | |
} | |
else { | |
rb_raise(rb_eSyntaxError, "ISEQ is required"); | |
} | |
iseq_add_mark_object(iseq, iseqval); | |
return iseqval; | |
} | |
static VALUE | |
iseq_build_callinfo_from_hash(rb_iseq_t *iseq, VALUE op) | |
{ | |
ID mid = 0; | |
int orig_argc = 0; | |
VALUE block = 0; | |
unsigned int flag = 0; | |
rb_call_info_kw_arg_t *kw_arg = 0; | |
if (!NIL_P(op)) { | |
VALUE vmid = rb_hash_aref(op, ID2SYM(rb_intern("mid"))); | |
VALUE vflag = rb_hash_aref(op, ID2SYM(rb_intern("flag"))); | |
VALUE vorig_argc = rb_hash_aref(op, ID2SYM(rb_intern("orig_argc"))); | |
VALUE vblock = rb_hash_aref(op, ID2SYM(rb_intern("blockptr"))); | |
VALUE vkw_arg = rb_hash_aref(op, ID2SYM(rb_intern("kw_arg"))); | |
if (!NIL_P(vmid)) mid = SYM2ID(vmid); | |
if (!NIL_P(vflag)) flag = NUM2UINT(vflag); | |
if (!NIL_P(vorig_argc)) orig_argc = FIX2INT(vorig_argc); | |
if (!NIL_P(vblock)) block = iseq_build_load_iseq(iseq, vblock); | |
if (!NIL_P(vkw_arg)) { | |
int i; | |
int len = RARRAY_LENINT(vkw_arg); | |
size_t n = sizeof(rb_call_info_kw_arg_t) + sizeof(ID) * (len - 1); | |
kw_arg = xmalloc(n); | |
kw_arg->keyword_len = len; | |
for (i = 0; i < len; i++) { | |
kw_arg->keywords[i] = SYM2ID(RARRAY_AREF(vkw_arg, i)); | |
} | |
} | |
} | |
return (VALUE)new_callinfo(iseq, mid, orig_argc, block, flag, kw_arg); | |
} | |
static int | |
iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *anchor, | |
VALUE body, struct st_table *labels_table) | |
{ | |
/* TODO: body should be frozen */ | |
const VALUE *ptr = RARRAY_CONST_PTR(body); | |
long i, len = RARRAY_LEN(body); | |
int j; | |
int line_no = 0; | |
/* | |
* index -> LABEL *label | |
*/ | |
static struct st_table *insn_table; | |
if (insn_table == 0) { | |
insn_table = insn_make_insn_table(); | |
} | |
for (i=0; i<len; i++) { | |
VALUE obj = ptr[i]; | |
if (SYMBOL_P(obj)) { | |
LABEL *label = register_label(iseq, labels_table, obj); | |
ADD_LABEL(anchor, label); | |
} | |
else if (FIXNUM_P(obj)) { | |
line_no = NUM2INT(obj); | |
} | |
else if (RB_TYPE_P(obj, T_ARRAY)) { | |
VALUE *argv = 0; | |
int argc = RARRAY_LENINT(obj) - 1; | |
st_data_t insn_id; | |
VALUE insn; | |
insn = (argc < 0) ? Qnil : RARRAY_AREF(obj, 0); | |
if (st_lookup(insn_table, (st_data_t)insn, &insn_id) == 0) { | |
/* TODO: exception */ | |
rb_compile_error(RSTRING_PTR(iseq->location.path), line_no, | |
"unknown instruction: %"PRIsVALUE, | |
rb_inspect(insn)); | |
} | |
if (argc != insn_len((VALUE)insn_id)-1) { | |
rb_compile_error(RSTRING_PTR(iseq->location.path), line_no, | |
"operand size mismatch"); | |
} | |
if (argc > 0) { | |
argv = compile_data_alloc(iseq, sizeof(VALUE) * argc); | |
for (j=0; j<argc; j++) { | |
VALUE op = rb_ary_entry(obj, j+1); | |
switch (insn_op_type((VALUE)insn_id, j)) { | |
case TS_OFFSET: { | |
LABEL *label = register_label(iseq, labels_table, op); | |
argv[j] = (VALUE)label; | |
break; | |
} | |
case TS_LINDEX: | |
case TS_NUM: | |
(void)NUM2INT(op); | |
argv[j] = op; | |
break; | |
case TS_VALUE: | |
argv[j] = op; | |
iseq_add_mark_object(iseq, op); | |
break; | |
case TS_ISEQ: | |
{ | |
if (op != Qnil) { | |
argv[j] = iseq_build_load_iseq(iseq, op); | |
} | |
else { | |
argv[j] = 0; | |
} | |
} | |
break; | |
case TS_GENTRY: | |
op = rb_convert_type(op, T_SYMBOL, "Symbol", "to_sym"); | |
argv[j] = (VALUE)rb_global_entry(SYM2ID(op)); | |
break; | |
case TS_IC: | |
argv[j] = op; | |
if (NUM2INT(op) >= iseq->is_size) { | |
iseq->is_size = NUM2INT(op) + 1; | |
} | |
break; | |
case TS_CALLINFO: | |
argv[j] = iseq_build_callinfo_from_hash(iseq, op); | |
break; | |
case TS_ID: | |
argv[j] = rb_convert_type(op, T_SYMBOL, | |
"Symbol", "to_sym"); | |
break; | |
case TS_CDHASH: | |
{ | |
int i; | |
VALUE map = rb_hash_new(); | |
rb_hash_tbl_raw(map)->type = &cdhash_type; | |
op = rb_convert_type(op, T_ARRAY, "Array", "to_ary"); | |
op = rb_ary_dup(op); | |
for (i=0; i<RARRAY_LEN(op); i+=2) { | |
VALUE key = RARRAY_AREF(op, i); | |
VALUE sym = RARRAY_AREF(op, i+1); | |
LABEL *label = | |
register_label(iseq, labels_table, sym); | |
rb_hash_aset(map, key, (VALUE)label | 1); | |
} | |
RB_GC_GUARD(op); | |
argv[j] = map; | |
iseq_add_mark_object_compile_time(iseq, map); | |
} | |
break; | |
case TS_FUNCPTR: | |
{ | |
#if SIZEOF_VALUE <= SIZEOF_LONG | |
long funcptr = NUM2LONG(op); | |
#else | |
LONG_LONG funcptr = NUM2LL(op); | |
#endif | |
argv[j] = (VALUE)funcptr; | |
} | |
break; | |
default: | |
rb_raise(rb_eSyntaxError, "unknown operand: %c", insn_op_type((VALUE)insn_id, j)); | |
} | |
} | |
} | |
ADD_ELEM(anchor, | |
(LINK_ELEMENT*)new_insn_core(iseq, line_no, | |
(enum ruby_vminsn_type)insn_id, argc, argv)); | |
} | |
else { | |
rb_raise(rb_eTypeError, "unexpected object for instruction"); | |
} | |
} | |
validate_labels(iseq, labels_table); | |
st_free_table(labels_table); | |
iseq_setup(iseq, anchor); | |
return COMPILE_OK; | |
} | |
#define CHECK_ARRAY(v) rb_convert_type((v), T_ARRAY, "Array", "to_ary") | |
#define CHECK_SYMBOL(v) rb_convert_type((v), T_SYMBOL, "Symbol", "to_sym") | |
static int | |
int_param(int *dst, VALUE param, VALUE sym) | |
{ | |
VALUE val = rb_hash_aref(param, sym); | |
switch (TYPE(val)) { | |
case T_NIL: | |
return FALSE; | |
case T_FIXNUM: | |
*dst = FIX2INT(val); | |
return TRUE; | |
default: | |
rb_raise(rb_eTypeError, "invalid %+"PRIsVALUE" Fixnum: %+"PRIsVALUE, | |
sym, val); | |
} | |
return FALSE; | |
} | |
static void | |
iseq_build_kw(rb_iseq_t *iseq, VALUE params, VALUE keywords) | |
{ | |
int i, j; | |
int len = RARRAY_LENINT(keywords); | |
int default_len; | |
VALUE key, sym, default_val; | |
iseq->param.flags.has_kw = TRUE; | |
iseq->param.keyword = ZALLOC(struct rb_iseq_param_keyword); | |
iseq->param.keyword->num = len; | |
#define SYM(s) ID2SYM(rb_intern(#s)) | |
(void)int_param(&iseq->param.keyword->bits_start, params, SYM(kwbits)); | |
i = iseq->param.keyword->bits_start - iseq->param.keyword->num; | |
iseq->param.keyword->table = &iseq->local_table[i]; | |
#undef SYM | |
/* required args */ | |
for (i = 0; i < len; i++) { | |
VALUE val = RARRAY_AREF(keywords, i); | |
if (!SYMBOL_P(val)) { | |
goto default_values; | |
} | |
iseq->param.keyword->table[i] = SYM2ID(val); | |
iseq->param.keyword->required_num++; | |
} | |
default_values: /* note: we intentionally preserve `i' from previous loop */ | |
default_len = len - i; | |
if (default_len == 0) { | |
return; | |
} | |
iseq->param.keyword->default_values = ALLOC_N(VALUE, default_len); | |
for (j = 0; i < len; i++, j++) { | |
key = RARRAY_AREF(keywords, i); | |
CHECK_ARRAY(key); | |
switch (RARRAY_LEN(key)) { | |
case 1: | |
sym = RARRAY_AREF(key, 0); | |
default_val = Qundef; | |
break; | |
case 2: | |
sym = RARRAY_AREF(key, 0); | |
default_val = RARRAY_AREF(key, 1); | |
break; | |
default: | |
rb_raise(rb_eTypeError, | |
"keyword default has unsupported len %+"PRIsVALUE, | |
key); | |
} | |
iseq->param.keyword->table[i] = SYM2ID(sym); | |
iseq->param.keyword->default_values[j] = default_val; | |
} | |
} | |
VALUE | |
rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE misc, VALUE locals, VALUE params, | |
VALUE exception, VALUE body) | |
{ | |
#define SYM(s) ID2SYM(rb_intern(#s)) | |
int i, len; | |
ID *tbl; | |
struct st_table *labels_table = st_init_numtable(); | |
VALUE arg_opt_labels = rb_hash_aref(params, SYM(opt)); | |
VALUE keywords = rb_hash_aref(params, SYM(keyword)); | |
VALUE sym_arg_rest = ID2SYM(rb_intern("#arg_rest")); | |
DECL_ANCHOR(anchor); | |
INIT_ANCHOR(anchor); | |
len = RARRAY_LENINT(locals); | |
iseq->local_table_size = len; | |
iseq->local_table = tbl = (ID *)ALLOC_N(ID, iseq->local_table_size); | |
iseq->local_size = iseq->local_table_size + 1; | |
for (i = 0; i < len; i++) { | |
VALUE lv = RARRAY_AREF(locals, i); | |
if (sym_arg_rest == lv) { | |
tbl[i] = 0; | |
} | |
else { | |
tbl[i] = FIXNUM_P(lv) ? (ID)FIX2LONG(lv) : SYM2ID(CHECK_SYMBOL(lv)); | |
} | |
} | |
/* | |
* we currently ignore misc params, | |
* local_size, stack_size and param.size are all calculated | |
*/ | |
#define INT_PARAM(F) int_param(&iseq->param.F, params, SYM(F)) | |
if (INT_PARAM(lead_num)) { | |
iseq->param.flags.has_lead = TRUE; | |
} | |
if (INT_PARAM(post_num)) iseq->param.flags.has_post = TRUE; | |
if (INT_PARAM(post_start)) iseq->param.flags.has_post = TRUE; | |
if (INT_PARAM(rest_start)) iseq->param.flags.has_rest = TRUE; | |
if (INT_PARAM(block_start)) iseq->param.flags.has_block = TRUE; | |
#undef INT_PARAM | |
switch (TYPE(arg_opt_labels)) { | |
case T_ARRAY: | |
len = RARRAY_LENINT(arg_opt_labels); | |
iseq->param.flags.has_opt = !!(len - 1 >= 0); | |
if (iseq->param.flags.has_opt) { | |
iseq->param.opt_num = len - 1; | |
iseq->param.opt_table = (VALUE *)ALLOC_N(VALUE, len); | |
for (i = 0; i < len; i++) { | |
VALUE ent = RARRAY_AREF(arg_opt_labels, i); | |
LABEL *label = register_label(iseq, labels_table, ent); | |
iseq->param.opt_table[i] = (VALUE)label; | |
} | |
} | |
case T_NIL: | |
break; | |
default: | |
rb_raise(rb_eTypeError, ":opt param is not an array: %+"PRIsVALUE, | |
arg_opt_labels); | |
} | |
switch (TYPE(keywords)) { | |
case T_ARRAY: | |
iseq_build_kw(iseq, params, keywords); | |
case T_NIL: | |
break; | |
default: | |
rb_raise(rb_eTypeError, ":keywords param is not an array: %+"PRIsVALUE, | |
keywords); | |
} | |
if (Qtrue == rb_hash_aref(params, SYM(ambiguous_param0))) { | |
iseq->param.flags.ambiguous_param0 = TRUE; | |
} | |
if (int_param(&i, params, SYM(kwrest))) { | |
if (!iseq->param.keyword) { | |
iseq->param.keyword = ZALLOC(struct rb_iseq_param_keyword); | |
} | |
iseq->param.keyword->rest_start = i; | |
iseq->param.flags.has_kwrest = TRUE; | |
} | |
#undef SYM | |
iseq_calc_param_size(iseq); | |
/* exception */ | |
iseq_build_from_ary_exception(iseq, labels_table, exception); | |
/* body */ | |
iseq_build_from_ary_body(iseq, anchor, body, labels_table); | |
return iseq->self; | |
} | |
/* for parser */ | |
int | |
rb_dvar_defined(ID id) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
rb_iseq_t *iseq; | |
if (th->base_block && (iseq = th->base_block->iseq)) { | |
while (iseq->type == ISEQ_TYPE_BLOCK || | |
iseq->type == ISEQ_TYPE_RESCUE || | |
iseq->type == ISEQ_TYPE_ENSURE || | |
iseq->type == ISEQ_TYPE_EVAL || | |
iseq->type == ISEQ_TYPE_MAIN | |
) { | |
int i; | |
for (i = 0; i < iseq->local_table_size; i++) { | |
if (iseq->local_table[i] == id) { | |
return 1; | |
} | |
} | |
iseq = iseq->parent_iseq; | |
} | |
} | |
return 0; | |
} | |
int | |
rb_local_defined(ID id) | |
{ | |
rb_thread_t *th = GET_THREAD(); | |
rb_iseq_t *iseq; | |
if (th->base_block && th->base_block->iseq) { | |
int i; | |
iseq = th->base_block->iseq->local_iseq; | |
for (i=0; i<iseq->local_table_size; i++) { | |
if (iseq->local_table[i] == id) { | |
return 1; | |
} | |
} | |
} | |
return 0; | |
} | |
int | |
rb_parse_in_eval(void) | |
{ | |
return GET_THREAD()->parse_in_eval > 0; | |
} | |
int | |
rb_parse_in_main(void) | |
{ | |
return GET_THREAD()->parse_in_eval < 0; | |
} | |
static int | |
caller_location(VALUE *path, VALUE *absolute_path) | |
{ | |
const rb_thread_t *const th = GET_THREAD(); | |
const rb_control_frame_t *const cfp = | |
rb_vm_get_ruby_level_next_cfp(th, th->cfp); | |
if (cfp) { | |
int line = rb_vm_get_sourceline(cfp); | |
*path = cfp->iseq->location.path; | |
*absolute_path = cfp->iseq->location.absolute_path; | |
return line; | |
} | |
else { | |
*path = rb_str_new2("<compiled>"); | |
*absolute_path = *path; | |
return 1; | |
} | |
} | |
typedef struct { | |
VALUE arg; | |
rb_insn_func_t func; | |
int line; | |
} accessor_args; | |
static VALUE | |
method_for_self(VALUE name, VALUE arg, rb_insn_func_t func, | |
VALUE (*build)(rb_iseq_t *, LINK_ANCHOR *, VALUE)) | |
{ | |
VALUE path, absolute_path; | |
accessor_args acc; | |
acc.arg = arg; | |
acc.func = func; | |
acc.line = caller_location(&path, &absolute_path); | |
return rb_iseq_new_with_opt(NEW_IFUNC(build, (VALUE)&acc), | |
rb_sym2str(name), path, absolute_path, | |
INT2FIX(acc.line), 0, ISEQ_TYPE_METHOD, 0); | |
} | |
static VALUE | |
for_self_aref(rb_iseq_t *iseq, LINK_ANCHOR *ret, VALUE a) | |
{ | |
const accessor_args *const args = (void *)a; | |
const int line = args->line; | |
iseq_set_local_table(iseq, 0); | |
iseq->param.lead_num = 0; | |
iseq->param.size = 0; | |
ADD_INSN1(ret, line, putobject, args->arg); | |
ADD_INSN1(ret, line, opt_call_c_function, (VALUE)args->func); | |
return Qnil; | |
} | |
static VALUE | |
for_self_aset(rb_iseq_t *iseq, LINK_ANCHOR *ret, VALUE a) | |
{ | |
const accessor_args *const args = (void *)a; | |
const int line = args->line; | |
static const ID vars[] = {1, idUScore}; | |
iseq_set_local_table(iseq, vars); | |
iseq->param.lead_num = 1; | |
iseq->param.size = 1; | |
ADD_INSN2(ret, line, getlocal, INT2FIX(numberof(vars)-0), INT2FIX(0)); | |
ADD_INSN1(ret, line, putobject, args->arg); | |
ADD_INSN1(ret, line, opt_call_c_function, (VALUE)args->func); | |
ADD_INSN(ret, line, pop); | |
return Qnil; | |
} | |
/* | |
* func (index) -> (value) | |
*/ | |
VALUE | |
rb_method_for_self_aref(VALUE name, VALUE arg, rb_insn_func_t func) | |
{ | |
return method_for_self(name, arg, func, for_self_aref); | |
} | |
/* | |
* func (index, value) -> (index, value) | |
*/ | |
VALUE | |
rb_method_for_self_aset(VALUE name, VALUE arg, rb_insn_func_t func) | |
{ | |
return method_for_self(name, arg, func, for_self_aset); | |
} |