/**********************************************************************

  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 <math.h>

#define USE_INSN_STACK_INCREASE 1
#include "vm_core.h"
#include "iseq.h"
#include "insns.inc"
#include "insns_info.inc"

#ifdef HAVE_DLADDR
# include <dlfcn.h>
#endif

#define FIXNUM_INC(n, i) ((n)+(INT2FIX(i)&~FIXNUM_FLAG))
#define FIXNUM_OR(n, i) ((n)|INT2FIX(i))

typedef struct iseq_link_element {
    enum {
	ISEQ_ELEMENT_NONE,
	ISEQ_ELEMENT_LABEL,
	ISEQ_ELEMENT_INSN,
	ISEQ_ELEMENT_ADJUST
    } type;
    struct iseq_link_element *next;
    struct iseq_link_element *prev;
} LINK_ELEMENT;

typedef struct iseq_link_anchor {
    LINK_ELEMENT anchor;
    LINK_ELEMENT *last;
} LINK_ANCHOR;

typedef struct iseq_label_data {
    LINK_ELEMENT link;
    int label_no;
    int position;
    int sc_state;
    int set;
    int sp;
} LABEL;

typedef struct iseq_insn_data {
    LINK_ELEMENT link;
    enum ruby_vminsn_type insn_id;
    unsigned int line_no;
    int operand_size;
    int sc_state;
    VALUE *operands;
} INSN;

typedef struct iseq_adjust_data {
    LINK_ELEMENT link;
    LABEL *label;
    int line_no;
} ADJUST;

struct ensure_range {
    LABEL *begin;
    LABEL *end;
    struct ensure_range *next;
};

struct iseq_compile_data_ensure_node_stack {
    NODE *ensure_node;
    struct iseq_compile_data_ensure_node_stack *prev;
    struct ensure_range *erange;
};

/**
 * debug function(macro) interface depend on CPDEBUG
 * if it is less than 0, runtime option is in effect.
 *
 * debug level:
 *  0: no debug output
 *  1: show node type
 *  2: show node important parameters
 *  ...
 *  5: show other parameters
 * 10: show every AST array
 */

#ifndef CPDEBUG
#define CPDEBUG 0
#endif

#if CPDEBUG >= 0
#define compile_debug CPDEBUG
#else
#define compile_debug iseq->compile_data->option->debug_level
#endif

#if CPDEBUG

#define compile_debug_print_indent(level) \
    ruby_debug_print_indent((level), compile_debug, gl_node_level * 2)

#define debugp(header, value) (void) \
  (compile_debug_print_indent(1) && \
   ruby_debug_print_value(1, compile_debug, (header), (value)))

#define debugi(header, id)  (void) \
  (compile_debug_print_indent(1) && \
   ruby_debug_print_id(1, compile_debug, (header), (id)))

#define debugp_param(header, value)  (void) \
  (compile_debug_print_indent(1) && \
   ruby_debug_print_value(1, compile_debug, (header), (value)))

#define debugp_verbose(header, value)  (void) \
  (compile_debug_print_indent(2) && \
   ruby_debug_print_value(2, compile_debug, (header), (value)))

#define debugp_verbose_node(header, value)  (void) \
  (compile_debug_print_indent(10) && \
   ruby_debug_print_value(10, compile_debug, (header), (value)))

#define debug_node_start(node)  ((void) \
  (compile_debug_print_indent(1) && \
   (ruby_debug_print_node(1, CPDEBUG, "", (NODE *)(node)), gl_node_level)), \
   gl_node_level++)

#define debug_node_end()  gl_node_level --

#else

static inline ID
r_id(ID id)
{
    return id;
}

static inline VALUE
r_value(VALUE value)
{
    return value;
}

#define debugi(header, id)                 r_id(id)
#define debugp(header, value)              r_value(value)
#define debugp_verbose(header, value)      r_value(value)
#define debugp_verbose_node(header, value) r_value(value)
#define debugp_param(header, value)        r_value(value)
#define debug_node_start(node)             ((void)0)
#define debug_node_end()                   ((void)0)
#endif

#if CPDEBUG > 1 || CPDEBUG < 0
#define debugs if (compile_debug_print_indent(1)) ruby_debug_printf
#define debug_compile(msg, v) ((void)(compile_debug_print_indent(1) && fputs((msg), stderr)), (v))
#else
#define debugs                             if(0)printf
#define debug_compile(msg, v) (v)
#endif


/* create new label */
#define NEW_LABEL(l) new_label_body(iseq, (l))

#define iseq_path(iseq) \
  (((rb_iseq_t*)DATA_PTR(iseq))->location.path)

#define iseq_absolute_path(iseq) \
  (((rb_iseq_t*)DATA_PTR(iseq))->location.absolute_path)

#define NEW_ISEQVAL(node, name, type, line_no)       \
  new_child_iseq(iseq, (node), rb_fstring(name), 0, (type), (line_no))

#define NEW_CHILD_ISEQVAL(node, name, type, line_no)       \
  new_child_iseq(iseq, (node), rb_fstring(name), iseq->self, (type), (line_no))

/* add instructions */
#define ADD_SEQ(seq1, seq2) \
  APPEND_LIST((seq1), (seq2))

/* add an instruction */
#define ADD_INSN(seq, line, insn) \
  ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_body(iseq, (line), BIN(insn), 0))

/* add an instruction with some operands (1, 2, 3, 5) */
#define ADD_INSN1(seq, line, insn, op1) \
  ADD_ELEM((seq), (LINK_ELEMENT *) \
           new_insn_body(iseq, (line), BIN(insn), 1, (VALUE)(op1)))

/* add an instruction with label operand (alias of ADD_INSN1) */
#define ADD_INSNL(seq, line, insn, label) ADD_INSN1(seq, line, insn, label)

#define ADD_INSN2(seq, line, insn, op1, op2) \
  ADD_ELEM((seq), (LINK_ELEMENT *) \
           new_insn_body(iseq, (line), BIN(insn), 2, (VALUE)(op1), (VALUE)(op2)))

#define ADD_INSN3(seq, line, insn, op1, op2, op3) \
  ADD_ELEM((seq), (LINK_ELEMENT *) \
           new_insn_body(iseq, (line), BIN(insn), 3, (VALUE)(op1), (VALUE)(op2), (VALUE)(op3)))

/* Specific Insn factory */
#define ADD_SEND(seq, line, id, argc) \
  ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(0), NULL)

#define ADD_SEND_WITH_FLAG(seq, line, id, argc, flag) \
  ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)(flag), NULL)

#define ADD_SEND_WITH_BLOCK(seq, line, id, argc, block) \
  ADD_SEND_R((seq), (line), (id), (argc), (VALUE)(block), (VALUE)INT2FIX(0), NULL)

#define ADD_CALL_RECEIVER(seq, line) \
  ADD_INSN((seq), (line), putself)

#define ADD_CALL(seq, line, id, argc) \
  ADD_SEND_R((seq), (line), (id), (argc), (VALUE)Qfalse, (VALUE)INT2FIX(VM_CALL_FCALL), NULL)

#define ADD_CALL_WITH_BLOCK(seq, line, id, argc, block) \
  ADD_SEND_R((seq), (line), (id), (argc), (block), (VALUE)INT2FIX(VM_CALL_FCALL), NULL)

#define ADD_SEND_R(seq, line, id, argc, block, flag, keywords) \
  ADD_ELEM((seq), (LINK_ELEMENT *) new_insn_send(iseq, (line), (id), (VALUE)(argc), (VALUE)(block), (VALUE)(flag), (keywords)))

#define ADD_TRACE(seq, line, event) \
  do { \
      if ((event) == RUBY_EVENT_LINE && iseq->coverage && \
	  (line) != iseq->compile_data->last_coverable_line) { \
	  RARRAY_ASET(iseq->coverage, (line) - 1, INT2FIX(0)); \
	  iseq->compile_data->last_coverable_line = (line); \
	  ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
      } \
      if (iseq->compile_data->option->trace_instruction) { \
	  ADD_INSN1((seq), (line), trace, INT2FIX(event)); \
      } \
  } while (0)

/* add label */
#define ADD_LABEL(seq, label) \
  ADD_ELEM((seq), (LINK_ELEMENT *) (label))

#define APPEND_LABEL(seq, before, label) \
  APPEND_ELEM((seq), (before), (LINK_ELEMENT *) (label))

#define ADD_ADJUST(seq, line, label) \
  ADD_ELEM((seq), (LINK_ELEMENT *) new_adjust_body(iseq, (label), (line)))

#define ADD_ADJUST_RESTORE(seq, label) \
  ADD_ELEM((seq), (LINK_ELEMENT *) new_adjust_body(iseq, (label), -1))

#define ADD_CATCH_ENTRY(type, ls, le, iseqv, lc)		\
    (rb_ary_push(iseq->compile_data->catch_table_ary,		\
		 rb_ary_new3(5, (type),				\
			     (VALUE)(ls) | 1, (VALUE)(le) | 1,	\
			     (VALUE)(iseqv), (VALUE)(lc) | 1)))

/* compile node */
#define COMPILE(anchor, desc, node) \
  (debug_compile("== " desc "\n", \
                 iseq_compile_each(iseq, (anchor), (node), 0)))

/* compile node, this node's value will be popped */
#define COMPILE_POPED(anchor, desc, node)    \
  (debug_compile("== " desc "\n", \
                 iseq_compile_each(iseq, (anchor), (node), 1)))

/* compile node, which is popped when 'poped' is true */
#define COMPILE_(anchor, desc, node, poped)  \
  (debug_compile("== " desc "\n", \
                 iseq_compile_each(iseq, (anchor), (node), (poped))))

#define COMPILE_RECV(anchor, desc, node) \
    (private_recv_p(node) ? \
     (ADD_INSN(anchor, nd_line(node), putself), VM_CALL_FCALL) : \
     (COMPILE(anchor, desc, node->nd_recv), 0))

#define OPERAND_AT(insn, idx) \
  (((INSN*)(insn))->operands[(idx)])

#define INSN_OF(insn) \
  (((INSN*)(insn))->insn_id)

/* error */
#define COMPILE_ERROR(strs)                        \
{                                                  \
  VALUE tmp = GET_THREAD()->errinfo;               \
  if (compile_debug) rb_compile_bug strs;          \
  GET_THREAD()->errinfo = iseq->compile_data->err_info;  \
  rb_compile_error strs;                           \
  RB_OBJ_WRITE(iseq->self, &iseq->compile_data->err_info, GET_THREAD()->errinfo); \
  GET_THREAD()->errinfo = tmp;                     \
  ret = 0;                                         \
  break;                                           \
}

#define ERROR_ARGS ruby_sourcefile, nd_line(node),


#define COMPILE_OK 1
#define COMPILE_NG 0


/* leave name uninitialized so that compiler warn if INIT_ANCHOR is
 * missing */
#define DECL_ANCHOR(name) \
  LINK_ANCHOR *name, name##_body__ = {{0,},}
#define INIT_ANCHOR(name) \
  (name##_body__.last = &name##_body__.anchor, name = &name##_body__)

#define hide_obj(obj) do {OBJ_FREEZE(obj); RBASIC_CLEAR_CLASS(obj);} while (0)

#include "optinsn.inc"
#if OPT_INSTRUCTIONS_UNIFICATION
#include "optunifs.inc"
#endif

/* for debug */
#if CPDEBUG < 0
#define ISEQ_ARG iseq,
#define ISEQ_ARG_DECLARE rb_iseq_t *iseq,
#else
#define ISEQ_ARG
#define ISEQ_ARG_DECLARE
#endif

#if CPDEBUG
#define gl_node_level iseq->compile_data->node_level
#endif

static void dump_disasm_list(LINK_ELEMENT *elem);

static int insn_data_length(INSN *iobj);
static int calc_sp_depth(int depth, INSN *iobj);

static INSN *new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int argc, ...);
static LABEL *new_label_body(rb_iseq_t *iseq, long line);
static ADJUST *new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line);

static int iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * n, int);
static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
static int iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
static int iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor);

static int iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl);
static int iseq_set_exception_local_table(rb_iseq_t *iseq);
static int iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *anchor, NODE * node);

static int iseq_set_sequence_stackcaching(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *anchor);
static int iseq_set_exception_table(rb_iseq_t *iseq);
static int iseq_set_optargs_table(rb_iseq_t *iseq);

/*
 * To make Array to LinkedList, use link_anchor
 */

static void
verify_list(ISEQ_ARG_DECLARE const char *info, LINK_ANCHOR *anchor)
{
#if CPDEBUG
    int flag = 0;
    LINK_ELEMENT *list, *plist;

    if (!compile_debug) return;

    list = anchor->anchor.next;
    plist = &anchor->anchor;
    while (list) {
	if (plist != list->prev) {
	    flag += 1;
	}
	plist = list;
	list = list->next;
    }

    if (anchor->last != plist && anchor->last != 0) {
	flag |= 0x70000;
    }

    if (flag != 0) {
	rb_bug("list verify error: %08x (%s)", flag, info);
    }
#endif
}
#if CPDEBUG < 0
#define verify_list(info, anchor) verify_list(iseq, (info), (anchor))
#endif

/*
 * elem1, elem2 => elem1, elem2, elem
 */
static void
ADD_ELEM(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor, LINK_ELEMENT *elem)
{
    elem->prev = anchor->last;
    anchor->last->next = elem;
    anchor->last = elem;
    verify_list("add", anchor);
}

/*
 * elem1, before, elem2 => elem1, before, elem, elem2
 */
static void
APPEND_ELEM(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor, LINK_ELEMENT *before, LINK_ELEMENT *elem)
{
    elem->prev = before;
    elem->next = before->next;
    elem->next->prev = elem;
    before->next = elem;
    if (before == anchor->last) anchor->last = elem;
    verify_list("add", anchor);
}
#if CPDEBUG < 0
#define ADD_ELEM(anchor, elem) ADD_ELEM(iseq, (anchor), (elem))
#define APPEND_ELEM(anchor, before, elem) ADD_ELEM(iseq, (anchor), (before), (elem))
#endif

static int
iseq_add_mark_object(rb_iseq_t *iseq, VALUE v)
{
    if (!SPECIAL_CONST_P(v)) {
	rb_iseq_add_mark_object(iseq, v);
    }
    return COMPILE_OK;
}

#define ruby_sourcefile		RSTRING_PTR(iseq->location.path)

static int
iseq_add_mark_object_compile_time(rb_iseq_t *iseq, VALUE v)
{
    if (!SPECIAL_CONST_P(v)) {
	rb_ary_push(iseq->compile_data->mark_ary, v);
    }
    return COMPILE_OK;
}

static int
validate_label(st_data_t name, st_data_t label, st_data_t arg)
{
    rb_iseq_t *iseq = (rb_iseq_t *)arg;
    LABEL *lobj = (LABEL *)label;
    if (!lobj->link.next) {
	do {
	    int ret;
	    COMPILE_ERROR((ruby_sourcefile, lobj->position,
			   "%"PRIsVALUE": undefined label",
			   rb_id2str((ID)name)));
	    if (ret) break;
	} while (0);
    }
    return ST_CONTINUE;
}

static void
validate_labels(rb_iseq_t *iseq, st_table *labels_table)
{
    st_foreach(labels_table, validate_label, (st_data_t)iseq);
    if (!NIL_P(iseq->compile_data->err_info)) {
	rb_exc_raise(iseq->compile_data->err_info);
    }
}

VALUE
rb_iseq_compile_node(VALUE self, NODE *node)
{
    DECL_ANCHOR(ret);
    rb_iseq_t *iseq;
    INIT_ANCHOR(ret);
    GetISeqPtr(self, iseq);

    if (node == 0) {
	COMPILE(ret, "nil", node);
	iseq_set_local_table(iseq, 0);
    }
    else if (nd_type(node) == NODE_SCOPE) {
	/* iseq type of top, method, class, block */
	iseq_set_local_table(iseq, node->nd_tbl);
	iseq_set_arguments(iseq, ret, node->nd_args);

	switch (iseq->type) {
	  case ISEQ_TYPE_BLOCK:
	    {
		LABEL *start = iseq->compile_data->start_label = NEW_LABEL(0);
		LABEL *end = iseq->compile_data->end_label = NEW_LABEL(0);

		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_B_CALL);
		ADD_LABEL(ret, start);
		COMPILE(ret, "block body", node->nd_body);
		ADD_LABEL(ret, end);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_B_RETURN);

		/* wide range catch handler must put at last */
		ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, 0, start);
		ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, 0, end);
		break;
	    }
	  case ISEQ_TYPE_CLASS:
	    {
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CLASS);
		COMPILE(ret, "scoped node", node->nd_body);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_END);
		break;
	    }
	  case ISEQ_TYPE_METHOD:
	    {
		ADD_TRACE(ret, FIX2INT(iseq->location.first_lineno), RUBY_EVENT_CALL);
		COMPILE(ret, "scoped node", node->nd_body);
		ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN);
		break;
	    }
	  default: {
	    COMPILE(ret, "scoped node", node->nd_body);
	    break;
	  }
	}
    }
    else if (nd_type(node) == NODE_IFUNC) {
	/* user callback */
	(*node->nd_cfnc)(iseq, ret, node->nd_tval);
    }
    else {
	switch (iseq->type) {
	  case ISEQ_TYPE_METHOD:
	  case ISEQ_TYPE_CLASS:
	  case ISEQ_TYPE_BLOCK:
	  case ISEQ_TYPE_EVAL:
	  case ISEQ_TYPE_MAIN:
	  case ISEQ_TYPE_TOP:
	    rb_compile_error(ERROR_ARGS "compile/should not be reached: %s:%d",
			     __FILE__, __LINE__);
	    break;
	  case ISEQ_TYPE_RESCUE:
	    iseq_set_exception_local_table(iseq);
	    COMPILE(ret, "rescue", node);
	    break;
	  case ISEQ_TYPE_ENSURE:
	    iseq_set_exception_local_table(iseq);
	    COMPILE_POPED(ret, "ensure", node);
	    break;
	  case ISEQ_TYPE_DEFINED_GUARD:
	    iseq_set_local_table(iseq, 0);
	    COMPILE(ret, "defined guard", node);
	    break;
	  default:
	    rb_bug("unknown scope");
	}
    }

    if (iseq->type == ISEQ_TYPE_RESCUE || iseq->type == ISEQ_TYPE_ENSURE) {
	ADD_INSN2(ret, 0, getlocal, INT2FIX(2), INT2FIX(0));
	ADD_INSN1(ret, 0, throw, INT2FIX(0) /* continue throw */ );
    }
    else {
	ADD_INSN(ret, iseq->compile_data->last_line, leave);
    }

#if SUPPORT_JOKE
    if (iseq->compile_data->labels_table) {
	validate_labels(iseq, iseq->compile_data->labels_table);
    }
#endif
    return iseq_setup(iseq, ret);
}

int
rb_iseq_translate_threaded_code(rb_iseq_t *iseq)
{
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
    const void * const *table = rb_vm_get_insns_address_table();
    unsigned int i;

    for (i = 0; i < iseq->iseq_size; /* */ ) {
	int insn = (int)iseq->iseq_encoded[i];
	int len = insn_len(insn);
	iseq->iseq_encoded[i] = (VALUE)table[insn];
	i += len;
    }
#endif
    return COMPILE_OK;
}

#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
static int
rb_vm_insn_addr2insn(const void *addr) /* cold path */
{
    int insn;
    const void * const *table = rb_vm_get_insns_address_table();

    for (insn = 0; insn < VM_INSTRUCTION_SIZE; insn++) {
	if (table[insn] == addr) {
	    return insn;
	}
    }
    rb_bug("rb_vm_insn_addr2insn: invalid insn address: %p", addr);
}
#endif

VALUE *
rb_iseq_original_iseq(rb_iseq_t *iseq) /* cold path */
{
    if (iseq->iseq) return iseq->iseq;

    iseq->iseq = ALLOC_N(VALUE, iseq->iseq_size);

    MEMCPY(iseq->iseq, iseq->iseq_encoded, VALUE, iseq->iseq_size);

#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
    {
	unsigned int i;

	for (i = 0; i < iseq->iseq_size; /* */ ) {
	    const void *addr = (const void *)iseq->iseq[i];
	    const int insn = rb_vm_insn_addr2insn(addr);

	    iseq->iseq[i] = insn;
	    i += insn_len(insn);
	}
    }
#endif
    return iseq->iseq;
}

/*********************************************/
/* definition of data structure for compiler */
/*********************************************/

/*
 * On 32-bit SPARC, GCC by default generates SPARC V7 code that may require
 * 8-byte word alignment. On the other hand, Oracle Solaris Studio seems to
 * generate SPARCV8PLUS code with unaligned memory accesss instructions.
 * That is why the STRICT_ALIGNMENT is defined only with GCC.
 */
#if defined(__sparc) && SIZEOF_VOIDP == 4 && defined(__GNUC__)
  #define STRICT_ALIGNMENT
#endif

#ifdef STRICT_ALIGNMENT
  #if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE
    #define ALIGNMENT_SIZE SIZEOF_LONG_LONG
  #else
    #define ALIGNMENT_SIZE SIZEOF_VALUE
  #endif
  #define PADDING_SIZE_MAX    ((size_t)((ALIGNMENT_SIZE) - 1))
  #define ALIGNMENT_SIZE_MASK PADDING_SIZE_MAX
  /* Note: ALIGNMENT_SIZE == (2 ** N) is expected. */
#else
  #define PADDING_SIZE_MAX 0
#endif /* STRICT_ALIGNMENT */

#ifdef STRICT_ALIGNMENT
/* calculate padding size for aligned memory access */
static size_t
calc_padding(void *ptr, size_t size)
{
    size_t mis;
    size_t padding = 0;

    mis = (size_t)ptr & ALIGNMENT_SIZE_MASK;
    if (mis > 0) {
        padding = ALIGNMENT_SIZE - mis;
    }
/*
 * On 32-bit sparc or equivalents, when a single VALUE is requested
 * and padding == sizeof(VALUE), it is clear that no padding is needed.
 */
#if ALIGNMENT_SIZE > SIZEOF_VALUE
    if (size == sizeof(VALUE) && padding == sizeof(VALUE)) {
        padding = 0;
    }
#endif

    return padding;
}
#endif /* STRICT_ALIGNMENT */

