From fcbf63e62c627deae76c1b8cb8c0876c536ed811 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Mon, 16 Mar 2020 18:49:26 +0900 Subject: Fresh start --- jni/ruby/compile.c | 6337 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 6337 insertions(+) create mode 100644 jni/ruby/compile.c (limited to 'jni/ruby/compile.c') diff --git a/jni/ruby/compile.c b/jni/ruby/compile.c new file mode 100644 index 0000000..3baf0f3 --- /dev/null +++ b/jni/ruby/compile.c @@ -0,0 +1,6337 @@ +/********************************************************************** + + compile.c - ruby node tree -> VM instruction sequence + + $Author: nagachika $ + created at: 04/01/01 03:42:15 JST + + Copyright (C) 2004-2007 Koichi Sasada + +**********************************************************************/ + +#include "internal.h" +#include + +#define USE_INSN_STACK_INCREASE 1 +#include "vm_core.h" +#include "iseq.h" +#include "insns.inc" +#include "insns_info.inc" + +#ifdef HAVE_DLADDR +# include +#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)); + VALUE *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] = 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; ind_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_ARRAY && !node->nd_head) { + kw = node->nd_next; + node = 0; + if (kw) { + opt_p = 0; + 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; ind_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; ind_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; iparent_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; jlocal_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; jlocal_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) { + if (!node->nd_nth) { + ADD_INSN(ret, line, putnil); + break; + } + 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("", 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("", 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, "", 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, "", 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, "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, ""); + 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(%4d)\n", pos, StringValueCStr(str), iobj->line_no); + pos += insn_data_length(iobj); + break; + } + case ISEQ_ELEMENT_LABEL: + { + lobj = (LABEL *)link; + printf("\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; iself, 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; iself, 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(VALUE) * (len - 1); + + kw_arg = xmalloc(n); + kw_arg->keyword_len = len; + for (i = 0; i < len; i++) { + VALUE kw = RARRAY_AREF(vkw_arg, i); + SYM2ID(kw); /* make immortal */ + kw_arg->keywords[i] = kw; + } + } + } + + 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; ilocation.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= 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; iparam.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; ilocal_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(""); + *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); +} -- cgit v1.2.3