static void *
compile_data_alloc(rb_iseq_t *iseq, size_t size)
{
    void *ptr = 0;
    struct iseq_compile_data_storage *storage =
	iseq->compile_data->storage_current;
#ifdef STRICT_ALIGNMENT
    size_t padding = calc_padding((void *)&storage->buff[storage->pos], size);
#else
    const size_t padding = 0; /* expected to be optimized by compiler */
#endif /* STRICT_ALIGNMENT */

    if (size >= INT_MAX - padding) rb_memerror();
    if (storage->pos + size + padding > storage->size) {
	unsigned int alloc_size = storage->size;

	while (alloc_size < size + PADDING_SIZE_MAX) {
	    if (alloc_size >= INT_MAX / 2) rb_memerror();
	    alloc_size *= 2;
	}
	storage->next = (void *)ALLOC_N(char, alloc_size +
					SIZEOF_ISEQ_COMPILE_DATA_STORAGE);
	storage = iseq->compile_data->storage_current = storage->next;
	storage->next = 0;
	storage->pos = 0;
	storage->size = alloc_size;
#ifdef STRICT_ALIGNMENT
        padding = calc_padding((void *)&storage->buff[storage->pos], size);
#endif /* STRICT_ALIGNMENT */
    }

#ifdef STRICT_ALIGNMENT
    storage->pos += (int)padding;
#endif /* STRICT_ALIGNMENT */

    ptr = (void *)&storage->buff[storage->pos];
    storage->pos += (int)size;
    return ptr;
}

static INSN *
compile_data_alloc_insn(rb_iseq_t *iseq)
{
    return (INSN *)compile_data_alloc(iseq, sizeof(INSN));
}

static LABEL *
compile_data_alloc_label(rb_iseq_t *iseq)
{
    return (LABEL *)compile_data_alloc(iseq, sizeof(LABEL));
}

static ADJUST *
compile_data_alloc_adjust(rb_iseq_t *iseq)
{
    return (ADJUST *)compile_data_alloc(iseq, sizeof(ADJUST));
}

/*
 * elem1, elemX => elem1, elem2, elemX
 */
static void
INSERT_ELEM_NEXT(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2)
{
    elem2->next = elem1->next;
    elem2->prev = elem1;
    elem1->next = elem2;
    if (elem2->next) {
	elem2->next->prev = elem2;
    }
}

/*
 * elemX, elem1, elemY => elemX, elem2, elemY
 */
static void
REPLACE_ELEM(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2)
{
    elem2->prev = elem1->prev;
    elem2->next = elem1->next;
    if (elem1->prev) {
	elem1->prev->next = elem2;
    }
    if (elem1->next) {
	elem1->next->prev = elem2;
    }
}

static void
REMOVE_ELEM(LINK_ELEMENT *elem)
{
    elem->prev->next = elem->next;
    if (elem->next) {
	elem->next->prev = elem->prev;
    }
}

static LINK_ELEMENT *
FIRST_ELEMENT(LINK_ANCHOR *anchor)
{
    return anchor->anchor.next;
}

static LINK_ELEMENT *
POP_ELEMENT(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor)
{
    LINK_ELEMENT *elem = anchor->last;
    anchor->last = anchor->last->prev;
    anchor->last->next = 0;
    verify_list("pop", anchor);
    return elem;
}
#if CPDEBUG < 0
#define POP_ELEMENT(anchor) POP_ELEMENT(iseq, (anchor))
#endif

static int
LIST_SIZE_ZERO(LINK_ANCHOR *anchor)
{
    if (anchor->anchor.next == 0) {
	return 1;
    }
    else {
	return 0;
    }
}

/*
 * anc1: e1, e2, e3
 * anc2: e4, e5
 *#=>
 * anc1: e1, e2, e3, e4, e5
 * anc2: e4, e5 (broken)
 */
static void
APPEND_LIST(ISEQ_ARG_DECLARE LINK_ANCHOR *anc1, LINK_ANCHOR *anc2)
{
    if (anc2->anchor.next) {
	anc1->last->next = anc2->anchor.next;
	anc2->anchor.next->prev = anc1->last;
	anc1->last = anc2->last;
    }
    verify_list("append", anc1);
}
#if CPDEBUG < 0
#define APPEND_LIST(anc1, anc2) APPEND_LIST(iseq, (anc1), (anc2))
#endif

/*
 * anc1: e1, e2, e3
 * anc2: e4, e5
 *#=>
 * anc1: e4, e5, e1, e2, e3
 * anc2: e4, e5 (broken)
 */
static void
INSERT_LIST(ISEQ_ARG_DECLARE LINK_ANCHOR *anc1, LINK_ANCHOR *anc2)
{
    if (anc2->anchor.next) {
	LINK_ELEMENT *first = anc1->anchor.next;
	anc1->anchor.next = anc2->anchor.next;
	anc1->anchor.next->prev = &anc1->anchor;
	anc2->last->next = first;
	if (first) {
	    first->prev = anc2->last;
	}
	else {
	    anc1->last = anc2->last;
	}
    }

    verify_list("append", anc1);
}
#if CPDEBUG < 0
#define INSERT_LIST(anc1, anc2) INSERT_LIST(iseq, (anc1), (anc2))
#endif

#if CPDEBUG && 0
static void
debug_list(ISEQ_ARG_DECLARE LINK_ANCHOR *anchor)
{
    LINK_ELEMENT *list = FIRST_ELEMENT(anchor);
    printf("----\n");
    printf("anch: %p, frst: %p, last: %p\n", &anchor->anchor,
	   anchor->anchor.next, anchor->last);
    while (list) {
	printf("curr: %p, next: %p, prev: %p, type: %d\n", list, list->next,
	       list->prev, FIX2INT(list->type));
	list = list->next;
    }
    printf("----\n");

    dump_disasm_list(anchor->anchor.next);
    verify_list("debug list", anchor);
}
#if CPDEBUG < 0
#define debug_list(anc) debug_list(iseq, (anc))
#endif
#endif

static LABEL *
new_label_body(rb_iseq_t *iseq, long line)
{
    LABEL *labelobj = compile_data_alloc_label(iseq);

    labelobj->link.type = ISEQ_ELEMENT_LABEL;
    labelobj->link.next = 0;

    labelobj->label_no = iseq->compile_data->label_no++;
    labelobj->sc_state = 0;
    labelobj->sp = -1;
    return labelobj;
}

static ADJUST *
new_adjust_body(rb_iseq_t *iseq, LABEL *label, int line)
{
    ADJUST *adjust = compile_data_alloc_adjust(iseq);
    adjust->link.type = ISEQ_ELEMENT_ADJUST;
    adjust->link.next = 0;
    adjust->label = label;
    adjust->line_no = line;
    return adjust;
}

static INSN *
new_insn_core(rb_iseq_t *iseq, int line_no,
	      int insn_id, int argc, VALUE *argv)
{
    INSN *iobj = compile_data_alloc_insn(iseq);
    /* printf("insn_id: %d, line: %d\n", insn_id, line_no); */

    iobj->link.type = ISEQ_ELEMENT_INSN;
    iobj->link.next = 0;
    iobj->insn_id = insn_id;
    iobj->line_no = line_no;
    iobj->operands = argv;
    iobj->operand_size = argc;
    iobj->sc_state = 0;
    return iobj;
}

static INSN *
new_insn_body(rb_iseq_t *iseq, int line_no, enum ruby_vminsn_type insn_id, int argc, ...)
{
    VALUE *operands = 0;
    va_list argv;
    if (argc > 0) {
	int i;
	va_init_list(argv, argc);
	operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * argc);
	for (i = 0; i < argc; i++) {
	    VALUE v = va_arg(argv, VALUE);
	    operands[i] = v;
	}
	va_end(argv);
    }
    return new_insn_core(iseq, line_no, insn_id, argc, operands);
}

static rb_call_info_t *
new_callinfo(rb_iseq_t *iseq, ID mid, int argc, VALUE block, unsigned int flag, rb_call_info_kw_arg_t *kw_arg)
{
    rb_call_info_t *ci = (rb_call_info_t *)compile_data_alloc(iseq, sizeof(rb_call_info_t));

    ci->mid = mid;
    ci->flag = flag;
    ci->orig_argc = argc;
    ci->argc = argc;
    ci->kw_arg = kw_arg;

    if (kw_arg) {
	ci->argc += kw_arg->keyword_len;
	ci->orig_argc += kw_arg->keyword_len;
    }

    if (block) {
	GetISeqPtr(block, ci->blockiseq);
    }
    else {
	ci->blockiseq = 0;
    }

    if (!(ci->flag & (VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG)) &&
	ci->blockiseq == NULL && ci->kw_arg == NULL) {
	ci->flag |= VM_CALL_ARGS_SIMPLE;
    }

    ci->method_state = 0;
    ci->class_serial = 0;
    ci->blockptr = 0;
    ci->recv = Qundef;
    ci->call = 0; /* TODO: should set default function? */

    ci->aux.index = iseq->callinfo_size++;

    return ci;
}

static INSN *
new_insn_send(rb_iseq_t *iseq, int line_no, ID id, VALUE argc, VALUE block, VALUE flag, rb_call_info_kw_arg_t *keywords)
{
    VALUE *operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * 1);
    operands[0] = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), block, FIX2INT(flag), keywords);
    return new_insn_core(iseq, line_no, BIN(send), 1, operands);
}

static VALUE
new_child_iseq(rb_iseq_t *iseq, NODE *node,
	       VALUE name, VALUE parent, enum iseq_type type, int line_no)
{
    VALUE ret;

    debugs("[new_child_iseq]> ---------------------------------------\n");
    ret = rb_iseq_new_with_opt(node, name,
			       iseq_path(iseq->self), iseq_absolute_path(iseq->self),
			       INT2FIX(line_no), parent, type, iseq->compile_data->option);
    debugs("[new_child_iseq]< ---------------------------------------\n");
    iseq_add_mark_object(iseq, ret);
    return ret;
}

static int
iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
    /* debugs("[compile step 2] (iseq_array_to_linkedlist)\n"); */

    if (compile_debug > 5)
	dump_disasm_list(FIRST_ELEMENT(anchor));

    debugs("[compile step 3.1 (iseq_optimize)]\n");
    iseq_optimize(iseq, anchor);

    if (compile_debug > 5)
	dump_disasm_list(FIRST_ELEMENT(anchor));

    if (iseq->compile_data->option->instructions_unification) {
	debugs("[compile step 3.2 (iseq_insns_unification)]\n");
	iseq_insns_unification(iseq, anchor);
	if (compile_debug > 5)
	    dump_disasm_list(FIRST_ELEMENT(anchor));
    }

    if (iseq->compile_data->option->stack_caching) {
	debugs("[compile step 3.3 (iseq_set_sequence_stackcaching)]\n");
	iseq_set_sequence_stackcaching(iseq, anchor);
	if (compile_debug > 5)
	    dump_disasm_list(FIRST_ELEMENT(anchor));
    }

    debugs("[compile step 4.1 (iseq_set_sequence)]\n");
    iseq_set_sequence(iseq, anchor);
    if (compile_debug > 5)
	dump_disasm_list(FIRST_ELEMENT(anchor));

    debugs("[compile step 4.2 (iseq_set_exception_table)]\n");
    iseq_set_exception_table(iseq);

    debugs("[compile step 4.3 (set_optargs_table)] \n");
    iseq_set_optargs_table(iseq);

    debugs("[compile step 5 (iseq_translate_threaded_code)] \n");
    rb_iseq_translate_threaded_code(iseq);

    if (compile_debug > 1) {
	VALUE str = rb_iseq_disasm(iseq->self);
	printf("%s\n", StringValueCStr(str));
	fflush(stdout);
    }
    debugs("[compile step: finish]\n");

    return 0;
}

static int
iseq_set_exception_local_table(rb_iseq_t *iseq)
{
    ID id_dollar_bang;

    CONST_ID(id_dollar_bang, "#$!");
    iseq->local_table = (ID *)ALLOC_N(ID, 1);
    iseq->local_table_size = 1;
    iseq->local_size = iseq->local_table_size + 1;
    iseq->local_table[0] = id_dollar_bang;
    return COMPILE_OK;
}

static int
get_lvar_level(rb_iseq_t *iseq)
{
    int lev = 0;
    while (iseq != iseq->local_iseq) {
	lev++;
	iseq = iseq->parent_iseq;
    }
    return lev;
}

static int
get_dyna_var_idx_at_raw(rb_iseq_t *iseq, ID id)
{
    int i;

    for (i = 0; i < iseq->local_table_size; i++) {
	if (iseq->local_table[i] == id) {
	    return i;
	}
    }
    return -1;
}

static int
get_local_var_idx(rb_iseq_t *iseq, ID id)
{
    int idx = get_dyna_var_idx_at_raw(iseq->local_iseq, id);

    if (idx < 0) {
	rb_bug("get_local_var_idx: %d", idx);
    }

    return idx;
}

static int
get_dyna_var_idx(rb_iseq_t *iseq, ID id, int *level, int *ls)
{
    int lv = 0, idx = -1;

    while (iseq) {
	idx = get_dyna_var_idx_at_raw(iseq, id);
	if (idx >= 0) {
	    break;
	}
	iseq = iseq->parent_iseq;
	lv++;
    }

    if (idx < 0) {
	rb_bug("get_dyna_var_idx: -1");
    }

    *level = lv;
    *ls = iseq->local_size;
    return idx;
}

static void
iseq_calc_param_size(rb_iseq_t *iseq)
{
    if (iseq->param.flags.has_opt ||
	iseq->param.flags.has_post ||
	iseq->param.flags.has_rest ||
	iseq->param.flags.has_block ||
	iseq->param.flags.has_kw ||
	iseq->param.flags.has_kwrest) {

	if (iseq->param.flags.has_block) {
	    iseq->param.size = iseq->param.block_start + 1;
	}
	else if (iseq->param.flags.has_kwrest) {
	    iseq->param.size = iseq->param.keyword->rest_start + 1;
	}
	else if (iseq->param.flags.has_kw) {
	    iseq->param.size = iseq->param.keyword->bits_start + 1;
	}
	else if (iseq->param.flags.has_post) {
	    iseq->param.size = iseq->param.post_start + iseq->param.post_num;
	}
	else if (iseq->param.flags.has_rest) {
	    iseq->param.size = iseq->param.rest_start + 1;
	}
	else if (iseq->param.flags.has_opt) {
	    iseq->param.size = iseq->param.lead_num + iseq->param.opt_num;
	}
	else {
	    rb_bug("unreachable");
	}
    }
    else {
	iseq->param.size = iseq->param.lead_num;
    }
}

static int
iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *optargs, NODE *node_args)
{
    debugs("iseq_set_arguments: %s\n", node_args ? "" : "0");

    if (node_args) {
	struct rb_args_info *args = node_args->nd_ainfo;
	ID rest_id = 0;
	int last_comma = 0;
	ID block_id = 0;

	if (nd_type(node_args) != NODE_ARGS) {
	    rb_bug("iseq_set_arguments: NODE_ARGS is expected, but %s",
		   ruby_node_name(nd_type(node_args)));
	}


	iseq->param.lead_num = (int)args->pre_args_num;
	if (iseq->param.lead_num > 0) iseq->param.flags.has_lead = TRUE;
	debugs("  - argc: %d\n", iseq->param.lead_num);

	rest_id = args->rest_arg;
	if (rest_id == 1) {
	    last_comma = 1;
	    rest_id = 0;
	}
	block_id = args->block_arg;

	if (args->first_post_arg) {
	    iseq->param.post_start = get_dyna_var_idx_at_raw(iseq, args->first_post_arg);
	    iseq->param.post_num = args->post_args_num;
	    iseq->param.flags.has_post = TRUE;
	}

	if (args->opt_args) {
	    NODE *node = args->opt_args;
	    LABEL *label;
	    VALUE labels = rb_ary_tmp_new(1);
	    int i = 0, j;

	    while (node) {
		label = NEW_LABEL(nd_line(node));
		rb_ary_push(labels, (VALUE)label | 1);
		ADD_LABEL(optargs, label);
		COMPILE_POPED(optargs, "optarg", node->nd_body);
		node = node->nd_next;
		i += 1;
	    }

	    /* last label */
	    label = NEW_LABEL(nd_line(node_args));
	    rb_ary_push(labels, (VALUE)label | 1);
	    ADD_LABEL(optargs, label);

	    iseq->param.opt_num = i;
	    iseq->param.opt_table = ALLOC_N(VALUE, i+1);
	    MEMCPY(iseq->param.opt_table, RARRAY_CONST_PTR(labels), VALUE, i+1);
	    for (j = 0; j < i+1; j++) {
		iseq->param.opt_table[j] &= ~1;
	    }
	    rb_ary_clear(labels);

	    iseq->param.flags.has_opt = TRUE;
	}

	if (args->kw_args) {
	    NODE *node = args->kw_args;
	    const VALUE default_values = rb_ary_tmp_new(1);
	    const VALUE complex_mark = rb_str_tmp_new(0);
	    int kw = 0, rkw = 0, di = 0, i;

	    iseq->param.flags.has_kw = TRUE;
	    iseq->param.keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
	    iseq->param.keyword->bits_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid);

	    while (node) {
		NODE *val_node = node->nd_body->nd_value;
		VALUE dv;

		if (val_node == (NODE *)-1) {
		    ++rkw;
		}
		else {
		    switch (nd_type(val_node)) {
		      case NODE_LIT:
			dv = val_node->nd_lit;
			iseq_add_mark_object(iseq, dv);
			break;
		      case NODE_NIL:
			dv = Qnil;
			break;
		      case NODE_TRUE:
			dv = Qtrue;
			break;
		      case NODE_FALSE:
			dv = Qfalse;
			break;
		      default:
			COMPILE_POPED(optargs, "kwarg", node); /* nd_type(node) == NODE_KW_ARG */
			dv = complex_mark;
		    }

		    iseq->param.keyword->num = ++di;
		    rb_ary_push(default_values, dv);
		}

		kw++;
		node = node->nd_next;
	    }

	    iseq->param.keyword->num = kw;

	    if (args->kw_rest_arg->nd_cflag != 0) {
		iseq->param.keyword->rest_start =  get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_cflag);
		iseq->param.flags.has_kwrest = TRUE;
	    }
	    iseq->param.keyword->required_num = rkw;
	    iseq->param.keyword->table = &iseq->local_table[iseq->param.keyword->bits_start - iseq->param.keyword->num];
	    iseq->param.keyword->default_values = ALLOC_N(VALUE, RARRAY_LEN(default_values));

	    for (i = 0; i < RARRAY_LEN(default_values); i++) {
		VALUE dv = RARRAY_AREF(default_values, i);
		if (dv == complex_mark) dv = Qundef;
		iseq->param.keyword->default_values[i] = dv;
	    }
	}
	else if (args->kw_rest_arg) {
	    iseq->param.flags.has_kwrest = TRUE;
	    iseq->param.keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
	    iseq->param.keyword->rest_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid);
	}

	if (args->pre_init) { /* m_init */
	    COMPILE_POPED(optargs, "init arguments (m)", args->pre_init);
	}
	if (args->post_init) { /* p_init */
	    COMPILE_POPED(optargs, "init arguments (p)", args->post_init);
	}

	if (rest_id) {
	    iseq->param.rest_start = get_dyna_var_idx_at_raw(iseq, rest_id);
	    iseq->param.flags.has_rest = TRUE;
	    assert(iseq->param.rest_start != -1);

	    if (iseq->param.post_start == 0) { /* TODO: why that? */
		iseq->param.post_start = iseq->param.rest_start + 1;
	    }
	}

	if (block_id) {
	    iseq->param.block_start = get_dyna_var_idx_at_raw(iseq, block_id);
	    iseq->param.flags.has_block = TRUE;
	}

	iseq_calc_param_size(iseq);

	if (iseq->type == ISEQ_TYPE_BLOCK) {
	    if (iseq->param.flags.has_opt    == FALSE &&
		iseq->param.flags.has_post   == FALSE &&
		iseq->param.flags.has_rest   == FALSE &&
		iseq->param.flags.has_kw     == FALSE &&
		iseq->param.flags.has_kwrest == FALSE) {

		if (iseq->param.lead_num == 1 && last_comma == 0) {
		    /* {|a|} */
		    iseq->param.flags.ambiguous_param0 = TRUE;
		}
	    }
	}
    }

    return COMPILE_OK;
}

static int
iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl)
{
    int size;

    if (tbl) {
	size = (int)*tbl;
	tbl++;
    }
    else {
	size = 0;
    }

    if (size > 0) {
	iseq->local_table = (ID *)ALLOC_N(ID, size);
	MEMCPY(iseq->local_table, tbl, ID, size);
    }

    iseq->local_size = iseq->local_table_size = size;
    iseq->local_size += 1;
    /*
      if (lfp == dfp ) { // top, class, method
	  dfp[-1]: svar
      else {             // block
          dfp[-1]: cref
      }
     */

    debugs("iseq_set_local_table: %d, %d\n", iseq->local_size, iseq->local_table_size);
    return COMPILE_OK;
}

static int
cdhash_cmp(VALUE val, VALUE lit)
{
    if (val == lit) return 0;
    if (SPECIAL_CONST_P(lit)) {
	return val != lit;
    }
    if (SPECIAL_CONST_P(val) || BUILTIN_TYPE(val) != BUILTIN_TYPE(lit)) {
	return -1;
    }
    if (BUILTIN_TYPE(lit) == T_STRING) {
	return rb_str_hash_cmp(lit, val);
    }
    return !rb_eql(lit, val);
}

static st_index_t
cdhash_hash(VALUE a)
{
    if (SPECIAL_CONST_P(a)) return (st_index_t)a;
    if (RB_TYPE_P(a, T_STRING)) return rb_str_hash(a);
    {
	VALUE hval = rb_hash(a);
	return (st_index_t)FIX2LONG(hval);
    }
}

static const struct st_hash_type cdhash_type = {
    cdhash_cmp,
    cdhash_hash,
};

struct cdhash_set_label_struct {
    VALUE hash;
    int pos;
    int len;
};

static int
cdhash_set_label_i(VALUE key, VALUE val, void *ptr)
{
    struct cdhash_set_label_struct *data = (struct cdhash_set_label_struct *)ptr;
    LABEL *lobj = (LABEL *)(val & ~1);
    rb_hash_aset(data->hash, key, INT2FIX(lobj->position - (data->pos+data->len)));
    return ST_CONTINUE;
}

/**
  ruby insn object list -> raw instruction sequence
 */
static int
iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
    LABEL *lobj;
    INSN *iobj;
    struct iseq_line_info_entry *line_info_table;
    unsigned int last_line = 0;
    LINK_ELEMENT *list;
    VALUE *generated_iseq;

    int k, pos, sp, stack_max = 0, line = 0;

    /* set label position */
    list = FIRST_ELEMENT(anchor);
    k = pos = 0;
    while (list) {
	switch (list->type) {
	  case ISEQ_ELEMENT_INSN:
	    {
		iobj = (INSN *)list;
		line = iobj->line_no;
		pos += insn_data_length(iobj);
		k++;
		break;
	    }
	  case ISEQ_ELEMENT_LABEL:
	    {
		lobj = (LABEL *)list;
		lobj->position = pos;
		lobj->set = TRUE;
		break;
	    }
	  case ISEQ_ELEMENT_NONE:
	    {
		/* ignore */
		break;
	    }
	  case ISEQ_ELEMENT_ADJUST:
	    {
		ADJUST *adjust = (ADJUST *)list;
		if (adjust->line_no != -1) {
		    pos += 2 /* insn + 1 operand */;
		    k++;
		}
		break;
	    }
	  default:
	    dump_disasm_list(FIRST_ELEMENT(anchor));
	    dump_disasm_list(list);
	    rb_compile_error(RSTRING_PTR(iseq->location.path), line,
			     "error: set_sequence");
	    break;
	}
	list = list->next;
    }

    /* make instruction sequence */
    generated_iseq = ALLOC_N(VALUE, pos);
    line_info_table = ALLOC_N(struct iseq_line_info_entry, k);
    iseq->is_entries = ZALLOC_N(union iseq_inline_storage_entry, iseq->is_size);
    iseq->callinfo_entries = ALLOC_N(rb_call_info_t, iseq->callinfo_size);
    /* MEMZERO(iseq->callinfo_entries, rb_call_info_t, iseq->callinfo_size); */

    list = FIRST_ELEMENT(anchor);
    k = pos = sp = 0;

    while (list) {
	switch (list->type) {
	  case ISEQ_ELEMENT_INSN:
	    {
		int j, len, insn;
		const char *types;
		VALUE *operands;

		iobj = (INSN *)list;

		/* update sp */
		sp = calc_sp_depth(sp, iobj);
		if (sp > stack_max) {
		    stack_max = sp;
		}

		/* fprintf(stderr, "insn: %-16s, sp: %d\n", insn_name(iobj->insn_id), sp); */
		operands = iobj->operands;
		insn = iobj->insn_id;
		generated_iseq[pos] = insn;
		types = insn_op_types(insn);
		len = insn_len(insn);

		/* operand check */
		if (iobj->operand_size != len - 1) {
		    /* printf("operand size miss! (%d, %d)\n", iobj->operand_size, len); */
		    dump_disasm_list(list);
		    rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no,
				     "operand size miss! (%d for %d)",
				     iobj->operand_size, len - 1);
		    xfree(generated_iseq);
		    xfree(line_info_table);
		    return 0;
		}

		for (j = 0; types[j]; j++) {
		    char type = types[j];
		    /* printf("--> [%c - (%d-%d)]\n", type, k, j); */
		    switch (type) {
		      case TS_OFFSET:
			{
			    /* label(destination position) */
			    lobj = (LABEL *)operands[j];
			    if (!lobj->set) {
				rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no,
						 "unknown label");
			    }
			    if (lobj->sp == -1) {
				lobj->sp = sp;
			    }
			    generated_iseq[pos + 1 + j] = lobj->position - (pos + len);
			    break;
			}
		      case TS_CDHASH:
			{
			    VALUE map = operands[j];
			    struct cdhash_set_label_struct data;
                            data.hash = map;
                            data.pos = pos;
                            data.len = len;
			    rb_hash_foreach(map, cdhash_set_label_i, (VALUE)&data);

			    hide_obj(map);
			    generated_iseq[pos + 1 + j] = map;
			    break;
			}
		      case TS_LINDEX:
		      case TS_NUM:	/* ulong */
			generated_iseq[pos + 1 + j] = FIX2INT(operands[j]);
			break;
		      case TS_ISEQ:	/* iseq */
			{
			    VALUE v = operands[j];
			    rb_iseq_t *block = 0;
			    if (v) {
				GetISeqPtr(v, block);
			    }
			    generated_iseq[pos + 1 + j] = (VALUE)block;
			    break;
			}
		      case TS_VALUE:	/* VALUE */
			{
			    VALUE v = operands[j];
			    generated_iseq[pos + 1 + j] = v;
			    /* to mark ruby object */
			    iseq_add_mark_object(iseq, v);
			    break;
			}
		      case TS_IC: /* inline cache */
			{
			    int ic_index = FIX2INT(operands[j]);
			    IC ic = (IC)&iseq->is_entries[ic_index];
			    if (UNLIKELY(ic_index >= iseq->is_size)) {
				rb_bug("iseq_set_sequence: ic_index overflow: index: %d, size: %d", ic_index, iseq->is_size);
			    }
			    generated_iseq[pos + 1 + j] = (VALUE)ic;
			    break;
			}
		      case TS_CALLINFO: /* call info */
			{
			    rb_call_info_t *base_ci = (rb_call_info_t *)operands[j];
			    rb_call_info_t *ci = &iseq->callinfo_entries[base_ci->aux.index];
			    *ci = *base_ci;

			    if (UNLIKELY(base_ci->aux.index >= iseq->callinfo_size)) {
				rb_bug("iseq_set_sequence: ci_index overflow: index: %d, size: %d", base_ci->argc, iseq->callinfo_size);
			    }
			    generated_iseq[pos + 1 + j] = (VALUE)ci;
			    break;
			}
		      case TS_ID: /* ID */
			generated_iseq[pos + 1 + j] = SYM2ID(operands[j]);
			break;
		      case TS_GENTRY:
			{
			    struct rb_global_entry *entry =
				(struct rb_global_entry *)(operands[j] & (~1));
			    generated_iseq[pos + 1 + j] = (VALUE)entry;
			}
			break;
		      case TS_FUNCPTR:
			generated_iseq[pos + 1 + j] = operands[j];
			break;
		      default:
			rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no,
					 "unknown operand type: %c", type);
			xfree(generated_iseq);
			xfree(line_info_table);
			return 0;
		    }
		}
		if (last_line != iobj->line_no) {
		    line_info_table[k].line_no = last_line = iobj->line_no;
		    line_info_table[k].position = pos;
		    k++;
		}
		pos += len;
		break;
	    }
	  case ISEQ_ELEMENT_LABEL:
	    {
		lobj = (LABEL *)list;
		if (lobj->sp == -1) {
		    lobj->sp = sp;
		}
		else {
		    sp = lobj->sp;
		}
		break;
	    }
	  case ISEQ_ELEMENT_ADJUST:
	    {
		ADJUST *adjust = (ADJUST *)list;
		int orig_sp = sp;

		if (adjust->label) {
		    sp = adjust->label->sp;
		}
		else {
		    sp = 0;
		}

		if (adjust->line_no != -1) {
		    if (orig_sp - sp > 0) {
			if (last_line != (unsigned int)adjust->line_no) {
			    line_info_table[k].line_no = last_line = adjust->line_no;
			    line_info_table[k].position = pos;
			    k++;
			}
			generated_iseq[pos++] = BIN(adjuststack);
			generated_iseq[pos++] = orig_sp - sp;
		    }
		    else if (orig_sp - sp == 0) {
			/* jump to next insn */
			if (last_line != (unsigned int)adjust->line_no) {
			    line_info_table[k].line_no = last_line = adjust->line_no;
			    line_info_table[k].position = pos;
			    k++;
			}
			generated_iseq[pos++] = BIN(nop);
			generated_iseq[pos++] = BIN(nop);
		    }
		    else {
			rb_bug("iseq_set_sequence: adjust bug");
		    }
		}
		break;
	    }
	  default:
	    /* ignore */
	    break;
	}
	list = list->next;
    }

    iseq->iseq_encoded = (void *)generated_iseq;
    iseq->iseq_size = pos;
    iseq->stack_max = stack_max;

    REALLOC_N(line_info_table, struct iseq_line_info_entry, k);
    iseq->line_info_table = line_info_table;
    iseq->line_info_size = k;

    return COMPILE_OK;
}

static int
label_get_position(LABEL *lobj)
{
    return lobj->position;
}

static int
label_get_sp(LABEL *lobj)
{
    return lobj->sp;
}

static int
iseq_set_exception_table(rb_iseq_t *iseq)
{
    const VALUE *tptr, *ptr;
    int tlen, i;
    struct iseq_catch_table_entry *entry;

    tlen = (int)RARRAY_LEN(iseq->compile_data->catch_table_ary);
    tptr = RARRAY_CONST_PTR(iseq->compile_data->catch_table_ary);

    iseq->catch_table = 0;
    if (tlen > 0) {
	iseq->catch_table = xmalloc(iseq_catch_table_bytes(tlen));
	iseq->catch_table->size = tlen;
    }

    if (iseq->catch_table) for (i = 0; i < iseq->catch_table->size; i++) {
	ptr = RARRAY_CONST_PTR(tptr[i]);
	entry = &iseq->catch_table->entries[i];
	entry->type = (enum catch_type)(ptr[0] & 0xffff);
	entry->start = label_get_position((LABEL *)(ptr[1] & ~1));
	entry->end = label_get_position((LABEL *)(ptr[2] & ~1));
	entry->iseq = ptr[3];

	/* register iseq as mark object */
	if (entry->iseq != 0) {
	    iseq_add_mark_object(iseq, entry->iseq);
	}

	/* stack depth */
	if (ptr[4]) {
	    LABEL *lobj = (LABEL *)(ptr[4] & ~1);
	    entry->cont = label_get_position(lobj);
	    entry->sp = label_get_sp(lobj);

	    /* TODO: Dirty Hack!  Fix me */
	    if (entry->type == CATCH_TYPE_RESCUE ||
		entry->type == CATCH_TYPE_BREAK ||
		entry->type == CATCH_TYPE_NEXT) {
		entry->sp--;
	    }
	}
	else {
	    entry->cont = 0;
	}
    }

    RB_OBJ_WRITE(iseq->self, &iseq->compile_data->catch_table_ary, 0); /* free */
    return COMPILE_OK;
}

/*
 * set optional argument table
 *   def foo(a, b=expr1, c=expr2)
 *   =>
 *    b:
 *      expr1
 *    c:
 *      expr2
 */
static int
iseq_set_optargs_table(rb_iseq_t *iseq)
{
    int i;

    if (iseq->param.flags.has_opt) {
	for (i = 0; i < iseq->param.opt_num + 1; i++) {
	    iseq->param.opt_table[i] = label_get_position((LABEL *)iseq->param.opt_table[i]);
	}
    }
    return COMPILE_OK;
}

static LINK_ELEMENT *
get_destination_insn(INSN *iobj)
{
    LABEL *lobj = (LABEL *)OPERAND_AT(iobj, 0);
    LINK_ELEMENT *list;

    list = lobj->link.next;
    while (list) {
	if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) {
	    break;
	}
	list = list->next;
    }
    return list;
}

static LINK_ELEMENT *
get_next_insn(INSN *iobj)
{
    LINK_ELEMENT *list = iobj->link.next;

    while (list) {
	if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) {
	    return list;
	}
	list = list->next;
    }
    return 0;
}

static LINK_ELEMENT *
get_prev_insn(INSN *iobj)
{
    LINK_ELEMENT *list = iobj->link.prev;

    while (list) {
	if (list->type == ISEQ_ELEMENT_INSN || list->type == ISEQ_ELEMENT_ADJUST) {
	    return list;
	}
	list = list->prev;
    }
    return 0;
}

static int
iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
{
    INSN *iobj = (INSN *)list;
  again:
    if (iobj->insn_id == BIN(jump)) {
	INSN *niobj, *diobj, *piobj;
	/*
	 *  useless jump elimination:
	 *     jump LABEL1
	 *     ...
	 *   LABEL1:
	 *     jump LABEL2
	 *
	 *   => in this case, first jump instruction should jump to
	 *      LABEL2 directly
	 */
	diobj = (INSN *)get_destination_insn(iobj);
	niobj = (INSN *)get_next_insn(iobj);

	if (diobj == niobj) {
	    /*
	     *   jump LABEL
	     *  LABEL:
	     * =>
	     *   LABEL:
	     */
	    REMOVE_ELEM(&iobj->link);
	}
	else if (iobj != diobj && diobj->insn_id == BIN(jump)) {
	    if (OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0)) {
		OPERAND_AT(iobj, 0) = OPERAND_AT(diobj, 0);
		goto again;
	    }
	}
	else if (diobj->insn_id == BIN(leave)) {
	    /*
	     *  jump LABEL
	     *  ...
	     * LABEL:
	     *  leave
	     * =>
	     *  leave
	     *  ...
	     * LABEL:
	     *  leave
	     */
	    INSN *eiobj = new_insn_core(iseq, iobj->line_no, BIN(leave),
					diobj->operand_size, diobj->operands);
	    INSN *popiobj = new_insn_core(iseq, iobj->line_no,
					  BIN(pop), 0, 0);
	    /* replace */
	    REPLACE_ELEM((LINK_ELEMENT *)iobj, (LINK_ELEMENT *)eiobj);
	    INSERT_ELEM_NEXT((LINK_ELEMENT *)eiobj, (LINK_ELEMENT *)popiobj);
	    iobj = popiobj;
	}
	/*
	 * useless jump elimination (if/unless destination):
	 *   if   L1
	 *   jump L2
	 * L1:
	 *   ...
	 * L2:
	 *
	 * ==>
	 *   unless L2
	 * L1:
	 *   ...
	 * L2:
	 */
	else if ((piobj = (INSN *)get_prev_insn(iobj)) != 0 &&
		 (piobj->insn_id == BIN(branchif) ||
		  piobj->insn_id == BIN(branchunless))) {
	    if (niobj == (INSN *)get_destination_insn(piobj)) {
		piobj->insn_id = (piobj->insn_id == BIN(branchif))
		  ? BIN(branchunless) : BIN(branchif);
		OPERAND_AT(piobj, 0) = OPERAND_AT(iobj, 0);
		REMOVE_ELEM(&iobj->link);
	    }
	}
    }

    if (iobj->insn_id == BIN(branchif) ||
	iobj->insn_id == BIN(branchunless)) {
	/*
	 *   if L1
	 *   ...
	 * L1:
	 *   jump L2
	 * =>
	 *   if L2
	 */
	INSN *nobj = (INSN *)get_destination_insn(iobj);
	if (nobj->insn_id == BIN(jump)) {
	    OPERAND_AT(iobj, 0) = OPERAND_AT(nobj, 0);
	}
    }

    if (do_tailcallopt && iobj->insn_id == BIN(leave)) {
	/*
	 *  send ...
	 *  leave
	 * =>
	 *  send ..., ... | VM_CALL_TAILCALL, ...
	 *  leave # unreachable
	 */
	INSN *piobj = (INSN *)get_prev_insn((INSN *)list);
	enum ruby_vminsn_type previ = piobj->insn_id;

	if (previ == BIN(send) || previ == BIN(opt_send_without_block) || previ == BIN(invokesuper)) {
	    rb_call_info_t *ci = (rb_call_info_t *)piobj->operands[0];
	    if (ci->blockiseq == 0) {
		ci->flag |= VM_CALL_TAILCALL;
	    }
	}
    }
    return COMPILE_OK;
}

static int
insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id)
{
    int old_opsize = iobj->operand_size;
    iobj->insn_id = insn_id;
    iobj->operand_size = insn_len(insn_id) - 1;

    if (iobj->operand_size > old_opsize) {
	VALUE *old_operands = iobj->operands;
	if (insn_id != BIN(opt_neq)) {
	    rb_bug("insn_set_specialized_instruction: unknown insn: %d", insn_id);
	}
	iobj->operands = (VALUE *)compile_data_alloc(iseq, iobj->operand_size * sizeof(VALUE));
	iobj->operands[0] = old_operands[0];
	iobj->operands[1] = (VALUE)new_callinfo(iseq, idEq, 1, 0, 0, NULL);
    }

    return COMPILE_OK;
}

static int
iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
{
    if (iobj->insn_id == BIN(send)) {
	rb_call_info_t *ci = (rb_call_info_t *)OPERAND_AT(iobj, 0);

#define SP_INSN(opt) insn_set_specialized_instruction(iseq, iobj, BIN(opt_##opt))
	if (ci->flag & VM_CALL_ARGS_SIMPLE) {
	    switch (ci->orig_argc) {
	      case 0:
		switch (ci->mid) {
		  case idLength: SP_INSN(length); return COMPILE_OK;
		  case idSize:	 SP_INSN(size);	  return COMPILE_OK;
		  case idEmptyP: SP_INSN(empty_p);return COMPILE_OK;
		  case idSucc:	 SP_INSN(succ);	  return COMPILE_OK;
		  case idNot:	 SP_INSN(not);	  return COMPILE_OK;
		}
		break;
	      case 1:
		switch (ci->mid) {
		  case idPLUS:	 SP_INSN(plus);	  return COMPILE_OK;
		  case idMINUS:	 SP_INSN(minus);  return COMPILE_OK;
		  case idMULT:	 SP_INSN(mult);	  return COMPILE_OK;
		  case idDIV:	 SP_INSN(div);	  return COMPILE_OK;
		  case idMOD:	 SP_INSN(mod);	  return COMPILE_OK;
		  case idEq:	 SP_INSN(eq);	  return COMPILE_OK;
		  case idNeq:	 SP_INSN(neq);	  return COMPILE_OK;
		  case idLT:	 SP_INSN(lt);	  return COMPILE_OK;
		  case idLE:	 SP_INSN(le);	  return COMPILE_OK;
		  case idGT:	 SP_INSN(gt);	  return COMPILE_OK;
		  case idGE:	 SP_INSN(ge);	  return COMPILE_OK;
		  case idLTLT:	 SP_INSN(ltlt);	  return COMPILE_OK;
		  case idAREF:	 SP_INSN(aref);	  return COMPILE_OK;
		}
		break;
	      case 2:
		switch (ci->mid) {
		  case idASET:	 SP_INSN(aset);	  return COMPILE_OK;
		}
		break;
	    }
	}

	if ((ci->flag & VM_CALL_ARGS_BLOCKARG) == 0 && ci->blockiseq == NULL) {
	    iobj->insn_id = BIN(opt_send_without_block);
	}
    }
#undef SP_INSN

    return COMPILE_OK;
}

static int
iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
    LINK_ELEMENT *list;
    const int do_peepholeopt = iseq->compile_data->option->peephole_optimization;
    const int do_tailcallopt = iseq->compile_data->option->tailcall_optimization;
    const int do_si = iseq->compile_data->option->specialized_instruction;
    const int do_ou = iseq->compile_data->option->operands_unification;
    list = FIRST_ELEMENT(anchor);

    while (list) {
	if (list->type == ISEQ_ELEMENT_INSN) {
	    if (do_peepholeopt) {
		iseq_peephole_optimize(iseq, list, do_tailcallopt);
	    }
	    if (do_si) {
		iseq_specialized_instruction(iseq, (INSN *)list);
	    }
	    if (do_ou) {
		insn_operands_unification((INSN *)list);
	    }
	}
	list = list->next;
    }
    return COMPILE_OK;
}

#if OPT_INSTRUCTIONS_UNIFICATION
static INSN *
new_unified_insn(rb_iseq_t *iseq,
		 int insn_id, int size, LINK_ELEMENT *seq_list)
{
    INSN *iobj = 0;
    LINK_ELEMENT *list = seq_list;
    int i, argc = 0;
    VALUE *operands = 0, *ptr = 0;


    /* count argc */
    for (i = 0; i < size; i++) {
	iobj = (INSN *)list;
	argc += iobj->operand_size;
	list = list->next;
    }

    if (argc > 0) {
	ptr = operands =
	    (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * argc);
    }

    /* copy operands */
    list = seq_list;
    for (i = 0; i < size; i++) {
	iobj = (INSN *)list;
	MEMCPY(ptr, iobj->operands, VALUE, iobj->operand_size);
	ptr += iobj->operand_size;
	list = list->next;
    }

    return new_insn_core(iseq, iobj->line_no, insn_id, argc, operands);
}
#endif

/*
 * This scheme can get more performance if do this optimize with
 * label address resolving.
 * It's future work (if compile time was bottle neck).
 */
static int
iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
#if OPT_INSTRUCTIONS_UNIFICATION
    LINK_ELEMENT *list;
    INSN *iobj, *niobj;
    int id, k;
    intptr_t j;

    list = FIRST_ELEMENT(anchor);
    while (list) {
	if (list->type == ISEQ_ELEMENT_INSN) {
	    iobj = (INSN *)list;
	    id = iobj->insn_id;
	    if (unified_insns_data[id] != 0) {
		const int *const *entry = unified_insns_data[id];
		for (j = 1; j < (intptr_t)entry[0]; j++) {
		    const int *unified = entry[j];
		    LINK_ELEMENT *li = list->next;
		    for (k = 2; k < unified[1]; k++) {
			if (li->type != ISEQ_ELEMENT_INSN ||
			    ((INSN *)li)->insn_id != unified[k]) {
			    goto miss;
			}
			li = li->next;
		    }
		    /* matched */
		    niobj =
			new_unified_insn(iseq, unified[0], unified[1] - 1,
					 list);

		    /* insert to list */
		    niobj->link.prev = (LINK_ELEMENT *)iobj->link.prev;
		    niobj->link.next = li;
		    if (li) {
			li->prev = (LINK_ELEMENT *)niobj;
		    }

		    list->prev->next = (LINK_ELEMENT *)niobj;
		    list = (LINK_ELEMENT *)niobj;
		    break;
		  miss:;
		}
	    }
	}
	list = list->next;
    }
#endif
    return COMPILE_OK;
}

#if OPT_STACK_CACHING

#define SC_INSN(insn, stat) sc_insn_info[(insn)][(stat)]
#define SC_NEXT(insn)       sc_insn_next[(insn)]

#include "opt_sc.inc"

static int
insn_set_sc_state(rb_iseq_t *iseq, INSN *iobj, int state)
{
    int nstate;
    int insn_id;

    insn_id = iobj->insn_id;
    iobj->insn_id = SC_INSN(insn_id, state);
    nstate = SC_NEXT(iobj->insn_id);

    if (insn_id == BIN(jump) ||
	insn_id == BIN(branchif) || insn_id == BIN(branchunless)) {
	LABEL *lobj = (LABEL *)OPERAND_AT(iobj, 0);

	if (lobj->sc_state != 0) {
	    if (lobj->sc_state != nstate) {
		dump_disasm_list((LINK_ELEMENT *)iobj);
		dump_disasm_list((LINK_ELEMENT *)lobj);
		printf("\n-- %d, %d\n", lobj->sc_state, nstate);
		rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no,
				 "insn_set_sc_state error\n");
		return 0;
	    }
	}
	else {
	    lobj->sc_state = nstate;
	}
	if (insn_id == BIN(jump)) {
	    nstate = SCS_XX;
	}
    }
    else if (insn_id == BIN(leave)) {
	nstate = SCS_XX;
    }

    return nstate;
}

static int
label_set_sc_state(LABEL *lobj, int state)
{
    if (lobj->sc_state != 0) {
	if (lobj->sc_state != state) {
	    state = lobj->sc_state;
	}
    }
    else {
	lobj->sc_state = state;
    }

    return state;
}


#endif

static int
iseq_set_sequence_stackcaching(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
#if OPT_STACK_CACHING
    LINK_ELEMENT *list;
    int state, insn_id;

    /* initialize */
    state = SCS_XX;
    list = FIRST_ELEMENT(anchor);
    /* dump_disasm_list(list); */

    /* for each list element */
    while (list) {
      redo_point:
	switch (list->type) {
	  case ISEQ_ELEMENT_INSN:
	    {
		INSN *iobj = (INSN *)list;
		insn_id = iobj->insn_id;

		/* dump_disasm_list(list); */

		switch (insn_id) {
		  case BIN(nop):
		    {
			/* exception merge point */
			if (state != SCS_AX) {
			    INSN *rpobj =
				new_insn_body(iseq, 0, BIN(reput), 0);

			    /* replace this insn */
			    REPLACE_ELEM(list, (LINK_ELEMENT *)rpobj);
			    list = (LINK_ELEMENT *)rpobj;
			    goto redo_point;
			}
			break;
		    }
		  case BIN(swap):
		    {
			if (state == SCS_AB || state == SCS_BA) {
			    state = (state == SCS_AB ? SCS_BA : SCS_AB);

			    REMOVE_ELEM(list);
			    list = list->next;
			    goto redo_point;
			}
			break;
		    }
		  case BIN(pop):
		    {
			switch (state) {
			  case SCS_AX:
			  case SCS_BX:
			    state = SCS_XX;
			    break;
			  case SCS_AB:
			    state = SCS_AX;
			    break;
			  case SCS_BA:
			    state = SCS_BX;
			    break;
			  case SCS_XX:
			    goto normal_insn;
			  default:
			    rb_compile_error(RSTRING_PTR(iseq->location.path), iobj->line_no,
					     "unreachable");
			}
			/* remove useless pop */
			REMOVE_ELEM(list);
			list = list->next;
			goto redo_point;
		    }
		  default:;
		    /* none */
		}		/* end of switch */
	      normal_insn:
		state = insn_set_sc_state(iseq, iobj, state);
		break;
	    }
	  case ISEQ_ELEMENT_LABEL:
	    {
		LABEL *lobj;
		lobj = (LABEL *)list;

		state = label_set_sc_state(lobj, state);
	    }
	  default:
	    break;
	}
	list = list->next;
    }
#endif
    return COMPILE_OK;
}

static int
compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *node, int *cntp)
{
    NODE *list = node->nd_next;
    VALUE lit = node->nd_lit;
    int cnt = 0;

    debugp_param("nd_lit", lit);
    if (!NIL_P(lit)) {
	cnt++;
	if (RB_TYPE_P(lit, T_STRING))
	    lit = node->nd_lit = rb_fstring(node->nd_lit);
	ADD_INSN1(ret, nd_line(node), putobject, lit);
    }

    while (list) {
	node = list->nd_head;
	if (nd_type(node) == NODE_STR) {
	    node->nd_lit = rb_fstring(node->nd_lit);
	    ADD_INSN1(ret, nd_line(node), putobject, node->nd_lit);
	}
	else {
	    COMPILE(ret, "each string", node);
	}
	cnt++;
	list = list->nd_next;
    }
    *cntp = cnt;

    return COMPILE_OK;
}

static int
compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node)
{
    int cnt;
    compile_dstr_fragments(iseq, ret, node, &cnt);
    ADD_INSN1(ret, nd_line(node), concatstrings, INT2FIX(cnt));
    return COMPILE_OK;
}

static int
compile_dregx(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node)
{
    int cnt;
    compile_dstr_fragments(iseq, ret, node, &cnt);
    ADD_INSN2(ret, nd_line(node), toregexp, INT2FIX(node->nd_cflag), INT2FIX(cnt));
    return COMPILE_OK;
}

static int
compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * cond,
			 LABEL *then_label, LABEL *else_label)
{
    switch (nd_type(cond)) {
      case NODE_AND:
	{
	    LABEL *label = NEW_LABEL(nd_line(cond));
	    compile_branch_condition(iseq, ret, cond->nd_1st, label,
				     else_label);
	    ADD_LABEL(ret, label);
	    compile_branch_condition(iseq, ret, cond->nd_2nd, then_label,
				     else_label);
	    break;
	}
      case NODE_OR:
	{
	    LABEL *label = NEW_LABEL(nd_line(cond));
	    compile_branch_condition(iseq, ret, cond->nd_1st, then_label,
				     label);
	    ADD_LABEL(ret, label);
	    compile_branch_condition(iseq, ret, cond->nd_2nd, then_label,
				     else_label);
	    break;
	}
      case NODE_LIT:		/* NODE_LIT is always not true */
      case NODE_TRUE:
      case NODE_STR:
	/* printf("useless condition eliminate (%s)\n",  ruby_node_name(nd_type(cond))); */
	ADD_INSNL(ret, nd_line(cond), jump, then_label);
	break;
      case NODE_FALSE:
      case NODE_NIL:
	/* printf("useless condition eliminate (%s)\n", ruby_node_name(nd_type(cond))); */
	ADD_INSNL(ret, nd_line(cond), jump, else_label);
	break;
      default:
	COMPILE(ret, "branch condition", cond);
	ADD_INSNL(ret, nd_line(cond), branchunless, else_label);
	ADD_INSNL(ret, nd_line(cond), jump, then_label);
	break;
    }
    return COMPILE_OK;
}

static int
compile_array_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *ret, const NODE * const root_node, rb_call_info_kw_arg_t ** const kw_arg_ptr)
{
    if (kw_arg_ptr == NULL) return FALSE;

    if (nd_type(root_node) == NODE_HASH && root_node->nd_head && nd_type(root_node->nd_head) == NODE_ARRAY) {
	NODE *node = root_node->nd_head;

	while (node) {
	    NODE *key_node = node->nd_head;

	    assert(nd_type(node) == NODE_ARRAY);
	    if (key_node && nd_type(key_node) == NODE_LIT && RB_TYPE_P(key_node->nd_lit, T_SYMBOL)) {
		/* can be keywords */
	    }
	    else {
		return FALSE;
	    }
	    node = node->nd_next; /* skip value node */
	    node = node->nd_next;
	}

	/* may be keywords */
	node = root_node->nd_head;
	{
	    int len = (int)node->nd_alen / 2;
	    rb_call_info_kw_arg_t *kw_arg  = (rb_call_info_kw_arg_t *)ruby_xmalloc(sizeof(rb_call_info_kw_arg_t) + sizeof(VALUE) * (len - 1));
	    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; i<max && node; i++, len++, node = node->nd_next) {
		if (CPDEBUG > 0 && nd_type(node) != NODE_ARRAY) {
		    rb_bug("compile_array: This node is not NODE_ARRAY, but %s", ruby_node_name(nd_type(node)));
		}

		if (type != COMPILE_ARRAY_TYPE_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; i<memindex; i++) { \
	if (mem[i] == (v)) return 0; \
    } \
    mem[memindex++] = (v); \
}

    if (rhsn == 0 || nd_type(rhsn) != NODE_ARRAY) {
	return 0;
    }

    while (lhsn) {
	NODE *ln = lhsn->nd_head;
	switch (nd_type(ln)) {
	  case NODE_LASGN:
	    MEMORY(ln->nd_vid);
	    break;
	  case NODE_DASGN:
	  case NODE_DASGN_CURR:
	  case NODE_IASGN:
	  case NODE_IASGN2:
	  case NODE_CVASGN:
	    MEMORY(ln->nd_vid);
	    break;
	  default:
	    return 0;
	}
	lhsn = lhsn->nd_next;
	llen++;
    }

    while (rhsn) {
	if (llen <= rlen) {
	    COMPILE_POPED(ret, "masgn val (popped)", rhsn->nd_head);
	}
	else {
	    COMPILE(ret, "masgn val", rhsn->nd_head);
	}
	rhsn = rhsn->nd_next;
	rlen++;
    }

    if (llen > rlen) {
	for (i=0; i<llen-rlen; i++) {
	    ADD_INSN(ret, nd_line(orig_lhsn), putnil);
	}
    }

    compile_massign_opt_lhs(iseq, ret, orig_lhsn);
    return 1;
}

static int
compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *node, int poped)
{
    NODE *rhsn = node->nd_value;
    NODE *splatn = node->nd_args;
    NODE *lhsn = node->nd_head;
    int lhs_splat = (splatn && (VALUE)splatn != (VALUE)-1) ? 1 : 0;

    if (!poped || splatn || !compile_massign_opt(iseq, ret, rhsn, lhsn)) {
	int llen = 0;
	DECL_ANCHOR(lhsseq);

	INIT_ANCHOR(lhsseq);

	while (lhsn) {
	    compile_massign_lhs(iseq, lhsseq, lhsn->nd_head);
	    llen += 1;
	    lhsn = lhsn->nd_next;
	}

	COMPILE(ret, "normal masgn rhs", rhsn);

	if (!poped) {
	    ADD_INSN(ret, nd_line(node), dup);
	}

	ADD_INSN2(ret, nd_line(node), expandarray,
		  INT2FIX(llen), INT2FIX(lhs_splat));
	ADD_SEQ(ret, lhsseq);

	if (lhs_splat) {
	    if (nd_type(splatn) == NODE_POSTARG) {
		/*a, b, *r, p1, p2 */
		NODE *postn = splatn->nd_2nd;
		NODE *restn = splatn->nd_1st;
		int num = (int)postn->nd_alen;
		int flag = 0x02 | (((VALUE)restn == (VALUE)-1) ? 0x00 : 0x01);

		ADD_INSN2(ret, nd_line(splatn), expandarray,
			  INT2FIX(num), INT2FIX(flag));

		if ((VALUE)restn != (VALUE)-1) {
		    compile_massign_lhs(iseq, ret, restn);
		}
		while (postn) {
		    compile_massign_lhs(iseq, ret, postn->nd_head);
		    postn = postn->nd_next;
		}
	    }
	    else {
		/* a, b, *r */
		compile_massign_lhs(iseq, ret, splatn);
	    }
	}
    }
    return COMPILE_OK;
}

static int
compile_colon2(rb_iseq_t *iseq, NODE * node,
	       LINK_ANCHOR *pref, LINK_ANCHOR *body)
{
    switch (nd_type(node)) {
      case NODE_CONST:
	debugi("compile_colon2 - colon", node->nd_vid);
	ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_vid));
	break;
      case NODE_COLON3:
	debugi("compile_colon2 - colon3", node->nd_mid);
	ADD_INSN(body, nd_line(node), pop);
	ADD_INSN1(body, nd_line(node), putobject, rb_cObject);
	ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_mid));
	break;
      case NODE_COLON2:
	compile_colon2(iseq, node->nd_head, pref, body);
	debugi("compile_colon2 - colon2", node->nd_mid);
	ADD_INSN1(body, nd_line(node), getconstant, ID2SYM(node->nd_mid));
	break;
      default:
	COMPILE(pref, "const colon2 prefix", node);
	break;
    }
    return COMPILE_OK;
}

static VALUE
compile_cpath(LINK_ANCHOR *ret, rb_iseq_t *iseq, NODE *cpath)
{
    if (nd_type(cpath) == NODE_COLON3) {
	/* toplevel class ::Foo */
	ADD_INSN1(ret, nd_line(cpath), putobject, rb_cObject);
	return Qfalse;
    }
    else if (cpath->nd_head) {
	/* Bar::Foo */
	COMPILE(ret, "nd_else->nd_head", cpath->nd_head);
	return Qfalse;
    }
    else {
	/* class at cbase Foo */
	ADD_INSN1(ret, nd_line(cpath), putspecialobject,
		  INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE));
	return Qtrue;
    }
}

#define private_recv_p(node) (nd_type((node)->nd_recv) == NODE_SELF)

#define defined_expr defined_expr0
static int
defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *ret,
	     NODE *node, LABEL **lfinish, VALUE needstr)
{
    enum defined_type expr_type = 0;
    enum node_type type;

    switch (type = nd_type(node)) {

	/* easy literals */
      case NODE_NIL:
	expr_type = DEFINED_NIL;
	break;
      case NODE_SELF:
	expr_type = DEFINED_SELF;
	break;
      case NODE_TRUE:
	expr_type = DEFINED_TRUE;
	break;
      case NODE_FALSE:
	expr_type = DEFINED_FALSE;
	break;

      case NODE_ARRAY:{
	NODE *vals = node;

	do {
	    defined_expr(iseq, ret, vals->nd_head, lfinish, Qfalse);

	    if (!lfinish[1]) {
		lfinish[1] = NEW_LABEL(nd_line(node));
	    }
	    ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]);
	} while ((vals = vals->nd_next) != NULL);
      }
      case NODE_STR:
      case NODE_LIT:
      case NODE_ZARRAY:
      case NODE_AND:
      case NODE_OR:
      default:
	expr_type = DEFINED_EXPR;
	break;

	/* variables */
      case NODE_LVAR:
      case NODE_DVAR:
	expr_type = DEFINED_LVAR;
	break;

      case NODE_IVAR:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_IVAR),
		  ID2SYM(node->nd_vid), needstr);
	return 1;

      case NODE_GVAR:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_GVAR),
		  ID2SYM(node->nd_entry->id), needstr);
	return 1;

      case NODE_CVAR:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CVAR),
		  ID2SYM(node->nd_vid), needstr);
	return 1;

      case NODE_CONST:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CONST),
		  ID2SYM(node->nd_vid), needstr);
	return 1;
      case NODE_COLON2:
	if (!lfinish[1]) {
	    lfinish[1] = NEW_LABEL(nd_line(node));
	}
	defined_expr(iseq, ret, node->nd_head, lfinish, Qfalse);
	ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]);

	if (rb_is_const_id(node->nd_mid)) {
	    COMPILE(ret, "defined/colon2#nd_head", node->nd_head);
	    ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_CONST),
		      ID2SYM(node->nd_mid), needstr);
	}
	else {
	    COMPILE(ret, "defined/colon2#nd_head", node->nd_head);
	    ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_METHOD),
		      ID2SYM(node->nd_mid), needstr);
	}
	return 1;
      case NODE_COLON3:
	ADD_INSN1(ret, nd_line(node), putobject, rb_cObject);
	ADD_INSN3(ret, nd_line(node), defined,
		  INT2FIX(DEFINED_CONST), ID2SYM(node->nd_mid), needstr);
	return 1;

	/* method dispatch */
      case NODE_CALL:
      case NODE_VCALL:
      case NODE_FCALL:
      case NODE_ATTRASGN:{
	const int explicit_receiver =
	    (type == NODE_CALL ||
	     (type == NODE_ATTRASGN && !private_recv_p(node)));

	if (!lfinish[1]) {
	    lfinish[1] = NEW_LABEL(nd_line(node));
	}
	if (node->nd_args) {
	    defined_expr(iseq, ret, node->nd_args, lfinish, Qfalse);
	    ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]);
	}
	if (explicit_receiver) {
	    defined_expr(iseq, ret, node->nd_recv, lfinish, Qfalse);
	    ADD_INSNL(ret, nd_line(node), branchunless, lfinish[1]);
	    COMPILE(ret, "defined/recv", node->nd_recv);
	    ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_METHOD),
		      ID2SYM(node->nd_mid), needstr);
	}
	else {
	    ADD_INSN(ret, nd_line(node), putself);
	    ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_FUNC),
		      ID2SYM(node->nd_mid), needstr);
	}
	return 1;
      }

      case NODE_YIELD:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_YIELD), 0,
		  needstr);
	return 1;

      case NODE_BACK_REF:
      case NODE_NTH_REF:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_REF),
		  INT2FIX((node->nd_nth << 1) | (type == NODE_BACK_REF)),
		  needstr);
	return 1;

      case NODE_SUPER:
      case NODE_ZSUPER:
	ADD_INSN(ret, nd_line(node), putnil);
	ADD_INSN3(ret, nd_line(node), defined, INT2FIX(DEFINED_ZSUPER), 0,
		  needstr);
	return 1;

      case NODE_OP_ASGN1:
      case NODE_OP_ASGN2:
      case NODE_OP_ASGN_OR:
      case NODE_OP_ASGN_AND:
      case NODE_MASGN:
      case NODE_LASGN:
      case NODE_DASGN:
      case NODE_DASGN_CURR:
      case NODE_GASGN:
      case NODE_IASGN:
      case NODE_CDECL:
      case NODE_CVDECL:
      case NODE_CVASGN:
	expr_type = DEFINED_ASGN;
	break;
    }

    if (expr_type) {
	if (needstr != Qfalse) {
	    VALUE str = rb_iseq_defined_string(expr_type);
	    ADD_INSN1(ret, nd_line(node), putobject, str);
	}
	else {
	    ADD_INSN1(ret, nd_line(node), putobject, Qtrue);
	}
	return 1;
    }
    return 0;
}
#undef defined_expr

static int
defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *ret,
	     NODE *node, LABEL **lfinish, VALUE needstr)
{
    LINK_ELEMENT *lcur = ret->last;
    int done = defined_expr0(iseq, ret, node, lfinish, needstr);
    if (lfinish[1]) {
	int line = nd_line(node);
	LABEL *lstart = NEW_LABEL(line);
	LABEL *lend = NEW_LABEL(line);
	VALUE rescue = NEW_CHILD_ISEQVAL(NEW_NIL(),
					 rb_str_concat(rb_str_new2
						       ("defined guard in "),
						       iseq->location.label),
					 ISEQ_TYPE_DEFINED_GUARD, 0);
	APPEND_LABEL(ret, lcur, lstart);
	ADD_LABEL(ret, lend);
	ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lfinish[1]);
    }
    return done;
}

static VALUE
make_name_for_block(rb_iseq_t *iseq)
{
    int level = 1;
    rb_iseq_t *ip = iseq;

    if (iseq->parent_iseq != 0) {
	while (ip->local_iseq != ip) {
	    if (ip->type == ISEQ_TYPE_BLOCK) {
		level++;
	    }
	    ip = ip->parent_iseq;
	}
    }

    if (level == 1) {
	return rb_sprintf("block in %"PRIsVALUE, ip->location.label);
    }
    else {
	return rb_sprintf("block (%d levels) in %"PRIsVALUE, level, ip->location.label);
    }
}

static void
push_ensure_entry(rb_iseq_t *iseq,
		  struct iseq_compile_data_ensure_node_stack *enl,
		  struct ensure_range *er, NODE *node)
{
    enl->ensure_node = node;
    enl->prev = iseq->compile_data->ensure_node_stack;	/* prev */
    enl->erange = er;
    iseq->compile_data->ensure_node_stack = enl;
}

static void
add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange,
		 LABEL *lstart, LABEL *lend)
{
    struct ensure_range *ne =
	compile_data_alloc(iseq, sizeof(struct ensure_range));

    while (erange->next != 0) {
	erange = erange->next;
    }
    ne->next = 0;
    ne->begin = lend;
    ne->end = erange->end;
    erange->end = lstart;

    erange->next = ne;
}

static void
add_ensure_iseq(LINK_ANCHOR *ret, rb_iseq_t *iseq, int is_return)
{
    struct iseq_compile_data_ensure_node_stack *enlp =
	iseq->compile_data->ensure_node_stack;
    struct iseq_compile_data_ensure_node_stack *prev_enlp = enlp;
    DECL_ANCHOR(ensure);

    INIT_ANCHOR(ensure);
    while (enlp) {
	if (enlp->erange != 0) {
	    DECL_ANCHOR(ensure_part);
	    LABEL *lstart = NEW_LABEL(0);
	    LABEL *lend = NEW_LABEL(0);
	    INIT_ANCHOR(ensure_part);

	    add_ensure_range(iseq, enlp->erange, lstart, lend);

	    iseq->compile_data->ensure_node_stack = enlp->prev;
	    ADD_LABEL(ensure_part, lstart);
	    COMPILE_POPED(ensure_part, "ensure part", enlp->ensure_node);
	    ADD_LABEL(ensure_part, lend);
	    ADD_SEQ(ensure, ensure_part);
	}
	else {
	    if (!is_return) {
		break;
	    }
	}
	enlp = enlp->prev;
    }
    iseq->compile_data->ensure_node_stack = prev_enlp;
    ADD_SEQ(ret, ensure);
}

static VALUE
setup_args(rb_iseq_t *iseq, LINK_ANCHOR *args, NODE *argn, unsigned int *flag, rb_call_info_kw_arg_t **keywords)
{
    VALUE argc = INT2FIX(0);
    int nsplat = 0;
    DECL_ANCHOR(arg_block);
    DECL_ANCHOR(args_splat);

    INIT_ANCHOR(arg_block);
    INIT_ANCHOR(args_splat);
    if (argn && nd_type(argn) == NODE_BLOCK_PASS) {
	COMPILE(arg_block, "block", argn->nd_body);
	*flag |= VM_CALL_ARGS_BLOCKARG;
	argn = argn->nd_head;
    }

  setup_argn:
    if (argn) {
	switch (nd_type(argn)) {
	  case NODE_SPLAT: {
	    COMPILE(args, "args (splat)", argn->nd_head);
	    ADD_INSN1(args, nd_line(argn), splatarray, Qfalse);
	    argc = INT2FIX(1);
	    nsplat++;
	    *flag |= VM_CALL_ARGS_SPLAT;
	    break;
	  }
	  case NODE_ARGSCAT:
	  case NODE_ARGSPUSH: {
	    int next_is_array = (nd_type(argn->nd_head) == NODE_ARRAY);
	    DECL_ANCHOR(tmp);

	    INIT_ANCHOR(tmp);
	    COMPILE(tmp, "args (cat: splat)", argn->nd_body);
	    if (nd_type(argn) == NODE_ARGSCAT) {
		ADD_INSN1(tmp, nd_line(argn), splatarray, Qfalse);
	    }
	    else {
		ADD_INSN1(tmp, nd_line(argn), newarray, INT2FIX(1));
	    }
	    INSERT_LIST(args_splat, tmp);
	    nsplat++;
	    *flag |= VM_CALL_ARGS_SPLAT;

	    if (next_is_array) {
		argc = INT2FIX(compile_array(iseq, args, argn->nd_head, COMPILE_ARRAY_TYPE_ARGS) + 1);
	    }
	    else {
		argn = argn->nd_head;
		goto setup_argn;
	    }
	    break;
	  }
	  case NODE_ARRAY:
	    {
		argc = INT2FIX(compile_array_(iseq, args, argn, COMPILE_ARRAY_TYPE_ARGS, keywords, FALSE));
		break;
	    }
	  default: {
	    rb_bug("setup_arg: unknown node: %s\n", ruby_node_name(nd_type(argn)));
	  }
	}
    }

    if (nsplat > 1) {
	int i;
	for (i=1; i<nsplat; i++) {
	    ADD_INSN(args_splat, nd_line(args), concatarray);
	}
    }

    if (!LIST_SIZE_ZERO(args_splat)) {
	ADD_SEQ(args, args_splat);
    }

    if (*flag & VM_CALL_ARGS_BLOCKARG) {
	ADD_SEQ(args, arg_block);
    }
    return argc;
}

static VALUE
build_postexe_iseq(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE *body)
{
    int line = nd_line(body);
    VALUE argc = INT2FIX(0);
    VALUE block = NEW_CHILD_ISEQVAL(body, make_name_for_block(iseq->parent_iseq), ISEQ_TYPE_BLOCK, line);
    ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
    ADD_CALL_WITH_BLOCK(ret, line, id_core_set_postexe, argc, block);
    iseq_set_local_table(iseq, 0);
    return Qnil;
}

/**
  compile each node

  self:  InstructionSequence
  node:  Ruby compiled node
  poped: This node will be poped
 */
static int
iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped)
{
    enum node_type type;
    LINK_ELEMENT *saved_last_element = 0;
    int line;

    if (node == 0) {
	if (!poped) {
	    debugs("node: NODE_NIL(implicit)\n");
	    ADD_INSN(ret, iseq->compile_data->last_line, putnil);
	}
	return COMPILE_OK;
    }

    line = (int)nd_line(node);

    if (iseq->compile_data->last_line == line) {
	/* ignore */
    }
    else {
	if (node->flags & NODE_FL_NEWLINE) {
	    iseq->compile_data->last_line = line;
	    ADD_TRACE(ret, line, RUBY_EVENT_LINE);
	    saved_last_element = ret->last;
	}
    }

    debug_node_start(node);

    type = nd_type(node);

    switch (type) {
      case NODE_BLOCK:{
	while (node && nd_type(node) == NODE_BLOCK) {
	    COMPILE_(ret, "BLOCK body", node->nd_head,
		     (node->nd_next == 0 && poped == 0) ? 0 : 1);
	    node = node->nd_next;
	}
	if (node) {
	    COMPILE_(ret, "BLOCK next", node->nd_next, poped);
	}
	break;
      }
      case NODE_IF:{
	DECL_ANCHOR(cond_seq);
	DECL_ANCHOR(then_seq);
	DECL_ANCHOR(else_seq);
	LABEL *then_label, *else_label, *end_label;

	INIT_ANCHOR(cond_seq);
	INIT_ANCHOR(then_seq);
	INIT_ANCHOR(else_seq);
	then_label = NEW_LABEL(line);
	else_label = NEW_LABEL(line);
	end_label = NEW_LABEL(line);

	compile_branch_condition(iseq, cond_seq, node->nd_cond,
				 then_label, else_label);
	COMPILE_(then_seq, "then", node->nd_body, poped);
	COMPILE_(else_seq, "else", node->nd_else, poped);

	ADD_SEQ(ret, cond_seq);

	ADD_LABEL(ret, then_label);
	ADD_SEQ(ret, then_seq);
	ADD_INSNL(ret, line, jump, end_label);

	ADD_LABEL(ret, else_label);
	ADD_SEQ(ret, else_seq);

	ADD_LABEL(ret, end_label);

	break;
      }
      case NODE_CASE:{
	NODE *vals;
	NODE *tempnode = node;
	LABEL *endlabel, *elselabel;
	DECL_ANCHOR(head);
	DECL_ANCHOR(body_seq);
	DECL_ANCHOR(cond_seq);
	int only_special_literals = 1;
	VALUE literals = rb_hash_new();

	INIT_ANCHOR(head);
	INIT_ANCHOR(body_seq);
	INIT_ANCHOR(cond_seq);

	rb_hash_tbl_raw(literals)->type = &cdhash_type;

	if (node->nd_head == 0) {
	    COMPILE_(ret, "when", node->nd_body, poped);
	    break;
	}
	COMPILE(head, "case base", node->nd_head);

	node = node->nd_body;
	type = nd_type(node);
	line = nd_line(node);

	if (type != NODE_WHEN) {
	    COMPILE_ERROR((ERROR_ARGS "NODE_CASE: unexpected node. must be NODE_WHEN, but %s", ruby_node_name(type)));
	}

	endlabel = NEW_LABEL(line);
	elselabel = NEW_LABEL(line);

	ADD_SEQ(ret, head);	/* case VAL */

	while (type == NODE_WHEN) {
	    LABEL *l1;

	    l1 = NEW_LABEL(line);
	    ADD_LABEL(body_seq, l1);
	    ADD_INSN(body_seq, line, pop);
	    COMPILE_(body_seq, "when body", node->nd_body, poped);
	    ADD_INSNL(body_seq, line, jump, endlabel);

	    vals = node->nd_head;
	    if (vals) {
		switch (nd_type(vals)) {
		  case NODE_ARRAY:
		    only_special_literals = when_vals(iseq, cond_seq, vals, l1, only_special_literals, literals);
		    break;
		  case NODE_SPLAT:
		  case NODE_ARGSCAT:
		  case NODE_ARGSPUSH:
		    only_special_literals = 0;
		    ADD_INSN (cond_seq, nd_line(vals), dup);
		    COMPILE(cond_seq, "when/cond splat", vals);
		    ADD_INSN1(cond_seq, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_CASE | VM_CHECKMATCH_ARRAY));
		    ADD_INSNL(cond_seq, nd_line(vals), branchif, l1);
		    break;
		  default:
		    rb_bug("NODE_CASE: unknown node (%s)",
			   ruby_node_name(nd_type(vals)));
		}
	    }
	    else {
		rb_bug("NODE_CASE: must be NODE_ARRAY, but 0");
	    }

	    node = node->nd_next;
	    if (!node) {
		break;
	    }
	    type = nd_type(node);
	    line = nd_line(node);
	}
	/* else */
	if (node) {
	    ADD_LABEL(cond_seq, elselabel);
	    ADD_INSN(cond_seq, line, pop);
	    COMPILE_(cond_seq, "else", node, poped);
	    ADD_INSNL(cond_seq, line, jump, endlabel);
	}
	else {
	    debugs("== else (implicit)\n");
	    ADD_LABEL(cond_seq, elselabel);
	    ADD_INSN(cond_seq, nd_line(tempnode), pop);
	    if (!poped) {
		ADD_INSN(cond_seq, nd_line(tempnode), putnil);
	    }
	    ADD_INSNL(cond_seq, nd_line(tempnode), jump, endlabel);
	}

	if (only_special_literals) {
	    iseq_add_mark_object(iseq, literals);

	    ADD_INSN(ret, nd_line(tempnode), dup);
	    ADD_INSN2(ret, nd_line(tempnode), opt_case_dispatch, literals, elselabel);
	}

	ADD_SEQ(ret, cond_seq);
	ADD_SEQ(ret, body_seq);
	ADD_LABEL(ret, endlabel);
	break;
      }
      case NODE_WHEN:{
	NODE *vals;
	NODE *val;
	NODE *orig_node = node;
	LABEL *endlabel;
	DECL_ANCHOR(body_seq);

	INIT_ANCHOR(body_seq);
	endlabel = NEW_LABEL(line);

	while (node && nd_type(node) == NODE_WHEN) {
	    LABEL *l1 = NEW_LABEL(line = nd_line(node));
	    ADD_LABEL(body_seq, l1);
	    COMPILE_(body_seq, "when", node->nd_body, poped);
	    ADD_INSNL(body_seq, line, jump, endlabel);

	    vals = node->nd_head;
	    if (!vals) {
		rb_bug("NODE_WHEN: must be NODE_ARRAY, but 0");
	    }
	    switch (nd_type(vals)) {
	      case NODE_ARRAY:
		while (vals) {
		    val = vals->nd_head;
		    COMPILE(ret, "when2", val);
		    ADD_INSNL(ret, nd_line(val), branchif, l1);
		    vals = vals->nd_next;
		}
		break;
	      case NODE_SPLAT:
	      case NODE_ARGSCAT:
	      case NODE_ARGSPUSH:
		ADD_INSN(ret, nd_line(vals), putnil);
		COMPILE(ret, "when2/cond splat", vals);
		ADD_INSN1(ret, nd_line(vals), checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_WHEN | VM_CHECKMATCH_ARRAY));
		ADD_INSNL(ret, nd_line(vals), branchif, l1);
		break;
	      default:
		rb_bug("NODE_WHEN: unknown node (%s)",
		       ruby_node_name(nd_type(vals)));
	    }
	    node = node->nd_next;
	}
	/* else */
	COMPILE_(ret, "else", node, poped);
	ADD_INSNL(ret, nd_line(orig_node), jump, endlabel);

	ADD_SEQ(ret, body_seq);
	ADD_LABEL(ret, endlabel);

	break;
      }
      case NODE_OPT_N:
      case NODE_WHILE:
      case NODE_UNTIL:{
	LABEL *prev_start_label = iseq->compile_data->start_label;
	LABEL *prev_end_label = iseq->compile_data->end_label;
	LABEL *prev_redo_label = iseq->compile_data->redo_label;
	int prev_loopval_popped = iseq->compile_data->loopval_popped;

	struct iseq_compile_data_ensure_node_stack enl;

	LABEL *next_label = iseq->compile_data->start_label = NEW_LABEL(line);	/* next  */
	LABEL *redo_label = iseq->compile_data->redo_label = NEW_LABEL(line);	/* redo  */
	LABEL *break_label = iseq->compile_data->end_label = NEW_LABEL(line);	/* break */
	LABEL *end_label = NEW_LABEL(line);

	LABEL *next_catch_label = NEW_LABEL(line);
	LABEL *tmp_label = NULL;

	iseq->compile_data->loopval_popped = 0;
	push_ensure_entry(iseq, &enl, 0, 0);

	if (type == NODE_OPT_N || node->nd_state == 1) {
	    ADD_INSNL(ret, line, jump, next_label);
	}
	else {
	    tmp_label = NEW_LABEL(line);
	    ADD_INSNL(ret, line, jump, tmp_label);
	}
	ADD_INSN(ret, line, putnil);
	ADD_LABEL(ret, next_catch_label);
	ADD_INSN(ret, line, pop);
	ADD_INSNL(ret, line, jump, next_label);
	if (tmp_label) ADD_LABEL(ret, tmp_label);

	ADD_LABEL(ret, redo_label);
	COMPILE_POPED(ret, "while body", node->nd_body);
	ADD_LABEL(ret, next_label);	/* next */

	if (type == NODE_WHILE) {
	    compile_branch_condition(iseq, ret, node->nd_cond,
				     redo_label, end_label);
	}
	else if (type == NODE_UNTIL) {
	    /* until */
	    compile_branch_condition(iseq, ret, node->nd_cond,
				     end_label, redo_label);
	}
	else {
	    ADD_CALL_RECEIVER(ret, line);
	    ADD_CALL(ret, line, idGets, INT2FIX(0));
	    ADD_INSNL(ret, line, branchif, redo_label);
	    /* opt_n */
	}

	ADD_LABEL(ret, end_label);

	if (node->nd_state == Qundef) {
	    /* ADD_INSN(ret, line, putundef); */
	    rb_bug("unsupported: putundef");
	}
	else {
	    ADD_INSN(ret, line, putnil);
	}

	ADD_LABEL(ret, break_label);	/* break */

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}

	ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, redo_label, break_label,
			0, break_label);
	ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, redo_label, break_label, 0,
			next_catch_label);
	ADD_CATCH_ENTRY(CATCH_TYPE_REDO, redo_label, break_label, 0,
			iseq->compile_data->redo_label);

	iseq->compile_data->start_label = prev_start_label;
	iseq->compile_data->end_label = prev_end_label;
	iseq->compile_data->redo_label = prev_redo_label;
	iseq->compile_data->loopval_popped = prev_loopval_popped;
	iseq->compile_data->ensure_node_stack = iseq->compile_data->ensure_node_stack->prev;
	break;
      }
      case NODE_ITER:
      case NODE_FOR:{
	VALUE prevblock = iseq->compile_data->current_block;
	LABEL *retry_label = NEW_LABEL(line);
	LABEL *retry_end_l = NEW_LABEL(line);

	ADD_LABEL(ret, retry_label);
	if (nd_type(node) == NODE_FOR) {
	    COMPILE(ret, "iter caller (for)", node->nd_iter);

	    iseq->compile_data->current_block =
		NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq),
				  ISEQ_TYPE_BLOCK, line);

	    ADD_SEND_WITH_BLOCK(ret, line, idEach, INT2FIX(0), iseq->compile_data->current_block);
	}
	else {
	    iseq->compile_data->current_block =
		NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq),
				  ISEQ_TYPE_BLOCK, line);
	    COMPILE(ret, "iter caller", node->nd_iter);
	}
	ADD_LABEL(ret, retry_end_l);

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}

	iseq->compile_data->current_block = prevblock;

	ADD_CATCH_ENTRY(CATCH_TYPE_BREAK, retry_label, retry_end_l, 0, retry_end_l);

	break;
      }
      case NODE_BREAK:{
	unsigned long level = 0;

	if (iseq->compile_data->redo_label != 0) {
	    /* while/until */
	    LABEL *splabel = NEW_LABEL(0);
	    ADD_LABEL(ret, splabel);
	    ADD_ADJUST(ret, line, iseq->compile_data->redo_label);
	    COMPILE_(ret, "break val (while/until)", node->nd_stts, iseq->compile_data->loopval_popped);
	    add_ensure_iseq(ret, iseq, 0);
	    ADD_INSNL(ret, line, jump, iseq->compile_data->end_label);
	    ADD_ADJUST_RESTORE(ret, splabel);

	    if (!poped) {
		ADD_INSN(ret, line, putnil);
	    }
	}
	else if (iseq->type == ISEQ_TYPE_BLOCK) {
	  break_by_insn:
	    /* escape from block */
	    COMPILE(ret, "break val (block)", node->nd_stts);
	    ADD_INSN1(ret, line, throw, INT2FIX(level | 0x02) /* TAG_BREAK */ );
	    if (poped) {
		ADD_INSN(ret, line, pop);
	    }
	}
	else if (iseq->type == ISEQ_TYPE_EVAL) {
	  break_in_eval:
	    COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with break"));
	}
	else {
	    rb_iseq_t *ip = iseq->parent_iseq;
	    while (ip) {
		if (!ip->compile_data) {
		    ip = 0;
		    break;
		}

		level++;
		if (ip->compile_data->redo_label != 0) {
		    level = 0x8000;
		    if (ip->compile_data->loopval_popped == 0) {
			/* need value */
			level |= 0x4000;
		    }
		    goto break_by_insn;
		}
		else if (ip->type == ISEQ_TYPE_BLOCK) {
		    level <<= 16;
		    goto break_by_insn;
		}
		else if (ip->type == ISEQ_TYPE_EVAL) {
		    goto break_in_eval;
		}

		ip = ip->parent_iseq;
	    }
	    COMPILE_ERROR((ERROR_ARGS "Invalid break"));
	}
	break;
      }
      case NODE_NEXT:{
	unsigned long level = 0;

	if (iseq->compile_data->redo_label != 0) {
	    LABEL *splabel = NEW_LABEL(0);
	    debugs("next in while loop\n");
	    ADD_LABEL(ret, splabel);
	    COMPILE(ret, "next val/valid syntax?", node->nd_stts);
	    add_ensure_iseq(ret, iseq, 0);
	    ADD_ADJUST(ret, line, iseq->compile_data->redo_label);
	    ADD_INSNL(ret, line, jump, iseq->compile_data->start_label);
	    ADD_ADJUST_RESTORE(ret, splabel);
	    if (!poped) {
		ADD_INSN(ret, line, putnil);
	    }
	}
	else if (iseq->compile_data->end_label) {
	    LABEL *splabel = NEW_LABEL(0);
	    debugs("next in block\n");
	    ADD_LABEL(ret, splabel);
	    ADD_ADJUST(ret, line, iseq->compile_data->start_label);
	    COMPILE(ret, "next val", node->nd_stts);
	    add_ensure_iseq(ret, iseq, 0);
	    ADD_INSNL(ret, line, jump, iseq->compile_data->end_label);
	    ADD_ADJUST_RESTORE(ret, splabel);

	    if (!poped) {
		ADD_INSN(ret, line, putnil);
	    }
	}
	else if (iseq->type == ISEQ_TYPE_EVAL) {
	  next_in_eval:
	    COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with next"));
	}
	else {
	    rb_iseq_t *ip;
	    ip = iseq;
	    while (ip) {
		if (!ip->compile_data) {
		    ip = 0;
		    break;
		}

		level = 0x8000 | 0x4000;
		if (ip->compile_data->redo_label != 0) {
		    /* while loop */
		    break;
		}
		else if (ip->type == ISEQ_TYPE_BLOCK) {
		    break;
		}
		else if (ip->type == ISEQ_TYPE_EVAL) {
		    goto next_in_eval;
		}

		ip = ip->parent_iseq;
	    }
	    if (ip != 0) {
		COMPILE(ret, "next val", node->nd_stts);
		ADD_INSN1(ret, line, throw, INT2FIX(level | 0x03) /* TAG_NEXT */ );

		if (poped) {
		    ADD_INSN(ret, line, pop);
		}
	    }
	    else {
		COMPILE_ERROR((ERROR_ARGS "Invalid next"));
	    }
	}
	break;
      }
      case NODE_REDO:{
	if (iseq->compile_data->redo_label) {
	    LABEL *splabel = NEW_LABEL(0);
	    debugs("redo in while");
	    ADD_LABEL(ret, splabel);
	    ADD_ADJUST(ret, line, iseq->compile_data->redo_label);
	    add_ensure_iseq(ret, iseq, 0);
	    ADD_INSNL(ret, line, jump, iseq->compile_data->redo_label);
	    ADD_ADJUST_RESTORE(ret, splabel);
	    if (!poped) {
		ADD_INSN(ret, line, putnil);
	    }
	}
	else if (iseq->type == ISEQ_TYPE_EVAL) {
	  redo_in_eval:
	    COMPILE_ERROR((ERROR_ARGS "Can't escape from eval with redo"));
	}
	else if (iseq->compile_data->start_label) {
	    LABEL *splabel = NEW_LABEL(0);

	    debugs("redo in block");
	    ADD_LABEL(ret, splabel);
	    add_ensure_iseq(ret, iseq, 0);
	    ADD_ADJUST(ret, line, iseq->compile_data->start_label);
	    ADD_INSNL(ret, line, jump, iseq->compile_data->start_label);
	    ADD_ADJUST_RESTORE(ret, splabel);

	    if (!poped) {
		ADD_INSN(ret, line, putnil);
	    }
	}
	else {
	    rb_iseq_t *ip;
	    unsigned long level;
	    level = 0x8000 | 0x4000;
	    ip = iseq;
	    while (ip) {
		if (!ip->compile_data) {
		    ip = 0;
		    break;
		}

		if (ip->compile_data->redo_label != 0) {
		    break;
		}
		else if (ip->type == ISEQ_TYPE_BLOCK) {
		    break;
		}
		else if (ip->type == ISEQ_TYPE_EVAL) {
		    goto redo_in_eval;
		}

		ip = ip->parent_iseq;
	    }
	    if (ip != 0) {
		ADD_INSN(ret, line, putnil);
		ADD_INSN1(ret, line, throw, INT2FIX(level | 0x05) /* TAG_REDO */ );

		if (poped) {
		    ADD_INSN(ret, line, pop);
		}
	    }
	    else {
		COMPILE_ERROR((ERROR_ARGS "Invalid redo"));
	    }
	}
	break;
      }
      case NODE_RETRY:{
	if (iseq->type == ISEQ_TYPE_RESCUE) {
	    ADD_INSN(ret, line, putnil);
	    ADD_INSN1(ret, line, throw, INT2FIX(0x04) /* TAG_RETRY */ );

	    if (poped) {
		ADD_INSN(ret, line, pop);
	    }
	}
	else {
	    COMPILE_ERROR((ERROR_ARGS "Invalid retry"));
	}
	break;
      }
      case NODE_BEGIN:{
	COMPILE_(ret, "NODE_BEGIN", node->nd_body, poped);
	break;
      }
      case NODE_RESCUE:{
	LABEL *lstart = NEW_LABEL(line);
	LABEL *lend = NEW_LABEL(line);
	LABEL *lcont = NEW_LABEL(line);
	VALUE rescue = NEW_CHILD_ISEQVAL(
	    node->nd_resq,
	    rb_str_concat(rb_str_new2("rescue in "), iseq->location.label),
	    ISEQ_TYPE_RESCUE, line);

	ADD_LABEL(ret, lstart);
	COMPILE(ret, "rescue head", node->nd_head);
	ADD_LABEL(ret, lend);
	if (node->nd_else) {
	    ADD_INSN(ret, line, pop);
	    COMPILE(ret, "rescue else", node->nd_else);
	}
	ADD_INSN(ret, line, nop);
	ADD_LABEL(ret, lcont);

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}

	/* register catch entry */
	ADD_CATCH_ENTRY(CATCH_TYPE_RESCUE, lstart, lend, rescue, lcont);
	ADD_CATCH_ENTRY(CATCH_TYPE_RETRY, lend, lcont, 0, lstart);
	break;
      }
      case NODE_RESBODY:{
	NODE *resq = node;
	NODE *narg;
	LABEL *label_miss, *label_hit;

	while (resq) {
	    label_miss = NEW_LABEL(line);
	    label_hit = NEW_LABEL(line);

	    narg = resq->nd_args;
	    if (narg) {
		switch (nd_type(narg)) {
		  case NODE_ARRAY:
		    while (narg) {
			ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0));
			COMPILE(ret, "rescue arg", narg->nd_head);
			ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE));
			ADD_INSNL(ret, line, branchif, label_hit);
			narg = narg->nd_next;
		    }
		    break;
		  case NODE_SPLAT:
		  case NODE_ARGSCAT:
		  case NODE_ARGSPUSH:
		    ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0));
		    COMPILE(ret, "rescue/cond splat", narg);
		    ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE | VM_CHECKMATCH_ARRAY));
		    ADD_INSNL(ret, line, branchif, label_hit);
		    break;
		  default:
		    rb_bug("NODE_RESBODY: unknown node (%s)",
			   ruby_node_name(nd_type(narg)));
		}
	    }
	    else {
		ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0));
		ADD_INSN1(ret, line, putobject, rb_eStandardError);
		ADD_INSN1(ret, line, checkmatch, INT2FIX(VM_CHECKMATCH_TYPE_RESCUE));
		ADD_INSNL(ret, line, branchif, label_hit);
	    }
	    ADD_INSNL(ret, line, jump, label_miss);
	    ADD_LABEL(ret, label_hit);
	    COMPILE(ret, "resbody body", resq->nd_body);
	    if (iseq->compile_data->option->tailcall_optimization) {
		ADD_INSN(ret, line, nop);
	    }
	    ADD_INSN(ret, line, leave);
	    ADD_LABEL(ret, label_miss);
	    resq = resq->nd_head;
	}
	break;
      }
      case NODE_ENSURE:{
	DECL_ANCHOR(ensr);
	VALUE ensure = NEW_CHILD_ISEQVAL(node->nd_ensr,
					 rb_str_concat(rb_str_new2
						       ("ensure in "),
						       iseq->location.label),
					 ISEQ_TYPE_ENSURE, line);
	LABEL *lstart = NEW_LABEL(line);
	LABEL *lend = NEW_LABEL(line);
	LABEL *lcont = NEW_LABEL(line);
	struct ensure_range er;
	struct iseq_compile_data_ensure_node_stack enl;
	struct ensure_range *erange;

	INIT_ANCHOR(ensr);
	COMPILE_POPED(ensr, "ensure ensr", node->nd_ensr);

	er.begin = lstart;
	er.end = lend;
	er.next = 0;
	push_ensure_entry(iseq, &enl, &er, node->nd_ensr);

	ADD_LABEL(ret, lstart);
	COMPILE_(ret, "ensure head", node->nd_head, poped);
	ADD_LABEL(ret, lend);
	if (ensr->anchor.next == 0) {
	    ADD_INSN(ret, line, nop);
	}
	else {
	    ADD_SEQ(ret, ensr);
	}
	ADD_LABEL(ret, lcont);

	erange = iseq->compile_data->ensure_node_stack->erange;
	while (erange) {
	    ADD_CATCH_ENTRY(CATCH_TYPE_ENSURE, erange->begin, erange->end,
			    ensure, lcont);
	    erange = erange->next;
	}

	iseq->compile_data->ensure_node_stack = enl.prev;
	break;
      }

      case NODE_AND:
      case NODE_OR:{
	LABEL *end_label = NEW_LABEL(line);
	COMPILE(ret, "nd_1st", node->nd_1st);
	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}
	if (type == NODE_AND) {
	    ADD_INSNL(ret, line, branchunless, end_label);
	}
	else {
	    ADD_INSNL(ret, line, branchif, end_label);
	}
	if (!poped) {
	    ADD_INSN(ret, line, pop);
	}
	COMPILE_(ret, "nd_2nd", node->nd_2nd, poped);
	ADD_LABEL(ret, end_label);
	break;
      }

      case NODE_MASGN:{
	compile_massign(iseq, ret, node, poped);
	break;
      }

      case NODE_LASGN:{
	ID id = node->nd_vid;
	int idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id);

	debugs("lvar: %"PRIsVALUE" idx: %d\n", rb_id2str(id), idx);
	COMPILE(ret, "rvalue", node->nd_value);

	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}
	ADD_INSN2(ret, line, setlocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq)));

	break;
      }
      case NODE_DASGN:
      case NODE_DASGN_CURR:{
	int idx, lv, ls;
	COMPILE(ret, "dvalue", node->nd_value);
	debugi("dassn id", rb_id2str(node->nd_vid) ? node->nd_vid : '*');

	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}

	idx = get_dyna_var_idx(iseq, node->nd_vid, &lv, &ls);

	if (idx < 0) {
	    rb_bug("NODE_DASGN(_CURR): unknown id (%"PRIsVALUE")", rb_id2str(node->nd_vid));
	}

	ADD_INSN2(ret, line, setlocal, INT2FIX(ls - idx), INT2FIX(lv));
	break;
      }
      case NODE_GASGN:{
	COMPILE(ret, "lvalue", node->nd_value);

	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}
	ADD_INSN1(ret, line, setglobal,
		  ((VALUE)node->nd_entry | 1));
	break;
      }
      case NODE_IASGN:
      case NODE_IASGN2:{
	COMPILE(ret, "lvalue", node->nd_value);
	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}
	ADD_INSN2(ret, line, setinstancevariable,
		  ID2SYM(node->nd_vid), INT2FIX(iseq->is_size++));
	break;
      }
      case NODE_CDECL:{
	COMPILE(ret, "lvalue", node->nd_value);

	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}

	if (node->nd_vid) {
	    ADD_INSN1(ret, line, putspecialobject,
		      INT2FIX(VM_SPECIAL_OBJECT_CONST_BASE));
	    ADD_INSN1(ret, line, setconstant, ID2SYM(node->nd_vid));
	}
	else {
	    compile_cpath(ret, iseq, node->nd_else);
	    ADD_INSN1(ret, line, setconstant, ID2SYM(node->nd_else->nd_mid));
	}
	break;
      }
      case NODE_CVASGN:{
	COMPILE(ret, "cvasgn val", node->nd_value);
	if (!poped) {
	    ADD_INSN(ret, line, dup);
	}
	ADD_INSN1(ret, line, setclassvariable,
		  ID2SYM(node->nd_vid));
	break;
      }
      case NODE_OP_ASGN1: {
	DECL_ANCHOR(args);
	VALUE argc;
	unsigned int flag = 0;
	unsigned int asgnflag = 0;
	ID id = node->nd_mid;
	int boff = 0;

	/*
	 * a[x] (op)= y
	 *
	 * nil       # nil
	 * eval a    # nil a
	 * eval x    # nil a x
	 * dupn 2    # nil a x a x
	 * send :[]  # nil a x a[x]
	 * eval y    # nil a x a[x] y
	 * send op   # nil a x ret
	 * setn 3    # ret a x ret
	 * send []=  # ret ?
	 * pop       # ret
	 */

	/*
	 * nd_recv[nd_args->nd_body] (nd_mid)= nd_args->nd_head;
	 * NODE_OP_ASGN nd_recv
	 *              nd_args->nd_head
	 *              nd_args->nd_body
	 *              nd_mid
	 */

	if (!poped) {
	    ADD_INSN(ret, line, putnil);
	}
	asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN1 recv", node);
	switch (nd_type(node->nd_args->nd_head)) {
	  case NODE_ZARRAY:
	    argc = INT2FIX(0);
	    break;
	  case NODE_BLOCK_PASS:
	    boff = 1;
	  default:
	    INIT_ANCHOR(args);
	    argc = setup_args(iseq, args, node->nd_args->nd_head, &flag, NULL);
	    ADD_SEQ(ret, args);
	}
	ADD_INSN1(ret, line, dupn, FIXNUM_INC(argc, 1 + boff));
	flag |= asgnflag;
	ADD_SEND_WITH_FLAG(ret, line, idAREF, argc, INT2FIX(flag));

	if (id == 0 || id == 1) {
	    /* 0: or, 1: and
	       a[x] ||= y

	       unless/if a[x]
	       a[x]= y
	       else
	       nil
	       end
	    */
	    LABEL *label = NEW_LABEL(line);
	    LABEL *lfin = NEW_LABEL(line);

	    ADD_INSN(ret, line, dup);
	    if (id == 0) {
		/* or */
		ADD_INSNL(ret, line, branchif, label);
	    }
	    else {
		/* and */
		ADD_INSNL(ret, line, branchunless, label);
	    }
	    ADD_INSN(ret, line, pop);

	    COMPILE(ret, "NODE_OP_ASGN1 args->body: ", node->nd_args->nd_body);
	    if (!poped) {
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff));
	    }
	    if (flag & VM_CALL_ARGS_SPLAT) {
		ADD_INSN1(ret, line, newarray, INT2FIX(1));
		if (boff > 0) {
		    ADD_INSN1(ret, line, dupn, INT2FIX(3));
		    ADD_INSN(ret, line, swap);
		    ADD_INSN(ret, line, pop);
		}
		ADD_INSN(ret, line, concatarray);
		if (boff > 0) {
		    ADD_INSN1(ret, line, setn, INT2FIX(3));
		    ADD_INSN(ret, line, pop);
		    ADD_INSN(ret, line, pop);
		}
		ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag));
	    }
	    else {
		if (boff > 0)
		    ADD_INSN(ret, line, swap);
		ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag));
	    }
	    ADD_INSN(ret, line, pop);
	    ADD_INSNL(ret, line, jump, lfin);
	    ADD_LABEL(ret, label);
	    if (!poped) {
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff));
	    }
	    ADD_INSN1(ret, line, adjuststack, FIXNUM_INC(argc, 2+boff));
	    ADD_LABEL(ret, lfin);
	}
	else {
	    COMPILE(ret, "NODE_OP_ASGN1 args->body: ", node->nd_args->nd_body);
	    ADD_SEND(ret, line, id, INT2FIX(1));
	    if (!poped) {
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2+boff));
	    }
	    if (flag & VM_CALL_ARGS_SPLAT) {
		ADD_INSN1(ret, line, newarray, INT2FIX(1));
		if (boff > 0) {
		    ADD_INSN1(ret, line, dupn, INT2FIX(3));
		    ADD_INSN(ret, line, swap);
		    ADD_INSN(ret, line, pop);
		}
		ADD_INSN(ret, line, concatarray);
		if (boff > 0) {
		    ADD_INSN1(ret, line, setn, INT2FIX(3));
		    ADD_INSN(ret, line, pop);
		    ADD_INSN(ret, line, pop);
		}
		ADD_SEND_WITH_FLAG(ret, line, idASET, argc, INT2FIX(flag));
	    }
	    else {
		if (boff > 0)
		    ADD_INSN(ret, line, swap);
		ADD_SEND_WITH_FLAG(ret, line, idASET, FIXNUM_INC(argc, 1), INT2FIX(flag));
	    }
	    ADD_INSN(ret, line, pop);
	}

	break;
      }
      case NODE_OP_ASGN2:{
	ID atype = node->nd_next->nd_mid;
	VALUE asgnflag;
	LABEL *lfin = NEW_LABEL(line);
	LABEL *lcfin = NEW_LABEL(line);
	/*
	  class C; attr_accessor :c; end
	  r = C.new
	  r.a &&= v # asgn2

	  eval r    # r
	  dup       # r r
	  eval r.a  # r o

	  # or
	  dup       # r o o
	  if lcfin  # r o
	  pop       # r
	  eval v    # r v
	  swap      # v r
	  topn 1    # v r v
	  send a=   # v ?
	  jump lfin # v ?

	  lcfin:      # r o
	  swap      # o r

	  lfin:       # o ?
	  pop       # o

	  # and
	  dup       # r o o
	  unless lcfin
	  pop       # r
	  eval v    # r v
	  swap      # v r
	  topn 1    # v r v
	  send a=   # v ?
	  jump lfin # v ?

	  # others
	  eval v    # r o v
	  send ??   # r w
	  send a=   # w

	*/

	asgnflag = COMPILE_RECV(ret, "NODE_OP_ASGN2#recv", node);
	ADD_INSN(ret, line, dup);
	ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_vid, INT2FIX(0), INT2FIX(asgnflag));

	if (atype == 0 || atype == 1) {	/* 0: OR or 1: AND */
	    ADD_INSN(ret, line, dup);
	    if (atype == 0) {
		ADD_INSNL(ret, line, branchif, lcfin);
	    }
	    else {
		ADD_INSNL(ret, line, branchunless, lcfin);
	    }
	    ADD_INSN(ret, line, pop);
	    COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
	    ADD_INSN(ret, line, swap);
	    ADD_INSN1(ret, line, topn, INT2FIX(1));
	    ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag));
	    ADD_INSNL(ret, line, jump, lfin);

	    ADD_LABEL(ret, lcfin);
	    ADD_INSN(ret, line, swap);

	    ADD_LABEL(ret, lfin);
	    ADD_INSN(ret, line, pop);
	    if (poped) {
		/* we can apply more optimize */
		ADD_INSN(ret, line, pop);
	    }
	}
	else {
	    COMPILE(ret, "NODE_OP_ASGN2 val", node->nd_value);
	    ADD_SEND(ret, line, node->nd_next->nd_mid,
		     INT2FIX(1));
	    if (!poped) {
		ADD_INSN(ret, line, swap);
		ADD_INSN1(ret, line, topn, INT2FIX(1));
	    }
	    ADD_SEND_WITH_FLAG(ret, line, node->nd_next->nd_aid, INT2FIX(1), INT2FIX(asgnflag));
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_OP_CDECL: {
	LABEL *lfin = 0;
	LABEL *lassign = 0;
	ID mid;

	switch (nd_type(node->nd_head)) {
	  case NODE_COLON3:
	    ADD_INSN1(ret, line, putobject, rb_cObject);
	    break;
	  case NODE_COLON2:
	    COMPILE(ret, "NODE_OP_CDECL/colon2#nd_head", node->nd_head->nd_head);
	    break;
	  default:
	    do {
		COMPILE_ERROR((ERROR_ARGS "%s: invalid node in NODE_OP_CDECL",
			       ruby_node_name(nd_type(node->nd_head))));
	    } while (0);
	    return COMPILE_NG;
	}
	mid = node->nd_head->nd_mid;
	/* cref */
	if (node->nd_aid == 0) {
	    lassign = NEW_LABEL(line);
	    ADD_INSN(ret, line, dup); /* cref cref */
	    ADD_INSN3(ret, line, defined, INT2FIX(DEFINED_CONST),
		      ID2SYM(mid), Qfalse); /* cref bool */
	    ADD_INSNL(ret, line, branchunless, lassign); /* cref */
	}
	ADD_INSN(ret, line, dup); /* cref cref */
	ADD_INSN1(ret, line, getconstant, ID2SYM(mid)); /* cref obj */

	if (node->nd_aid == 0 || node->nd_aid == 1) {
	    lfin = NEW_LABEL(line);
	    if (!poped) ADD_INSN(ret, line, dup); /* cref [obj] obj */
	    if (node->nd_aid == 0)
		ADD_INSNL(ret, line, branchif, lfin);
	    else
		ADD_INSNL(ret, line, branchunless, lfin);
	    /* cref [obj] */
	    if (!poped) ADD_INSN(ret, line, pop); /* cref */
	    if (lassign) ADD_LABEL(ret, lassign);
	    COMPILE(ret, "NODE_OP_CDECL#nd_value", node->nd_value);
	    /* cref value */
	    if (poped)
		ADD_INSN1(ret, line, topn, INT2FIX(1)); /* cref value cref */
	    else {
		ADD_INSN1(ret, line, dupn, INT2FIX(2)); /* cref value cref value */
		ADD_INSN(ret, line, swap); /* cref value value cref */
	    }
	    ADD_INSN1(ret, line, setconstant, ID2SYM(mid)); /* cref [value] */
	    ADD_LABEL(ret, lfin);			    /* cref [value] */
	    if (!poped) ADD_INSN(ret, line, swap); /* [value] cref */
	    ADD_INSN(ret, line, pop); /* [value] */
	}
	else {
	    COMPILE(ret, "NODE_OP_CDECL#nd_value", node->nd_value);
	    /* cref obj value */
	    ADD_CALL(ret, line, node->nd_aid, INT2FIX(1));
	    /* cref value */
	    ADD_INSN(ret, line, swap); /* value cref */
	    if (!poped) {
		ADD_INSN1(ret, line, topn, INT2FIX(1)); /* value cref value */
		ADD_INSN(ret, line, swap); /* value value cref */
	    }
	    ADD_INSN1(ret, line, setconstant, ID2SYM(mid));
	}
	break;
      }
      case NODE_OP_ASGN_AND:
      case NODE_OP_ASGN_OR:{
	LABEL *lfin = NEW_LABEL(line);
	LABEL *lassign;

	if (nd_type(node) == NODE_OP_ASGN_OR) {
	    LABEL *lfinish[2];
	    lfinish[0] = lfin;
	    lfinish[1] = 0;
	    defined_expr(iseq, ret, node->nd_head, lfinish, Qfalse);
	    lassign = lfinish[1];
	    if (!lassign) {
		lassign = NEW_LABEL(line);
	    }
	    ADD_INSNL(ret, line, branchunless, lassign);
	}
	else {
	    lassign = NEW_LABEL(line);
	}

	COMPILE(ret, "NODE_OP_ASGN_AND/OR#nd_head", node->nd_head);
	ADD_INSN(ret, line, dup);

	if (nd_type(node) == NODE_OP_ASGN_AND) {
	    ADD_INSNL(ret, line, branchunless, lfin);
	}
	else {
	    ADD_INSNL(ret, line, branchif, lfin);
	}

	ADD_INSN(ret, line, pop);
	ADD_LABEL(ret, lassign);
	COMPILE(ret, "NODE_OP_ASGN_AND/OR#nd_value", node->nd_value);
	ADD_LABEL(ret, lfin);

	if (poped) {
	    /* we can apply more optimize */
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_CALL:
	/* optimization shortcut
	 *   "literal".freeze -> opt_str_freeze("literal")
	 */
	if (node->nd_recv && nd_type(node->nd_recv) == NODE_STR &&
	    node->nd_mid == idFreeze && node->nd_args == NULL &&
	    iseq->compile_data->current_block == Qfalse &&
	    iseq->compile_data->option->specialized_instruction) {
	    VALUE str = rb_fstring(node->nd_recv->nd_lit);
	    iseq_add_mark_object(iseq, str);
	    ADD_INSN1(ret, line, opt_str_freeze, str);
	    if (poped) {
		ADD_INSN(ret, line, pop);
	    }
	    break;
	}
	/* optimization shortcut
	 *   obj["literal"] -> opt_aref_with(obj, "literal")
	 */
	if (node->nd_mid == idAREF && !private_recv_p(node) && node->nd_args &&
	    nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 1 &&
	    nd_type(node->nd_args->nd_head) == NODE_STR &&
	    iseq->compile_data->current_block == Qfalse &&
	    iseq->compile_data->option->specialized_instruction) {
	    VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit);
	    node->nd_args->nd_head->nd_lit = str;
	    COMPILE(ret, "recv", node->nd_recv);
	    ADD_INSN2(ret, line, opt_aref_with,
		      new_callinfo(iseq, idAREF, 1, 0, 0, NULL), str);
	    if (poped) {
		ADD_INSN(ret, line, pop);
	    }
	    break;
	}
      case NODE_FCALL:
      case NODE_VCALL:{		/* VCALL: variable or call */
	/*
	  call:  obj.method(...)
	  fcall: func(...)
	  vcall: func
	*/
	DECL_ANCHOR(recv);
	DECL_ANCHOR(args);
	ID mid = node->nd_mid;
	VALUE argc;
	unsigned int flag = 0;
	rb_call_info_kw_arg_t *keywords = NULL;
	VALUE parent_block = iseq->compile_data->current_block;
	iseq->compile_data->current_block = Qfalse;

	INIT_ANCHOR(recv);
	INIT_ANCHOR(args);
#if SUPPORT_JOKE
	if (nd_type(node) == NODE_VCALL) {
	    ID id_bitblt;
	    ID id_answer;

	    CONST_ID(id_bitblt, "bitblt");
	    CONST_ID(id_answer, "the_answer_to_life_the_universe_and_everything");

	    if (mid == id_bitblt) {
		ADD_INSN(ret, line, bitblt);
		break;
	    }
	    else if (mid == id_answer) {
		ADD_INSN(ret, line, answer);
		break;
	    }
	}
	/* only joke */
	{
	    ID goto_id;
	    ID label_id;

	    CONST_ID(goto_id, "__goto__");
	    CONST_ID(label_id, "__label__");

	    if (nd_type(node) == NODE_FCALL &&
		(mid == goto_id || mid == label_id)) {
		LABEL *label;
		st_data_t data;
		st_table *labels_table = iseq->compile_data->labels_table;
		ID label_name;

		if (!labels_table) {
		    labels_table = st_init_numtable();
		    iseq->compile_data->labels_table = labels_table;
		}
		if (nd_type(node->nd_args->nd_head) == NODE_LIT &&
		    SYMBOL_P(node->nd_args->nd_head->nd_lit)) {

		    label_name = SYM2ID(node->nd_args->nd_head->nd_lit);
		    if (!st_lookup(labels_table, (st_data_t)label_name, &data)) {
			label = NEW_LABEL(line);
			label->position = line;
			st_insert(labels_table, (st_data_t)label_name, (st_data_t)label);
		    }
		    else {
			label = (LABEL *)data;
		    }
		}
		else {
		    COMPILE_ERROR((ERROR_ARGS "invalid goto/label format"));
		}


		if (mid == goto_id) {
		    ADD_INSNL(ret, line, jump, label);
		}
		else {
		    ADD_LABEL(ret, label);
		}
		break;
	    }
	}
#endif
	/* receiver */
	if (type == NODE_CALL) {
	    COMPILE(recv, "recv", node->nd_recv);
	}
	else if (type == NODE_FCALL || type == NODE_VCALL) {
	    ADD_CALL_RECEIVER(recv, line);
	}

	/* args */
	if (nd_type(node) != NODE_VCALL) {
	    argc = setup_args(iseq, args, node->nd_args, &flag, &keywords);
	}
	else {
	    argc = INT2FIX(0);
	}

	ADD_SEQ(ret, recv);
	ADD_SEQ(ret, args);

	debugp_param("call args argc", argc);
	debugp_param("call method", ID2SYM(mid));

	switch (nd_type(node)) {
	  case NODE_VCALL:
	    flag |= VM_CALL_VCALL;
	    /* VCALL is funcall, so fall through */
	  case NODE_FCALL:
	    flag |= VM_CALL_FCALL;
	}

	ADD_SEND_R(ret, line, mid, argc, parent_block, INT2FIX(flag), keywords);

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_SUPER:
      case NODE_ZSUPER:{
	DECL_ANCHOR(args);
	int argc;
	unsigned int flag = 0;
	rb_call_info_kw_arg_t *keywords = NULL;
	VALUE parent_block = iseq->compile_data->current_block;

	INIT_ANCHOR(args);
	iseq->compile_data->current_block = Qfalse;
	if (nd_type(node) == NODE_SUPER) {
	    VALUE vargc = setup_args(iseq, args, node->nd_args, &flag, &keywords);
	    argc = FIX2INT(vargc);
	}
	else {
	    /* NODE_ZSUPER */
	    int i;
	    rb_iseq_t *liseq = iseq->local_iseq;
	    int lvar_level = get_lvar_level(iseq);

	    argc = liseq->param.lead_num;

	    /* normal arguments */
	    for (i = 0; i < liseq->param.lead_num; i++) {
		int idx = liseq->local_size - i;
		ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
	    }

	    if (liseq->param.flags.has_opt) {
		/* optional arguments */
		int j;
		for (j = 0; j < liseq->param.opt_num; j++) {
		    int idx = liseq->local_size - (i + j);
		    ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
		}
		i += j;
		argc = i;
	    }
	    if (liseq->param.flags.has_rest) {
		/* rest argument */
		int idx = liseq->local_size - liseq->param.rest_start;
		ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
		argc = liseq->param.rest_start + 1;
		flag |= VM_CALL_ARGS_SPLAT;
	    }
	    if (liseq->param.flags.has_post) {
		/* post arguments */
		int post_len = liseq->param.post_num;
		int post_start = liseq->param.post_start;

		if (liseq->param.flags.has_rest) {
		    int j;
		    for (j=0; j<post_len; j++) {
			int idx = liseq->local_size - (post_start + j);
			ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
		    }
		    ADD_INSN1(args, line, newarray, INT2FIX(j));
		    ADD_INSN (args, line, concatarray);
		    /* argc is settled at above */
		}
		else {
		    int j;
		    for (j=0; j<post_len; j++) {
			int idx = liseq->local_size - (post_start + j);
			ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
		    }
		    argc = post_len + post_start;
		}
	    }

	    if (liseq->param.flags.has_kw) { /* TODO: support keywords */
		int local_size = liseq->local_size;
		argc++;

		ADD_INSN1(args, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));

		if (liseq->param.flags.has_kwrest) {
		    ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->param.keyword->rest_start), INT2FIX(lvar_level));
		    ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0));
		}
		else {
		    ADD_INSN1(args, line, newhash, INT2FIX(0));
		}
		for (i = 0; i < liseq->param.keyword->num; ++i) {
		    ID id = liseq->param.keyword->table[i];
		    int idx = local_size - get_local_var_idx(liseq, id);
		    ADD_INSN1(args, line, putobject, ID2SYM(id));
		    ADD_INSN2(args, line, getlocal, INT2FIX(idx), INT2FIX(lvar_level));
		}
		ADD_SEND(args, line, id_core_hash_merge_ptr, INT2FIX(i * 2 + 1));
		if (liseq->param.flags.has_rest) {
		    ADD_INSN1(args, line, newarray, INT2FIX(1));
		    ADD_INSN (args, line, concatarray);
		    --argc;
		}
	    }
	    else if (liseq->param.flags.has_kwrest) {
		ADD_INSN2(args, line, getlocal, INT2FIX(liseq->local_size - liseq->param.keyword->rest_start), INT2FIX(lvar_level));
		ADD_SEND (args, line, rb_intern("dup"), INT2FIX(0));
		if (liseq->param.flags.has_rest) {
		    ADD_INSN1(args, line, newarray, INT2FIX(1));
		    ADD_INSN (args, line, concatarray);
		}
		else {
		    argc++;
		}
	    }
	}

	/* dummy receiver */
	ADD_INSN1(ret, line, putobject, nd_type(node) == NODE_ZSUPER ? Qfalse : Qtrue);
	ADD_SEQ(ret, args);
	ADD_INSN1(ret, line, invokesuper, new_callinfo(iseq, 0, argc, parent_block,
						       flag | VM_CALL_SUPER | VM_CALL_FCALL, keywords));

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_ARRAY:{
	compile_array_(iseq, ret, node, COMPILE_ARRAY_TYPE_ARRAY, NULL, poped);
	break;
      }
      case NODE_ZARRAY:{
	if (!poped) {
	    ADD_INSN1(ret, line, newarray, INT2FIX(0));
	}
	break;
      }
      case NODE_VALUES:{
	NODE *n = node;
	while (n) {
	    COMPILE(ret, "values item", n->nd_head);
	    n = n->nd_next;
	}
	ADD_INSN1(ret, line, newarray, INT2FIX(node->nd_alen));
	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_HASH:{
	DECL_ANCHOR(list);
	int type = node->nd_head ? nd_type(node->nd_head) : NODE_ZARRAY;

	INIT_ANCHOR(list);
	switch (type) {
	  case NODE_ARRAY:
	    compile_array(iseq, list, node->nd_head, COMPILE_ARRAY_TYPE_HASH);
	    ADD_SEQ(ret, list);
	    break;

	  case NODE_ZARRAY:
	    ADD_INSN1(ret, line, newhash, INT2FIX(0));
	    break;

	  default:
	    rb_bug("can't make hash with this node: %s", ruby_node_name(type));
	}

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_RETURN:{
	rb_iseq_t *is = iseq;

	if (is) {
	    if (is->type == ISEQ_TYPE_TOP) {
		COMPILE_ERROR((ERROR_ARGS "Invalid return"));
	    }
	    else {
		LABEL *splabel = 0;

		if (is->type == ISEQ_TYPE_METHOD) {
		    splabel = NEW_LABEL(0);
		    ADD_LABEL(ret, splabel);
		    ADD_ADJUST(ret, line, 0);
		}

		COMPILE(ret, "return nd_stts (return val)", node->nd_stts);

		if (is->type == ISEQ_TYPE_METHOD) {
		    add_ensure_iseq(ret, iseq, 1);
		    ADD_TRACE(ret, line, RUBY_EVENT_RETURN);
		    ADD_INSN(ret, line, leave);
		    ADD_ADJUST_RESTORE(ret, splabel);

		    if (!poped) {
			ADD_INSN(ret, line, putnil);
		    }
		}
		else {
		    ADD_INSN1(ret, line, throw, INT2FIX(0x01) /* TAG_RETURN */ );
		    if (poped) {
			ADD_INSN(ret, line, pop);
		    }
		}
	    }
	}
	break;
      }
      case NODE_YIELD:{
	DECL_ANCHOR(args);
	VALUE argc;
	unsigned int flag = 0;
	rb_call_info_kw_arg_t *keywords = NULL;

	INIT_ANCHOR(args);
	if (iseq->type == ISEQ_TYPE_TOP) {
	    COMPILE_ERROR((ERROR_ARGS "Invalid yield"));
	}

	if (node->nd_head) {
	    argc = setup_args(iseq, args, node->nd_head, &flag, &keywords);
	}
	else {
	    argc = INT2FIX(0);
	}

	ADD_SEQ(ret, args);
	ADD_INSN1(ret, line, invokeblock, new_callinfo(iseq, 0, FIX2INT(argc), 0, flag, keywords));

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_LVAR:{
	if (!poped) {
	    ID id = node->nd_vid;
	    int idx = iseq->local_iseq->local_size - get_local_var_idx(iseq, id);

	    debugs("id: %"PRIsVALUE" idx: %d\n", rb_id2str(id), idx);
	    ADD_INSN2(ret, line, getlocal, INT2FIX(idx), INT2FIX(get_lvar_level(iseq)));
	}
	break;
      }
      case NODE_DVAR:{
	int lv, idx, ls;
	debugi("nd_vid", node->nd_vid);
	if (!poped) {
	    idx = get_dyna_var_idx(iseq, node->nd_vid, &lv, &ls);
	    if (idx < 0) {
		rb_bug("unknown dvar (%"PRIsVALUE")", rb_id2str(node->nd_vid));
	    }
	    ADD_INSN2(ret, line, getlocal, INT2FIX(ls - idx), INT2FIX(lv));
	}
	break;
      }
      case NODE_GVAR:{
	ADD_INSN1(ret, line, getglobal,
		  ((VALUE)node->nd_entry | 1));
	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_IVAR:{
	debugi("nd_vid", node->nd_vid);
	if (!poped) {
	    ADD_INSN2(ret, line, getinstancevariable,
		      ID2SYM(node->nd_vid), INT2FIX(iseq->is_size++));
	}
	break;
      }
      case NODE_CONST:{
	debugi("nd_vid", node->nd_vid);

	if (iseq->compile_data->option->inline_const_cache) {
	    LABEL *lend = NEW_LABEL(line);
	    int ic_index = iseq->is_size++;

	    ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index));
	    ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_vid));
	    ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index));
	    ADD_LABEL(ret, lend);
	}
	else {
	    ADD_INSN(ret, line, putnil);
	    ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_vid));
	}

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_CVAR:{
	if (!poped) {
	    ADD_INSN1(ret, line, getclassvariable,
		      ID2SYM(node->nd_vid));
	}
	break;
      }
      case NODE_NTH_REF:{
        if (!poped) {
	    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("<class:%"PRIsVALUE">", rb_id2str(node->nd_cpath->nd_mid)),
		ISEQ_TYPE_CLASS, line);
	VALUE noscope = compile_cpath(ret, iseq, node->nd_cpath);
	int flags = VM_DEFINECLASS_TYPE_CLASS;
	if (!noscope) flags |= VM_DEFINECLASS_FLAG_SCOPED;
	if (node->nd_super) flags |= VM_DEFINECLASS_FLAG_HAS_SUPERCLASS;
	COMPILE(ret, "super", node->nd_super);
	ADD_INSN3(ret, line, defineclass,
		  ID2SYM(node->nd_cpath->nd_mid), iseqval, INT2FIX(flags));

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_MODULE:{
	VALUE iseqval = NEW_CHILD_ISEQVAL(
	    node->nd_body,
	    rb_sprintf("<module:%"PRIsVALUE">", rb_id2str(node->nd_cpath->nd_mid)),
	    ISEQ_TYPE_CLASS, line);

	VALUE noscope = compile_cpath(ret, iseq, node->nd_cpath);
	int flags = VM_DEFINECLASS_TYPE_MODULE;
	if (!noscope) flags |= VM_DEFINECLASS_FLAG_SCOPED;
	ADD_INSN (ret, line, putnil); /* dummy */
	ADD_INSN3(ret, line, defineclass,
		  ID2SYM(node->nd_cpath->nd_mid), iseqval, INT2FIX(flags));
	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_SCLASS:{
	ID singletonclass;
	VALUE iseqval =
	    NEW_ISEQVAL(node->nd_body, rb_str_new2("singleton class"),
			ISEQ_TYPE_CLASS, line);

	COMPILE(ret, "sclass#recv", node->nd_recv);
	ADD_INSN (ret, line, putnil);
	CONST_ID(singletonclass, "singletonclass");
	ADD_INSN3(ret, line, defineclass,
		  ID2SYM(singletonclass), iseqval,
		  INT2FIX(VM_DEFINECLASS_TYPE_SINGLETON_CLASS));

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_COLON2:{
	if (rb_is_const_id(node->nd_mid)) {
	    /* constant */
	    LABEL *lend = NEW_LABEL(line);
	    int ic_index = iseq->is_size++;

	    DECL_ANCHOR(pref);
	    DECL_ANCHOR(body);

	    INIT_ANCHOR(pref);
	    INIT_ANCHOR(body);
	    compile_colon2(iseq, node, pref, body);
	    if (LIST_SIZE_ZERO(pref)) {
		if (iseq->compile_data->option->inline_const_cache) {
		    ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index));
		}
		else {
		    ADD_INSN(ret, line, putnil);
		}

		ADD_SEQ(ret, body);

		if (iseq->compile_data->option->inline_const_cache) {
		    ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index));
		    ADD_LABEL(ret, lend);
		}
	    }
	    else {
		ADD_SEQ(ret, pref);
		ADD_SEQ(ret, body);
	    }
	}
	else {
	    /* function call */
	    ADD_CALL_RECEIVER(ret, line);
	    COMPILE(ret, "colon2#nd_head", node->nd_head);
	    ADD_CALL(ret, line, node->nd_mid, INT2FIX(1));
	}
	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_COLON3:{
	LABEL *lend = NEW_LABEL(line);
	int ic_index = iseq->is_size++;

	debugi("colon3#nd_mid", node->nd_mid);

	/* add cache insn */
	if (iseq->compile_data->option->inline_const_cache) {
	    ADD_INSN2(ret, line, getinlinecache, lend, INT2FIX(ic_index));
	    ADD_INSN(ret, line, pop);
	}

	ADD_INSN1(ret, line, putobject, rb_cObject);
	ADD_INSN1(ret, line, getconstant, ID2SYM(node->nd_mid));

	if (iseq->compile_data->option->inline_const_cache) {
	    ADD_INSN1(ret, line, setinlinecache, INT2FIX(ic_index));
	    ADD_LABEL(ret, lend);
	}

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_DOT2:
      case NODE_DOT3:{
	VALUE flag = type == NODE_DOT2 ? INT2FIX(0) : INT2FIX(1);
	COMPILE(ret, "min", (NODE *) node->nd_beg);
	COMPILE(ret, "max", (NODE *) node->nd_end);
	if (poped) {
	    ADD_INSN(ret, line, pop);
	    ADD_INSN(ret, line, pop);
	}
	else {
	    ADD_INSN1(ret, line, newrange, flag);
	}
	break;
      }
      case NODE_FLIP2:
      case NODE_FLIP3:{
	LABEL *lend = NEW_LABEL(line);
	LABEL *lfin = NEW_LABEL(line);
	LABEL *ltrue = NEW_LABEL(line);
	rb_iseq_t *local_iseq = iseq->local_iseq;
	rb_num_t cnt;
	VALUE key;

	cnt = local_iseq->flip_cnt++ + DEFAULT_SPECIAL_VAR_COUNT;
	key = INT2FIX(cnt);

	ADD_INSN2(ret, line, getspecial, key, INT2FIX(0));
	ADD_INSNL(ret, line, branchif, lend);

	/* *flip == 0 */
	COMPILE(ret, "flip2 beg", node->nd_beg);
	ADD_INSN(ret, line, dup);
	ADD_INSNL(ret, line, branchunless, lfin);
	if (nd_type(node) == NODE_FLIP3) {
	    ADD_INSN(ret, line, dup);
	    ADD_INSN1(ret, line, setspecial, key);
	    ADD_INSNL(ret, line, jump, lfin);
	}
	else {
	    ADD_INSN1(ret, line, setspecial, key);
	}

	/* *flip == 1 */
	ADD_LABEL(ret, lend);
	COMPILE(ret, "flip2 end", node->nd_end);
	ADD_INSNL(ret, line, branchunless, ltrue);
	ADD_INSN1(ret, line, putobject, Qfalse);
	ADD_INSN1(ret, line, setspecial, key);

	ADD_LABEL(ret, ltrue);
	ADD_INSN1(ret, line, putobject, Qtrue);

	ADD_LABEL(ret, lfin);
	break;
      }
      case NODE_SELF:{
	if (!poped) {
	    ADD_INSN(ret, line, putself);
	}
	break;
      }
      case NODE_NIL:{
	if (!poped) {
	    ADD_INSN(ret, line, putnil);
	}
	break;
      }
      case NODE_TRUE:{
	if (!poped) {
	    ADD_INSN1(ret, line, putobject, Qtrue);
	}
	break;
      }
      case NODE_FALSE:{
	if (!poped) {
	    ADD_INSN1(ret, line, putobject, Qfalse);
	}
	break;
      }
      case NODE_ERRINFO:{
	if (!poped) {
	    if (iseq->type == ISEQ_TYPE_RESCUE) {
		ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(0));
	    }
	    else {
		rb_iseq_t *ip = iseq;
		int level = 0;
		while (ip) {
		    if (ip->type == ISEQ_TYPE_RESCUE) {
			break;
		    }
		    ip = ip->parent_iseq;
		    level++;
		}
		if (ip) {
		    ADD_INSN2(ret, line, getlocal, INT2FIX(2), INT2FIX(level));
		}
		else {
		    ADD_INSN(ret, line, putnil);
		}
	    }
	}
	break;
      }
      case NODE_DEFINED:{
	if (poped) break;
	if (!node->nd_head) {
	    VALUE str = rb_iseq_defined_string(DEFINED_NIL);
	    ADD_INSN1(ret, nd_line(node), putobject, str);
	}
	else {
	    LABEL *lfinish[2];
	    lfinish[0] = NEW_LABEL(line);
	    lfinish[1] = 0;
	    ADD_INSN(ret, line, putnil);
	    defined_expr(iseq, ret, node->nd_head, lfinish, Qtrue);
	    ADD_INSN(ret, line, swap);
	    ADD_INSN(ret, line, pop);
	    if (lfinish[1]) {
		ADD_LABEL(ret, lfinish[1]);
	    }
	    ADD_LABEL(ret, lfinish[0]);
	}
	break;
      }
      case NODE_POSTEXE:{
	/* compiled to:
	 *   ONCE{ rb_mRubyVMFrozenCore::core#set_postexe{ ... } }
	 */
	int is_index = iseq->is_size++;
	VALUE once_iseq = NEW_CHILD_ISEQVAL(
	    NEW_IFUNC(build_postexe_iseq, node->nd_body),
	    make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line);

	ADD_INSN2(ret, line, once, once_iseq, INT2FIX(is_index));

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_KW_ARG:
	{
	    LABEL *end_label = NEW_LABEL(nd_line(node));
	    NODE *default_value = node->nd_body->nd_value;

	    if (default_value == (NODE *)-1) {
		/* required argument. do nothing */
		rb_bug("unreachable");
	    }
	    else if (nd_type(default_value) == NODE_LIT ||
		     nd_type(default_value) == NODE_NIL ||
		     nd_type(default_value) == NODE_TRUE ||
		     nd_type(default_value) == NODE_FALSE) {
		rb_bug("unreachable");
	    }
	    else {
		/* if keywordcheck(_kw_bits, nth_keyword)
		 *   kw = default_value
		 * end
		 */
		int kw_bits_idx = iseq->local_size - iseq->param.keyword->bits_start;
		int keyword_idx = iseq->param.keyword->num;

		ADD_INSN2(ret, line, checkkeyword, INT2FIX(kw_bits_idx), INT2FIX(keyword_idx));
		ADD_INSNL(ret, line, branchif, end_label);
		COMPILE_POPED(ret, "keyword default argument", node->nd_body);
		ADD_LABEL(ret, end_label);
	    }

	    break;
	}
      case NODE_DSYM:{
	compile_dstr(iseq, ret, node);
	if (!poped) {
	    ADD_SEND(ret, line, idIntern, INT2FIX(0));
	}
	else {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      case NODE_ATTRASGN:{
	DECL_ANCHOR(recv);
	DECL_ANCHOR(args);
	unsigned int flag = 0;
	VALUE argc;
	int asgnflag;

	/* optimization shortcut
	 *   obj["literal"] = value -> opt_aset_with(obj, "literal", value)
	 */
	if (node->nd_mid == idASET && !private_recv_p(node) && node->nd_args &&
	    nd_type(node->nd_args) == NODE_ARRAY && node->nd_args->nd_alen == 2 &&
	    nd_type(node->nd_args->nd_head) == NODE_STR &&
	    iseq->compile_data->current_block == Qfalse &&
	    iseq->compile_data->option->specialized_instruction)
	{
	    VALUE str = rb_fstring(node->nd_args->nd_head->nd_lit);
	    node->nd_args->nd_head->nd_lit = str;
	    iseq_add_mark_object(iseq, str);
	    COMPILE(ret, "recv", node->nd_recv);
	    COMPILE(ret, "value", node->nd_args->nd_next->nd_head);
	    if (!poped) {
		ADD_INSN(ret, line, swap);
		ADD_INSN1(ret, line, topn, INT2FIX(1));
	    }
	    ADD_INSN2(ret, line, opt_aset_with,
		      new_callinfo(iseq, idASET, 2, 0, 0, NULL), str);
	    ADD_INSN(ret, line, pop);
	    break;
	}

	INIT_ANCHOR(recv);
	INIT_ANCHOR(args);
	argc = setup_args(iseq, args, node->nd_args, &flag, NULL);

	flag |= (asgnflag = COMPILE_RECV(recv, "recv", node));

	debugp_param("argc", argc);
	debugp_param("nd_mid", ID2SYM(node->nd_mid));

	if (!poped) {
	    ADD_INSN(ret, line, putnil);
	    ADD_SEQ(ret, recv);
	    ADD_SEQ(ret, args);

	    if (flag & VM_CALL_ARGS_BLOCKARG) {
		ADD_INSN1(ret, line, topn, INT2FIX(1));
		if (flag & VM_CALL_ARGS_SPLAT) {
		    ADD_INSN1(ret, line, putobject, INT2FIX(-1));
		    ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag));
		}
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 3));
		ADD_INSN (ret, line, pop);
	    }
	    else if (flag & VM_CALL_ARGS_SPLAT) {
		ADD_INSN(ret, line, dup);
		ADD_INSN1(ret, line, putobject, INT2FIX(-1));
		ADD_SEND_WITH_FLAG(ret, line, idAREF, INT2FIX(1), INT2FIX(asgnflag));
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 2));
		ADD_INSN (ret, line, pop);
	    }
	    else {
		ADD_INSN1(ret, line, setn, FIXNUM_INC(argc, 1));
	    }
	}
	else {
	    ADD_SEQ(ret, recv);
	    ADD_SEQ(ret, args);
	}
	ADD_SEND_WITH_FLAG(ret, line, node->nd_mid, argc, INT2FIX(flag));
	ADD_INSN(ret, line, pop);

	break;
      }
      case NODE_PRELUDE:{
	COMPILE_POPED(ret, "prelude", node->nd_head);
	COMPILE_(ret, "body", node->nd_body, poped);
	break;
      }
      case NODE_LAMBDA:{
	/* compile same as lambda{...} */
	VALUE block = NEW_CHILD_ISEQVAL(node->nd_body, make_name_for_block(iseq), ISEQ_TYPE_BLOCK, line);
	VALUE argc = INT2FIX(0);
	ADD_INSN1(ret, line, putspecialobject, INT2FIX(VM_SPECIAL_OBJECT_VMCORE));
	ADD_CALL_WITH_BLOCK(ret, line, idLambda, argc, block);

	if (poped) {
	    ADD_INSN(ret, line, pop);
	}
	break;
      }
      default:
	rb_bug("iseq_compile_each: unknown node: %s", ruby_node_name(type));
	return COMPILE_NG;
    }

    /* check & remove redundant trace(line) */
    if (saved_last_element && ret /* ret can be 0 when error */ &&
	ret->last == saved_last_element &&
	((INSN *)saved_last_element)->insn_id == BIN(trace)) {
	POP_ELEMENT(ret);
    }

    debug_node_end();
    return COMPILE_OK;
}

/***************************/
/* instruction information */
/***************************/

static int
insn_data_length(INSN *iobj)
{
    return insn_len(iobj->insn_id);
}

static int
calc_sp_depth(int depth, INSN *insn)
{
    return insn_stack_increase(depth, insn->insn_id, insn->operands);
}

static VALUE
opobj_inspect(VALUE obj)
{
    struct RBasic *r = (struct RBasic *) obj;
    if (!SPECIAL_CONST_P(r)  && r->klass == 0) {
	switch (BUILTIN_TYPE(r)) {
	  case T_STRING:
	    obj = rb_str_new_cstr(RSTRING_PTR(obj));
	    break;
	  case T_ARRAY:
	    obj = rb_ary_dup(obj);
	    break;
	}
    }
    return rb_inspect(obj);
}



static VALUE
insn_data_to_s_detail(INSN *iobj)
{
    VALUE str = rb_sprintf("%-20s ", insn_name(iobj->insn_id));

    if (iobj->operands) {
	const char *types = insn_op_types(iobj->insn_id);
	int j;

	for (j = 0; types[j]; j++) {
	    char type = types[j];

	    switch (type) {
	      case TS_OFFSET:	/* label(destination position) */
		{
		    LABEL *lobj = (LABEL *)OPERAND_AT(iobj, j);
		    rb_str_catf(str, "<L%03d>", lobj->label_no);
		    break;
		}
		break;
	      case TS_ISEQ:	/* iseq */
		{
		    rb_iseq_t *iseq = (rb_iseq_t *)OPERAND_AT(iobj, j);
		    VALUE val = Qnil;
		    if (0 && iseq) { /* TODO: invalidate now */
			val = iseq->self;
		    }
		    rb_str_concat(str, opobj_inspect(val));
		}
		break;
	      case TS_LINDEX:
	      case TS_NUM:	/* ulong */
	      case TS_VALUE:	/* VALUE */
		{
		    VALUE v = OPERAND_AT(iobj, j);
		    rb_str_concat(str, opobj_inspect(v));
		    break;
		}
	      case TS_ID:	/* ID */
		rb_str_concat(str, opobj_inspect(OPERAND_AT(iobj, j)));
		break;
	      case TS_GENTRY:
		{
		    struct rb_global_entry *entry = (struct rb_global_entry *)
		      (OPERAND_AT(iobj, j) & (~1));
		    rb_str_append(str, rb_id2str(entry->id));
		    break;
		}
	      case TS_IC:	/* inline cache */
		rb_str_catf(str, "<ic:%d>", FIX2INT(OPERAND_AT(iobj, j)));
		break;
	      case TS_CALLINFO: /* call info */
		{
		    rb_call_info_t *ci = (rb_call_info_t *)OPERAND_AT(iobj, j);
		    rb_str_cat2(str, "<callinfo:");
		    if (ci->mid)
			rb_str_catf(str, "%"PRIsVALUE, rb_id2str(ci->mid));
		    rb_str_catf(str, ", %d>", ci->orig_argc);
		    break;
		}
	      case TS_CDHASH:	/* case/when condition cache */
		rb_str_cat2(str, "<ch>");
		break;
	      case TS_FUNCPTR:
		{
		    rb_insn_func_t func = (rb_insn_func_t)OPERAND_AT(iobj, j);
#ifdef HAVE_DLADDR
		    Dl_info info;
		    if (dladdr(func, &info) && info.dli_sname) {
			rb_str_cat2(str, info.dli_sname);
			break;
		    }
#endif
		    rb_str_catf(str, "<%p>", func);
		}
		break;
	      default:{
		rb_raise(rb_eSyntaxError, "unknown operand type: %c", type);
	      }
	    }
	    if (types[j + 1]) {
		rb_str_cat2(str, ", ");
	    }
	}
    }
    return str;
}

static void
dump_disasm_list(struct iseq_link_element *link)
{
    int pos = 0;
    INSN *iobj;
    LABEL *lobj;
    VALUE str;

    printf("-- raw disasm--------\n");

    while (link) {
	switch (link->type) {
	  case ISEQ_ELEMENT_INSN:
	    {
		iobj = (INSN *)link;
		str = insn_data_to_s_detail(iobj);
		printf("%04d %-65s(%4d)\n", pos, StringValueCStr(str), iobj->line_no);
		pos += insn_data_length(iobj);
		break;
	    }
	  case ISEQ_ELEMENT_LABEL:
	    {
		lobj = (LABEL *)link;
		printf("<L%03d>\n", lobj->label_no);
		break;
	    }
	  case ISEQ_ELEMENT_NONE:
	    {
		printf("[none]\n");
		break;
	    }
	  case ISEQ_ELEMENT_ADJUST:
	    {
		ADJUST *adjust = (ADJUST *)link;
		printf("adjust: [label: %d]\n", adjust->label ? adjust->label->label_no : -1);
		break;
	    }
	  default:
	    /* ignore */
	    rb_raise(rb_eSyntaxError, "dump_disasm_list error: %ld\n", FIX2LONG(link->type));
	}
	link = link->next;
    }
    printf("---------------------\n");
}

const char *
rb_insns_name(int i)
{
    return insn_name_info[i];
}

VALUE
rb_insns_name_array(void)
{
    VALUE ary = rb_ary_new();
    int i;
    for (i = 0; i < numberof(insn_name_info); i++) {
	rb_ary_push(ary, rb_fstring(rb_str_new2(insn_name_info[i])));
    }
    return rb_obj_freeze(ary);
}

static LABEL *
register_label(rb_iseq_t *iseq, struct st_table *labels_table, VALUE obj)
{
    LABEL *label = 0;
    st_data_t tmp;
    obj = rb_convert_type(obj, T_SYMBOL, "Symbol", "to_sym");

    if (st_lookup(labels_table, obj, &tmp) == 0) {
	label = NEW_LABEL(0);
	st_insert(labels_table, obj, (st_data_t)label);
    }
    else {
	label = (LABEL *)tmp;
    }
    return label;
}

static VALUE
get_exception_sym2type(VALUE sym)
{
#undef rb_intern
#define rb_intern(str) rb_intern_const(str)
    VALUE sym_inspect;
    static VALUE symRescue, symEnsure, symRetry;
    static VALUE symBreak, symRedo, symNext;

    if (symRescue == 0) {
	symRescue = ID2SYM(rb_intern("rescue"));
	symEnsure = ID2SYM(rb_intern("ensure"));
	symRetry  = ID2SYM(rb_intern("retry"));
	symBreak  = ID2SYM(rb_intern("break"));
	symRedo   = ID2SYM(rb_intern("redo"));
	symNext   = ID2SYM(rb_intern("next"));
    }

    if (sym == symRescue) return CATCH_TYPE_RESCUE;
    if (sym == symEnsure) return CATCH_TYPE_ENSURE;
    if (sym == symRetry)  return CATCH_TYPE_RETRY;
    if (sym == symBreak)  return CATCH_TYPE_BREAK;
    if (sym == symRedo)   return CATCH_TYPE_REDO;
    if (sym == symNext)   return CATCH_TYPE_NEXT;
    sym_inspect = rb_inspect(sym);
    rb_raise(rb_eSyntaxError, "invalid exception symbol: %s",
	     StringValuePtr(sym_inspect));
    return 0;
}

static int
iseq_build_from_ary_exception(rb_iseq_t *iseq, struct st_table *labels_table,
		     VALUE exception)
{
    int i;

    for (i=0; i<RARRAY_LEN(exception); i++) {
	VALUE v, type, eiseqval;
	const VALUE *ptr;
	LABEL *lstart, *lend, *lcont;
	unsigned int sp;

	v = rb_convert_type(RARRAY_AREF(exception, i), T_ARRAY,
					 "Array", "to_ary");
	if (RARRAY_LEN(v) != 6) {
	    rb_raise(rb_eSyntaxError, "wrong exception entry");
	}
	ptr  = RARRAY_CONST_PTR(v);
	type = get_exception_sym2type(ptr[0]);
	if (ptr[1] == Qnil) {
	    eiseqval = 0;
	}
	else {
	    eiseqval = rb_iseq_load(ptr[1], iseq->self, Qnil);
	}

	lstart = register_label(iseq, labels_table, ptr[2]);
	lend   = register_label(iseq, labels_table, ptr[3]);
	lcont  = register_label(iseq, labels_table, ptr[4]);
	sp     = NUM2UINT(ptr[5]);

	(void)sp;

	ADD_CATCH_ENTRY(type, lstart, lend, eiseqval, lcont);

	RB_GC_GUARD(v);
    }
    return COMPILE_OK;
}

static struct st_table *
insn_make_insn_table(void)
{
    struct st_table *table;
    int i;
    table = st_init_numtable();

    for (i=0; i<VM_INSTRUCTION_SIZE; i++) {
	st_insert(table, ID2SYM(rb_intern(insn_name(i))), i);
    }

    return table;
}

static VALUE
iseq_build_load_iseq(rb_iseq_t *iseq, VALUE op)
{
    VALUE iseqval;
    if (RB_TYPE_P(op, T_ARRAY)) {
	iseqval = rb_iseq_load(op, iseq->self, Qnil);
    }
    else if (CLASS_OF(op) == rb_cISeq) {
	iseqval = op;
    }
    else {
	rb_raise(rb_eSyntaxError, "ISEQ is required");
    }
    iseq_add_mark_object(iseq, iseqval);
    return iseqval;
}

static VALUE
iseq_build_callinfo_from_hash(rb_iseq_t *iseq, VALUE op)
{
    ID mid = 0;
    int orig_argc = 0;
    VALUE block = 0;
    unsigned int flag = 0;
    rb_call_info_kw_arg_t *kw_arg = 0;

    if (!NIL_P(op)) {
	VALUE vmid = rb_hash_aref(op, ID2SYM(rb_intern("mid")));
	VALUE vflag = rb_hash_aref(op, ID2SYM(rb_intern("flag")));
	VALUE vorig_argc = rb_hash_aref(op, ID2SYM(rb_intern("orig_argc")));
	VALUE vblock = rb_hash_aref(op, ID2SYM(rb_intern("blockptr")));
	VALUE vkw_arg = rb_hash_aref(op, ID2SYM(rb_intern("kw_arg")));

	if (!NIL_P(vmid)) mid = SYM2ID(vmid);
	if (!NIL_P(vflag)) flag = NUM2UINT(vflag);
	if (!NIL_P(vorig_argc)) orig_argc = FIX2INT(vorig_argc);
	if (!NIL_P(vblock)) block = iseq_build_load_iseq(iseq, vblock);

	if (!NIL_P(vkw_arg)) {
	    int i;
	    int len = RARRAY_LENINT(vkw_arg);
	    size_t n = sizeof(rb_call_info_kw_arg_t) + sizeof(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; i<len; i++) {
	VALUE obj = ptr[i];

	if (SYMBOL_P(obj)) {
	    LABEL *label = register_label(iseq, labels_table, obj);
	    ADD_LABEL(anchor, label);
	}
	else if (FIXNUM_P(obj)) {
	    line_no = NUM2INT(obj);
	}
	else if (RB_TYPE_P(obj, T_ARRAY)) {
	    VALUE *argv = 0;
	    int argc = RARRAY_LENINT(obj) - 1;
	    st_data_t insn_id;
	    VALUE insn;

	    insn = (argc < 0) ? Qnil : RARRAY_AREF(obj, 0);
	    if (st_lookup(insn_table, (st_data_t)insn, &insn_id) == 0) {
		/* TODO: exception */
		rb_compile_error(RSTRING_PTR(iseq->location.path), line_no,
				 "unknown instruction: %"PRIsVALUE,
				 rb_inspect(insn));
	    }

	    if (argc != insn_len((VALUE)insn_id)-1) {
		rb_compile_error(RSTRING_PTR(iseq->location.path), line_no,
				 "operand size mismatch");
	    }

	    if (argc > 0) {
		argv = compile_data_alloc(iseq, sizeof(VALUE) * argc);
		for (j=0; j<argc; j++) {
		    VALUE op = rb_ary_entry(obj, j+1);
		    switch (insn_op_type((VALUE)insn_id, j)) {
		      case TS_OFFSET: {
			LABEL *label = register_label(iseq, labels_table, op);
			argv[j] = (VALUE)label;
			break;
		      }
		      case TS_LINDEX:
		      case TS_NUM:
			(void)NUM2INT(op);
			argv[j] = op;
			break;
		      case TS_VALUE:
			argv[j] = op;
			iseq_add_mark_object(iseq, op);
			break;
		      case TS_ISEQ:
			{
			    if (op != Qnil) {
				argv[j] = iseq_build_load_iseq(iseq, op);
			    }
			    else {
				argv[j] = 0;
			    }
			}
			break;
		      case TS_GENTRY:
			op = rb_convert_type(op, T_SYMBOL, "Symbol", "to_sym");
			argv[j] = (VALUE)rb_global_entry(SYM2ID(op));
			break;
		      case TS_IC:
			argv[j] = op;
			if (NUM2INT(op) >= iseq->is_size) {
			    iseq->is_size = NUM2INT(op) + 1;
			}
			break;
		      case TS_CALLINFO:
			argv[j] = iseq_build_callinfo_from_hash(iseq, op);
			break;
		      case TS_ID:
			argv[j] = rb_convert_type(op, T_SYMBOL,
						  "Symbol", "to_sym");
			break;
		      case TS_CDHASH:
			{
			    int i;
			    VALUE map = rb_hash_new();

			    rb_hash_tbl_raw(map)->type = &cdhash_type;
			    op = rb_convert_type(op, T_ARRAY, "Array", "to_ary");
			    op = rb_ary_dup(op);
			    for (i=0; i<RARRAY_LEN(op); i+=2) {
				VALUE key = RARRAY_AREF(op, i);
				VALUE sym = RARRAY_AREF(op, i+1);
				LABEL *label =
				  register_label(iseq, labels_table, sym);
				rb_hash_aset(map, key, (VALUE)label | 1);
			    }
			    RB_GC_GUARD(op);
			    argv[j] = map;
			    iseq_add_mark_object_compile_time(iseq, map);
			}
			break;
		      case TS_FUNCPTR:
			{
#if SIZEOF_VALUE <= SIZEOF_LONG
			    long funcptr = NUM2LONG(op);
#else
			    LONG_LONG funcptr = NUM2LL(op);
#endif
			    argv[j] = (VALUE)funcptr;
			}
			break;
		      default:
			rb_raise(rb_eSyntaxError, "unknown operand: %c", insn_op_type((VALUE)insn_id, j));
		    }
		}
	    }
	    ADD_ELEM(anchor,
		     (LINK_ELEMENT*)new_insn_core(iseq, line_no,
						  (enum ruby_vminsn_type)insn_id, argc, argv));
	}
	else {
	    rb_raise(rb_eTypeError, "unexpected object for instruction");
	}
    }
    validate_labels(iseq, labels_table);
    st_free_table(labels_table);
    iseq_setup(iseq, anchor);
    return COMPILE_OK;
}

#define CHECK_ARRAY(v)   rb_convert_type((v), T_ARRAY, "Array", "to_ary")
#define CHECK_SYMBOL(v)  rb_convert_type((v), T_SYMBOL, "Symbol", "to_sym")

static int
int_param(int *dst, VALUE param, VALUE sym)
{
    VALUE val = rb_hash_aref(param, sym);
    switch (TYPE(val)) {
      case T_NIL:
	return FALSE;
      case T_FIXNUM:
	*dst = FIX2INT(val);
	return TRUE;
      default:
	rb_raise(rb_eTypeError, "invalid %+"PRIsVALUE" Fixnum: %+"PRIsVALUE,
		 sym, val);
    }
    return FALSE;
}

static void
iseq_build_kw(rb_iseq_t *iseq, VALUE params, VALUE keywords)
{
    int i, j;
    int len = RARRAY_LENINT(keywords);
    int default_len;
    VALUE key, sym, default_val;

    iseq->param.flags.has_kw = TRUE;

    iseq->param.keyword = ZALLOC(struct rb_iseq_param_keyword);
    iseq->param.keyword->num = len;
#define SYM(s) ID2SYM(rb_intern(#s))
    (void)int_param(&iseq->param.keyword->bits_start, params, SYM(kwbits));
    i = iseq->param.keyword->bits_start - iseq->param.keyword->num;
    iseq->param.keyword->table = &iseq->local_table[i];
#undef SYM

    /* required args */
    for (i = 0; i < len; i++) {
	VALUE val = RARRAY_AREF(keywords, i);

	if (!SYMBOL_P(val)) {
	    goto default_values;
	}
	iseq->param.keyword->table[i] = SYM2ID(val);
	iseq->param.keyword->required_num++;
    }

default_values: /* note: we intentionally preserve `i' from previous loop */
    default_len = len - i;
    if (default_len == 0) {
	return;
    }

    iseq->param.keyword->default_values = ALLOC_N(VALUE, default_len);

    for (j = 0; i < len; i++, j++) {
	key = RARRAY_AREF(keywords, i);
	CHECK_ARRAY(key);

	switch (RARRAY_LEN(key)) {
	  case 1:
	    sym = RARRAY_AREF(key, 0);
	    default_val = Qundef;
	    break;
	  case 2:
	    sym = RARRAY_AREF(key, 0);
	    default_val = RARRAY_AREF(key, 1);
	    break;
	  default:
	    rb_raise(rb_eTypeError,
		     "keyword default has unsupported len %+"PRIsVALUE,
		     key);
	}
	iseq->param.keyword->table[i] = SYM2ID(sym);
	iseq->param.keyword->default_values[j] = default_val;
    }
}

VALUE
rb_iseq_build_from_ary(rb_iseq_t *iseq, VALUE misc, VALUE locals, VALUE params,
			 VALUE exception, VALUE body)
{
#define SYM(s) ID2SYM(rb_intern(#s))
    int i, len;
    ID *tbl;
    struct st_table *labels_table = st_init_numtable();
    VALUE arg_opt_labels = rb_hash_aref(params, SYM(opt));
    VALUE keywords = rb_hash_aref(params, SYM(keyword));
    VALUE sym_arg_rest = ID2SYM(rb_intern("#arg_rest"));
    DECL_ANCHOR(anchor);
    INIT_ANCHOR(anchor);

    len = RARRAY_LENINT(locals);
    iseq->local_table_size = len;
    iseq->local_table = tbl = (ID *)ALLOC_N(ID, iseq->local_table_size);
    iseq->local_size = iseq->local_table_size + 1;

    for (i = 0; i < len; i++) {
	VALUE lv = RARRAY_AREF(locals, i);

	if (sym_arg_rest == lv) {
	    tbl[i] = 0;
	}
	else {
	    tbl[i] = FIXNUM_P(lv) ? (ID)FIX2LONG(lv) : SYM2ID(CHECK_SYMBOL(lv));
	}
    }

    /*
     * we currently ignore misc params,
     * local_size, stack_size and param.size are all calculated
     */

#define INT_PARAM(F) int_param(&iseq->param.F, params, SYM(F))
    if (INT_PARAM(lead_num)) {
	iseq->param.flags.has_lead = TRUE;
    }
    if (INT_PARAM(post_num)) iseq->param.flags.has_post = TRUE;
    if (INT_PARAM(post_start)) iseq->param.flags.has_post = TRUE;
    if (INT_PARAM(rest_start)) iseq->param.flags.has_rest = TRUE;
    if (INT_PARAM(block_start)) iseq->param.flags.has_block = TRUE;
#undef INT_PARAM

    switch (TYPE(arg_opt_labels)) {
      case T_ARRAY:
	len = RARRAY_LENINT(arg_opt_labels);
	iseq->param.flags.has_opt = !!(len - 1 >= 0);

	if (iseq->param.flags.has_opt) {
	    iseq->param.opt_num = len - 1;
	    iseq->param.opt_table = (VALUE *)ALLOC_N(VALUE, len);

	    for (i = 0; i < len; i++) {
		VALUE ent = RARRAY_AREF(arg_opt_labels, i);
		LABEL *label = register_label(iseq, labels_table, ent);

		iseq->param.opt_table[i] = (VALUE)label;
	    }
	}
      case T_NIL:
	break;
      default:
	rb_raise(rb_eTypeError, ":opt param is not an array: %+"PRIsVALUE,
		 arg_opt_labels);
    }

    switch (TYPE(keywords)) {
      case T_ARRAY:
	iseq_build_kw(iseq, params, keywords);
      case T_NIL:
	break;
      default:
	rb_raise(rb_eTypeError, ":keywords param is not an array: %+"PRIsVALUE,
		 keywords);
    }

    if (Qtrue == rb_hash_aref(params, SYM(ambiguous_param0))) {
	iseq->param.flags.ambiguous_param0 = TRUE;
    }

    if (int_param(&i, params, SYM(kwrest))) {
        if (!iseq->param.keyword) {
          iseq->param.keyword = ZALLOC(struct rb_iseq_param_keyword);
        }
        iseq->param.keyword->rest_start = i;
        iseq->param.flags.has_kwrest = TRUE;

    }
#undef SYM
    iseq_calc_param_size(iseq);

    /* exception */
    iseq_build_from_ary_exception(iseq, labels_table, exception);

    /* body */
    iseq_build_from_ary_body(iseq, anchor, body, labels_table);
    return iseq->self;
}

/* for parser */

int
rb_dvar_defined(ID id)
{
    rb_thread_t *th = GET_THREAD();
    rb_iseq_t *iseq;
    if (th->base_block && (iseq = th->base_block->iseq)) {
	while (iseq->type == ISEQ_TYPE_BLOCK ||
	       iseq->type == ISEQ_TYPE_RESCUE ||
	       iseq->type == ISEQ_TYPE_ENSURE ||
	       iseq->type == ISEQ_TYPE_EVAL ||
	       iseq->type == ISEQ_TYPE_MAIN
	       ) {
	    int i;

	    for (i = 0; i < iseq->local_table_size; i++) {
		if (iseq->local_table[i] == id) {
		    return 1;
		}
	    }
	    iseq = iseq->parent_iseq;
	}
    }
    return 0;
}

int
rb_local_defined(ID id)
{
    rb_thread_t *th = GET_THREAD();
    rb_iseq_t *iseq;

    if (th->base_block && th->base_block->iseq) {
	int i;
	iseq = th->base_block->iseq->local_iseq;

	for (i=0; i<iseq->local_table_size; i++) {
	    if (iseq->local_table[i] == id) {
		return 1;
	    }
	}
    }
    return 0;
}

int
rb_parse_in_eval(void)
{
    return GET_THREAD()->parse_in_eval > 0;
}

int
rb_parse_in_main(void)
{
    return GET_THREAD()->parse_in_eval < 0;
}

static int
caller_location(VALUE *path, VALUE *absolute_path)
{
    const rb_thread_t *const th = GET_THREAD();
    const rb_control_frame_t *const cfp =
	rb_vm_get_ruby_level_next_cfp(th, th->cfp);

    if (cfp) {
	int line = rb_vm_get_sourceline(cfp);
	*path = cfp->iseq->location.path;
	*absolute_path = cfp->iseq->location.absolute_path;
	return line;
    }
    else {
	*path = rb_str_new2("<compiled>");
	*absolute_path = *path;
	return 1;
    }
}

typedef struct {
    VALUE arg;
    rb_insn_func_t func;
    int line;
} accessor_args;

static VALUE
method_for_self(VALUE name, VALUE arg, rb_insn_func_t func,
		VALUE (*build)(rb_iseq_t *, LINK_ANCHOR *, VALUE))
{
    VALUE path, absolute_path;
    accessor_args acc;

    acc.arg = arg;
    acc.func = func;
    acc.line = caller_location(&path, &absolute_path);
    return rb_iseq_new_with_opt(NEW_IFUNC(build, (VALUE)&acc),
				rb_sym2str(name), path, absolute_path,
				INT2FIX(acc.line), 0, ISEQ_TYPE_METHOD, 0);
}

static VALUE
for_self_aref(rb_iseq_t *iseq, LINK_ANCHOR *ret, VALUE a)
{
    const accessor_args *const args = (void *)a;
    const int line = args->line;

    iseq_set_local_table(iseq, 0);
    iseq->param.lead_num = 0;
    iseq->param.size = 0;

    ADD_INSN1(ret, line, putobject, args->arg);
    ADD_INSN1(ret, line, opt_call_c_function, (VALUE)args->func);
    return Qnil;
}

static VALUE
for_self_aset(rb_iseq_t *iseq, LINK_ANCHOR *ret, VALUE a)
{
    const accessor_args *const args = (void *)a;
    const int line = args->line;
    static const ID vars[] = {1, idUScore};

    iseq_set_local_table(iseq, vars);
    iseq->param.lead_num = 1;
    iseq->param.size = 1;

    ADD_INSN2(ret, line, getlocal, INT2FIX(numberof(vars)-0), INT2FIX(0));
    ADD_INSN1(ret, line, putobject, args->arg);
    ADD_INSN1(ret, line, opt_call_c_function, (VALUE)args->func);
    ADD_INSN(ret, line, pop);
    return Qnil;
}

/*
 * func (index) -> (value)
 */
VALUE
rb_method_for_self_aref(VALUE name, VALUE arg, rb_insn_func_t func)
{
    return method_for_self(name, arg, func, for_self_aref);
}

/*
 * func (index, value) -> (index, value)
 */
VALUE
rb_method_for_self_aset(VALUE name, VALUE arg, rb_insn_func_t func)
{
    return method_for_self(name, arg, func, for_self_aset);
}
