Skip to content
Permalink
a170b76906
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
8477 lines (7523 sloc) 219 KB
/**********************************************************************
compile.c - ruby node tree -> VM instruction sequence
$Author$
created at: 04/01/01 03:42:15 JST
Copyright (C) 2004-2007 Koichi Sasada
**********************************************************************/
#include "internal.h"
#include "ruby/re.h"
#include "encindex.h"
#include <math.h>
#define USE_INSN_STACK_INCREASE 1
#include "vm_core.h"
#include "iseq.h"
#include "insns.inc"
#include "insns_info.inc"
#include "gc.h"
#ifdef HAVE_DLADDR
# include <dlfcn.h>
#endif
#undef RUBY_UNTYPED_DATA_WARNING
#define RUBY_UNTYPED_DATA_WARNING 0
#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 enum {
LABEL_RESCUE_NONE,
LABEL_RESCUE_BEG,
LABEL_RESCUE_END,
LABEL_RESCUE_TYPE_MAX
} LABEL_RESCUE_TYPE;
typedef struct iseq_label_data {
LINK_ELEMENT link;
int label_no;
int position;
int sc_state;
int sp;
int refcnt;
unsigned int set: 1;
unsigned int rescued: 2;
} 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(iseq)->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 printf ruby_debug_printf
#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) ((iseq)->body->location.path)
#define iseq_absolute_path(iseq) ((iseq)->body->location.absolute_path)
#define NEW_ISEQ(node, name, type, line_no) \
new_child_iseq(iseq, (node), rb_fstring(name), 0, (type), (line_no))
#define NEW_CHILD_ISEQ(node, name, type, line_no) \
new_child_iseq(iseq, (node), rb_fstring(name), iseq, (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))
/* insert an instruction before prev */
#define INSERT_BEFORE_INSN(prev, line, insn) \
INSERT_ELEM_PREV(&(prev)->link, (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)))
/* insert an instruction with some operands (1, 2, 3, 5) before prev */
#define INSERT_BEFORE_INSN1(prev, line, insn, op1) \
INSERT_ELEM_PREV(&(prev)->link, (LINK_ELEMENT *) \
new_insn_body(iseq, (line), BIN(insn), 1, (VALUE)(op1)))
#define LABEL_REF(label) ((label)->refcnt++)
/* add an instruction with label operand (alias of ADD_INSN1) */
#define ADD_INSNL(seq, line, insn, label) (ADD_INSN1(seq, line, insn, label), LABEL_REF(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), NULL, (VALUE)INT2FIX(0), NULL)
#define ADD_SEND_WITH_FLAG(seq, line, id, argc, flag) \
ADD_SEND_R((seq), (line), (id), (argc), NULL, (VALUE)(flag), NULL)
#define ADD_SEND_WITH_BLOCK(seq, line, id, argc, block) \
ADD_SEND_R((seq), (line), (id), (argc), (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), NULL, (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), (block), (VALUE)(flag), (keywords)))
#define ADD_TRACE(seq, line, event) \
do { \
if ((event) == RUBY_EVENT_LINE && ISEQ_COVERAGE(iseq) && \
(line) != ISEQ_COMPILE_DATA(iseq)->last_coverable_line) { \
RARRAY_ASET(ISEQ_COVERAGE(iseq), (line) - 1, INT2FIX(0)); \
ISEQ_COMPILE_DATA(iseq)->last_coverable_line = (line); \
ADD_INSN1((seq), (line), trace, INT2FIX(RUBY_EVENT_COVERAGE)); \
} \
if (ISEQ_COMPILE_DATA(iseq)->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) do { \
VALUE _e = rb_ary_new3(5, (type), \
(VALUE)(ls) | 1, (VALUE)(le) | 1, \
(VALUE)(iseqv), (VALUE)(lc) | 1); \
if (ls) LABEL_REF(ls); \
if (le) LABEL_REF(le); \
if (lc) LABEL_REF(lc); \
rb_ary_push(ISEQ_COMPILE_DATA(iseq)->catch_table_ary, freeze_hide_obj(_e)); \
} while (0)
/* 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)
#define IS_INSN(link) ((link)->type == ISEQ_ELEMENT_INSN)
#define IS_LABEL(link) ((link)->type == ISEQ_ELEMENT_LABEL)
#define IS_ADJUST(link) ((link)->type == ISEQ_ELEMENT_ADJUST)
#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == BIN(insn))
/* error */
typedef void (*compile_error_func)(rb_iseq_t *, int, const char *, ...);
static void
append_compile_error(rb_iseq_t *iseq, int line, const char *fmt, ...)
{
VALUE err_info = ISEQ_COMPILE_DATA(iseq)->err_info;
VALUE file = iseq->body->location.path;
VALUE err = err_info;
va_list args;
va_start(args, fmt);
err = rb_syntax_error_append(err, file, line, -1, NULL, fmt, args);
va_end(args);
if (NIL_P(err_info)) {
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->err_info, err);
rb_set_errinfo(err);
}
}
static void
compile_bug(rb_iseq_t *iseq, int line, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
rb_report_bug_valist(iseq->body->location.path, line, fmt, args);
va_end(args);
abort();
}
NOINLINE(static compile_error_func prepare_compile_error(rb_iseq_t *iseq));
static compile_error_func
prepare_compile_error(rb_iseq_t *iseq)
{
if (compile_debug) return &compile_bug;
return &append_compile_error;
}
#define COMPILE_ERROR prepare_compile_error(iseq)
#define ERROR_ARGS_AT(n) iseq, nd_line(n),
#define ERROR_ARGS ERROR_ARGS_AT(node)
#define EXPECT_NODE(prefix, node, ndtype) \
do { \
NODE *error_node = (node); \
enum node_type error_type = nd_type(error_node); \
if (error_type != (ndtype)) { \
compile_bug(ERROR_ARGS_AT(error_node) \
prefix ": " #ndtype " is expected, but %s", \
ruby_node_name(error_type)); \
} \
} while (0)
#define EXPECT_NODE_NONULL(prefix, parent, ndtype) \
do { \
compile_bug(ERROR_ARGS_AT(parent) \
prefix ": must be " #ndtype ", but 0"); \
} while (0)
#define UNKNOWN_NODE(prefix, node) \
do { \
NODE *error_node = (node); \
compile_bug(ERROR_ARGS_AT(error_node) prefix ": unknown node (%s)", \
ruby_node_name(nd_type(error_node))); \
} while (0)
#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__)
static inline VALUE
freeze_hide_obj(VALUE obj)
{
OBJ_FREEZE(obj);
RBASIC_CLEAR_CLASS(obj);
return obj;
}
#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(iseq)->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) APPEND_ELEM(iseq, (anchor), (before), (elem))
#endif
static int
iseq_add_mark_object(const 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->body->location.path)
static int
iseq_add_mark_object_compile_time(const rb_iseq_t *iseq, VALUE v)
{
if (!SPECIAL_CONST_P(v)) {
rb_ary_push(ISEQ_COMPILE_DATA(iseq)->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 {
COMPILE_ERROR(iseq, lobj->position,
"%"PRIsVALUE": undefined label",
rb_id2str((ID)name));
} 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);
st_free_table(labels_table);
if (!NIL_P(ISEQ_COMPILE_DATA(iseq)->err_info)) {
rb_exc_raise(ISEQ_COMPILE_DATA(iseq)->err_info);
}
}
VALUE
rb_iseq_compile_node(rb_iseq_t *iseq, NODE *node)
{
DECL_ANCHOR(ret);
INIT_ANCHOR(ret);
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->body->type) {
case ISEQ_TYPE_BLOCK:
{
LABEL *start = ISEQ_COMPILE_DATA(iseq)->start_label = NEW_LABEL(0);
LABEL *end = ISEQ_COMPILE_DATA(iseq)->end_label = NEW_LABEL(0);
start->rescued = LABEL_RESCUE_BEG;
end->rescued = LABEL_RESCUE_END;
ADD_TRACE(ret, FIX2INT(iseq->body->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->body->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->body->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 (RB_TYPE_P((VALUE)node, T_IMEMO)) {
const struct vm_ifunc *ifunc = (struct vm_ifunc *)node;
/* user callback */
(*ifunc->func)(iseq, ret, ifunc->data);
}
else {
switch (iseq->body->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:
COMPILE_ERROR(ERROR_ARGS "compile/should not be reached: %s:%d",
__FILE__, __LINE__);
return COMPILE_NG;
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_exception_local_table(iseq);
COMPILE(ret, "defined guard", node);
break;
default:
compile_bug(ERROR_ARGS "unknown scope");
}
}
if (iseq->body->type == ISEQ_TYPE_RESCUE || iseq->body->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(iseq)->last_line, leave);
}
#if SUPPORT_JOKE
if (ISEQ_COMPILE_DATA(iseq)->labels_table) {
st_table *labels_table = ISEQ_COMPILE_DATA(iseq)->labels_table;
ISEQ_COMPILE_DATA(iseq)->labels_table = 0;
validate_labels(iseq, 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;
VALUE *encoded = (VALUE *)iseq->body->iseq_encoded;
for (i = 0; i < iseq->body->iseq_size; /* */ ) {
int insn = (int)iseq->body->iseq_encoded[i];
int len = insn_len(insn);
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(const rb_iseq_t *iseq) /* cold path */
{
VALUE *original_code;
if (ISEQ_ORIGINAL_ISEQ(iseq)) return ISEQ_ORIGINAL_ISEQ(iseq);
original_code = ISEQ_ORIGINAL_ISEQ_ALLOC(iseq, iseq->body->iseq_size);
MEMCPY(original_code, iseq->body->iseq_encoded, VALUE, iseq->body->iseq_size);
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
{
unsigned int i;
for (i = 0; i < iseq->body->iseq_size; /* */ ) {
const void *addr = (const void *)original_code[i];
const int insn = rb_vm_insn_addr2insn(addr);
original_code[i] = insn;
i += insn_len(insn);
}
}
#endif
return original_code;
}
/*********************************************/
/* 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 access 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(iseq)->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(iseq)->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;
}
}
/*
* elem1, elemX => elemX, elem2, elem1
*/
static void
INSERT_ELEM_PREV(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2)
{
elem2->prev = elem1->prev;
elem2->next = elem1;
elem1->prev = elem2;
if (elem2->prev) {
elem2->prev->next = elem2;
}
}
#if 0
/*
* 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;
}
}
#endif
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 *
LAST_ELEMENT(LINK_ANCHOR *anchor)
{
return anchor->last;
}
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(iseq)->label_no++;
labelobj->sc_state = 0;
labelobj->sp = -1;
labelobj->refcnt = 0;
labelobj->set = 0;
labelobj->rescued = LABEL_RESCUE_NONE;
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;
if (label) LABEL_REF(label);
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 struct rb_call_info *
new_callinfo(rb_iseq_t *iseq, ID mid, int argc, unsigned int flag, struct rb_call_info_kw_arg *kw_arg, int has_blockiseq)
{
size_t size = kw_arg != NULL ? sizeof(struct rb_call_info_with_kwarg) : sizeof(struct rb_call_info);
struct rb_call_info *ci = (struct rb_call_info *)compile_data_alloc(iseq, size);
struct rb_call_info_with_kwarg *ci_kw = (struct rb_call_info_with_kwarg *)ci;
ci->mid = mid;
ci->flag = flag;
ci->orig_argc = argc;
if (kw_arg) {
ci->flag |= VM_CALL_KWARG;
ci_kw->kw_arg = kw_arg;
ci->orig_argc += kw_arg->keyword_len;
iseq->body->ci_kw_size++;
}
else {
iseq->body->ci_size++;
}
if (!(ci->flag & (VM_CALL_ARGS_SPLAT | VM_CALL_ARGS_BLOCKARG)) &&
kw_arg == NULL && !has_blockiseq) {
ci->flag |= VM_CALL_ARGS_SIMPLE;
}
return ci;
}
static INSN *
new_insn_send(rb_iseq_t *iseq, int line_no, ID id, VALUE argc, const rb_iseq_t *blockiseq, VALUE flag, struct rb_call_info_kw_arg *keywords)
{
VALUE *operands = (VALUE *)compile_data_alloc(iseq, sizeof(VALUE) * 3);
operands[0] = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), FIX2INT(flag), keywords, blockiseq != NULL);
operands[1] = Qfalse; /* cache */
operands[2] = (VALUE)blockiseq;
return new_insn_core(iseq, line_no, BIN(send), 3, operands);
}
static rb_iseq_t *
new_child_iseq(rb_iseq_t *iseq, NODE *node,
VALUE name, const rb_iseq_t *parent, enum iseq_type type, int line_no)
{
rb_iseq_t *ret_iseq;
debugs("[new_child_iseq]> ---------------------------------------\n");
ret_iseq = rb_iseq_new_with_opt(node, name,
iseq_path(iseq), iseq_absolute_path(iseq),
INT2FIX(line_no), parent, type, ISEQ_COMPILE_DATA(iseq)->option);
debugs("[new_child_iseq]< ---------------------------------------\n");
iseq_add_mark_object(iseq, (VALUE)ret_iseq);
return ret_iseq;
}
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(iseq)->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(iseq)->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");
if (!iseq_set_sequence(iseq, anchor)) return COMPILE_NG;
if (compile_debug > 5)
dump_disasm_list(FIRST_ELEMENT(anchor));
debugs("[compile step 4.2 (iseq_set_exception_table)]\n");
if (!iseq_set_exception_table(iseq)) return COMPILE_NG;
debugs("[compile step 4.3 (set_optargs_table)] \n");
if (!iseq_set_optargs_table(iseq)) return COMPILE_NG;
debugs("[compile step 5 (iseq_translate_threaded_code)] \n");
if (!rb_iseq_translate_threaded_code(iseq)) return COMPILE_NG;
if (compile_debug > 1) {
VALUE str = rb_iseq_disasm(iseq);
printf("%s\n", StringValueCStr(str));
}
debugs("[compile step: finish]\n");
return COMPILE_OK;
}
static int
iseq_set_exception_local_table(rb_iseq_t *iseq)
{
/* TODO: every id table is same -> share it.
* Current problem is iseq_free().
*/
ID id_dollar_bang;
ID *ids = (ID *)ALLOC_N(ID, 1);
CONST_ID(id_dollar_bang, "#$!");
iseq->body->local_table_size = 1;
iseq->body->local_size = iseq->body->local_table_size + 1;
ids[0] = id_dollar_bang;
iseq->body->local_table = ids;
return COMPILE_OK;
}
static int
get_lvar_level(const rb_iseq_t *iseq)
{
int lev = 0;
while (iseq != iseq->body->local_iseq) {
lev++;
iseq = iseq->body->parent_iseq;
}
return lev;
}
static int
get_dyna_var_idx_at_raw(const rb_iseq_t *iseq, ID id)
{
unsigned int i;
for (i = 0; i < iseq->body->local_table_size; i++) {
if (iseq->body->local_table[i] == id) {
return (int)i;
}
}
return -1;
}
static int
get_local_var_idx(const rb_iseq_t *iseq, ID id)
{
int idx = get_dyna_var_idx_at_raw(iseq->body->local_iseq, id);
if (idx < 0) {
rb_bug("get_local_var_idx: %d", idx);
}
return idx;
}
static int
get_dyna_var_idx(const 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->body->parent_iseq;
lv++;
}
if (idx < 0) {
rb_bug("get_dyna_var_idx: -1");
}
*level = lv;
*ls = iseq->body->local_size;
return idx;
}
static void
iseq_calc_param_size(rb_iseq_t *iseq)
{
if (iseq->body->param.flags.has_opt ||
iseq->body->param.flags.has_post ||
iseq->body->param.flags.has_rest ||
iseq->body->param.flags.has_block ||
iseq->body->param.flags.has_kw ||
iseq->body->param.flags.has_kwrest) {
if (iseq->body->param.flags.has_block) {
iseq->body->param.size = iseq->body->param.block_start + 1;
}
else if (iseq->body->param.flags.has_kwrest) {
iseq->body->param.size = iseq->body->param.keyword->rest_start + 1;
}
else if (iseq->body->param.flags.has_kw) {
iseq->body->param.size = iseq->body->param.keyword->bits_start + 1;
}
else if (iseq->body->param.flags.has_post) {
iseq->body->param.size = iseq->body->param.post_start + iseq->body->param.post_num;
}
else if (iseq->body->param.flags.has_rest) {
iseq->body->param.size = iseq->body->param.rest_start + 1;
}
else if (iseq->body->param.flags.has_opt) {
iseq->body->param.size = iseq->body->param.lead_num + iseq->body->param.opt_num;
}
else {
rb_bug("unreachable");
}
}
else {
iseq->body->param.size = iseq->body->param.lead_num;
}
}
static void
iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *optargs, const struct rb_args_info *args)
{
NODE *node = args->kw_args;
struct rb_iseq_param_keyword *keyword;
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->body->param.flags.has_kw = TRUE;
iseq->body->param.keyword = keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
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;
}
keyword->num = ++di;
rb_ary_push(default_values, dv);
}
kw++;
node = node->nd_next;
}
keyword->num = kw;
if (args->kw_rest_arg->nd_cflag != 0) {
keyword->rest_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_cflag);
iseq->body->param.flags.has_kwrest = TRUE;
}
keyword->required_num = rkw;
keyword->table = &iseq->body->local_table[keyword->bits_start - keyword->num];
{
VALUE *dvs = 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;
dvs[i] = dv;
}
keyword->default_values = dvs;
}
}
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;
EXPECT_NODE("iseq_set_arguments", node_args, NODE_ARGS);
iseq->body->param.lead_num = (int)args->pre_args_num;
if (iseq->body->param.lead_num > 0) iseq->body->param.flags.has_lead = TRUE;
debugs(" - argc: %d\n", iseq->body->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->body->param.post_start = get_dyna_var_idx_at_raw(iseq, args->first_post_arg);
iseq->body->param.post_num = args->post_args_num;
iseq->body->param.flags.has_post = TRUE;
}
if (args->opt_args) {
NODE *node = args->opt_args;
LABEL *label;
VALUE labels = rb_ary_tmp_new(1);
VALUE *opt_table;
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);
opt_table = ALLOC_N(VALUE, i+1);
MEMCPY(opt_table, RARRAY_CONST_PTR(labels), VALUE, i+1);
for (j = 0; j < i+1; j++) {
opt_table[j] &= ~1;
}
rb_ary_clear(labels);
iseq->body->param.flags.has_opt = TRUE;
iseq->body->param.opt_num = i;
iseq->body->param.opt_table = opt_table;
}
if (args->kw_args) {
iseq_set_arguments_keywords(iseq, optargs, args);
}
else if (args->kw_rest_arg) {
struct rb_iseq_param_keyword *keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
keyword->rest_start = get_dyna_var_idx_at_raw(iseq, args->kw_rest_arg->nd_vid);
iseq->body->param.keyword = keyword;
iseq->body->param.flags.has_kwrest = TRUE;
}
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->body->param.rest_start = get_dyna_var_idx_at_raw(iseq, rest_id);
iseq->body->param.flags.has_rest = TRUE;
assert(iseq->body->param.rest_start != -1);
if (iseq->body->param.post_start == 0) { /* TODO: why that? */
iseq->body->param.post_start = iseq->body->param.rest_start + 1;
}
}
if (block_id) {
iseq->body->param.block_start = get_dyna_var_idx_at_raw(iseq, block_id);
iseq->body->param.flags.has_block = TRUE;
}
iseq_calc_param_size(iseq);
if (iseq->body->type == ISEQ_TYPE_BLOCK) {
if (iseq->body->param.flags.has_opt == FALSE &&
iseq->body->param.flags.has_post == FALSE &&
iseq->body->param.flags.has_rest == FALSE &&
iseq->body->param.flags.has_kw == FALSE &&
iseq->body->param.flags.has_kwrest == FALSE) {
if (iseq->body->param.lead_num == 1 && last_comma == 0) {
/* {|a|} */
iseq->body->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) {
ID *ids = (ID *)ALLOC_N(ID, size);
MEMCPY(ids, tbl, ID, size);
iseq->body->local_table = ids;
}
iseq->body->local_size = iseq->body->local_table_size = size;
iseq->body->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->body->local_size, iseq->body->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)
{
struct iseq_line_info_entry *line_info_table;
unsigned int last_line = 0;
LINK_ELEMENT *list;
VALUE *generated_iseq;
int insn_num, code_index, line_info_index, sp, stack_max = 0, line = 0;
/* fix label position */
list = FIRST_ELEMENT(anchor);
insn_num = code_index = 0;
while (list) {
switch (list->type) {
case ISEQ_ELEMENT_INSN:
{
INSN *iobj = (INSN *)list;
line = iobj->line_no;
code_index += insn_data_length(iobj);
insn_num++;
break;
}
case ISEQ_ELEMENT_LABEL:
{
LABEL *lobj = (LABEL *)list;
lobj->position = code_index;
lobj->set = TRUE;
break;
}
case ISEQ_ELEMENT_NONE:
{
/* ignore */
break;
}
case ISEQ_ELEMENT_ADJUST:
{
ADJUST *adjust = (ADJUST *)list;
if (adjust->line_no != -1) {
code_index += 2 /* insn + 1 operand */;
insn_num++;
}
break;
}
default:
dump_disasm_list(FIRST_ELEMENT(anchor));
dump_disasm_list(list);
COMPILE_ERROR(iseq, line, "error: set_sequence");
return COMPILE_NG;
}
list = list->next;
}
/* make instruction sequence */
generated_iseq = ALLOC_N(VALUE, code_index);
line_info_table = ALLOC_N(struct iseq_line_info_entry, insn_num);
iseq->body->is_entries = ZALLOC_N(union iseq_inline_storage_entry, iseq->body->is_size);
iseq->body->ci_entries = (struct rb_call_info *)ruby_xmalloc(sizeof(struct rb_call_info) * iseq->body->ci_size +
sizeof(struct rb_call_info_with_kwarg) * iseq->body->ci_kw_size);
iseq->body->cc_entries = ZALLOC_N(struct rb_call_cache, iseq->body->ci_size + iseq->body->ci_kw_size);
ISEQ_COMPILE_DATA(iseq)->ci_index = ISEQ_COMPILE_DATA(iseq)->ci_kw_index = 0;
list = FIRST_ELEMENT(anchor);
line_info_index = code_index = sp = 0;
while (list) {
switch (list->type) {
case ISEQ_ELEMENT_INSN:
{
int j, len, insn;
const char *types;
VALUE *operands;
INSN *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[code_index] = 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);
xfree(generated_iseq);
xfree(line_info_table);
COMPILE_ERROR(iseq, iobj->line_no,
"operand size miss! (%d for %d)",
iobj->operand_size, len - 1);
return COMPILE_NG;
}
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) */
LABEL *lobj = (LABEL *)operands[j];
if (!lobj->set) {
COMPILE_ERROR(iseq, iobj->line_no,
"unknown label");
return COMPILE_NG;
}
if (lobj->sp == -1) {
lobj->sp = sp;
}
generated_iseq[code_index + 1 + j] = lobj->position - (code_index + len);
break;
}
case TS_CDHASH:
{
VALUE map = operands[j];
struct cdhash_set_label_struct data;
data.hash = map;
data.pos = code_index;
data.len = len;
rb_hash_foreach(map, cdhash_set_label_i, (VALUE)&data);
rb_hash_rehash(map);
freeze_hide_obj(map);
generated_iseq[code_index + 1 + j] = map;
break;
}
case TS_LINDEX:
case TS_NUM: /* ulong */
generated_iseq[code_index + 1 + j] = FIX2INT(operands[j]);
break;
case TS_ISEQ: /* iseq */
{
VALUE v = operands[j];
generated_iseq[code_index + 1 + j] = v;
break;
}
case TS_VALUE: /* VALUE */
{
VALUE v = operands[j];
generated_iseq[code_index + 1 + j] = v;
/* to mark ruby object */
iseq_add_mark_object(iseq, v);
break;
}
case TS_IC: /* inline cache */
{
unsigned int ic_index = FIX2UINT(operands[j]);
IC ic = (IC)&iseq->body->is_entries[ic_index];
if (UNLIKELY(ic_index >= iseq->body->is_size)) {
rb_bug("iseq_set_sequence: ic_index overflow: index: %d, size: %d", ic_index, iseq->body->is_size);
}
generated_iseq[code_index + 1 + j] = (VALUE)ic;
break;
}
case TS_CALLINFO: /* call info */
{
struct rb_call_info *base_ci = (struct rb_call_info *)operands[j];
struct rb_call_info *ci;
if (base_ci->flag & VM_CALL_KWARG) {
struct rb_call_info_with_kwarg *ci_kw_entries = (struct rb_call_info_with_kwarg *)&iseq->body->ci_entries[iseq->body->ci_size];
struct rb_call_info_with_kwarg *ci_kw = &ci_kw_entries[ISEQ_COMPILE_DATA(iseq)->ci_kw_index++];
*ci_kw = *((struct rb_call_info_with_kwarg *)base_ci);
ci = (struct rb_call_info *)ci_kw;
assert(ISEQ_COMPILE_DATA(iseq)->ci_kw_index <= iseq->body->ci_kw_size);
}
else {
ci = &iseq->body->ci_entries[ISEQ_COMPILE_DATA(iseq)->ci_index++];
*ci = *base_ci;
assert(ISEQ_COMPILE_DATA(iseq)->ci_index <= iseq->body->ci_size);
}
generated_iseq[code_index + 1 + j] = (VALUE)ci;
break;
}
case TS_CALLCACHE:
{
struct rb_call_cache *cc = &iseq->body->cc_entries[ISEQ_COMPILE_DATA(iseq)->ci_index + ISEQ_COMPILE_DATA(iseq)->ci_kw_index - 1];
generated_iseq[code_index + 1 + j] = (VALUE)cc;
break;
}
case TS_ID: /* ID */
generated_iseq[code_index + 1 + j] = SYM2ID(operands[j]);
break;
case TS_GENTRY:
{
struct rb_global_entry *entry =
(struct rb_global_entry *)(operands[j] & (~1));
generated_iseq[code_index + 1 + j] = (VALUE)entry;
}
break;
case TS_FUNCPTR:
generated_iseq[code_index + 1 + j] = operands[j];
break;
default:
xfree(generated_iseq);
xfree(line_info_table);
COMPILE_ERROR(iseq, iobj->line_no,
"unknown operand type: %c", type);
return COMPILE_NG;
}
}
if (last_line != iobj->line_no) {
line_info_table[line_info_index].line_no = last_line = iobj->line_no;
line_info_table[line_info_index].position = code_index;
line_info_index++;
}
code_index += len;
break;
}
case ISEQ_ELEMENT_LABEL:
{
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[line_info_index].line_no = last_line = adjust->line_no;
line_info_table[line_info_index].position = code_index;
line_info_index++;
}
generated_iseq[code_index++] = BIN(adjuststack);
generated_iseq[code_index++] = orig_sp - sp;
}
else if (orig_sp - sp == 0) {
/* jump to next insn */
if (last_line != (unsigned int)adjust->line_no) {
line_info_table[line_info_index].line_no = last_line = adjust->line_no;
line_info_table[line_info_index].position = code_index;
line_info_index++;
}
generated_iseq[code_index++] = BIN(nop);
generated_iseq[code_index++] = BIN(nop);
}
else {
compile_bug(iseq, adjust->line_no,
"iseq_set_sequence: adjust bug %d < %d",
orig_sp, sp);
}
}
break;
}
default:
/* ignore */
break;
}
list = list->next;
}
iseq->body->iseq_encoded = (void *)generated_iseq;
iseq->body->iseq_size = code_index;
iseq->body->stack_max = stack_max;
REALLOC_N(line_info_table, struct iseq_line_info_entry, line_info_index);
iseq->body->line_info_table = line_info_table;
iseq->body->line_info_size = line_info_index;
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;
unsigned int tlen, i;
struct iseq_catch_table_entry *entry;
tlen = (int)RARRAY_LEN(ISEQ_COMPILE_DATA(iseq)->catch_table_ary);
tptr = RARRAY_CONST_PTR(ISEQ_COMPILE_DATA(iseq)->catch_table_ary);
if (tlen > 0) {
struct iseq_catch_table *table = xmalloc(iseq_catch_table_bytes(tlen));
table->size = tlen;
for (i = 0; i < table->size; i++) {
ptr = RARRAY_CONST_PTR(tptr[i]);
entry = &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 = (rb_iseq_t *)ptr[3];
/* register iseq as mark object */
if (entry->iseq != 0) {
iseq_add_mark_object(iseq, (VALUE)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;
}
}
iseq->body->catch_table = table;
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->catch_table_ary, 0); /* free */
}
else {
iseq->body->catch_table = NULL;
}
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;
VALUE *opt_table = (VALUE *)iseq->body->param.opt_table;
if (iseq->body->param.flags.has_opt) {
for (i = 0; i < iseq->body->param.opt_num + 1; i++) {
opt_table[i] = label_get_position((LABEL *)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 (IS_INSN(list) || IS_ADJUST(list)) {
break;
}
list = list->next;
}
return list;
}
static LINK_ELEMENT *
get_next_insn(INSN *iobj)
{
LINK_ELEMENT *list = iobj->link.next;
while (list) {
if (IS_INSN(list) || IS_ADJUST(list)) {
return list;
}
list = list->next;
}
return 0;
}
static LINK_ELEMENT *
get_prev_insn(INSN *iobj)
{
LINK_ELEMENT *list = iobj->link.prev;
while (list) {
if (IS_INSN(list) || IS_ADJUST(list)) {
return list;
}
list = list->prev;
}
return 0;
}
static void
unref_destination(INSN *iobj, int pos)
{
LABEL *lobj = (LABEL *)OPERAND_AT(iobj, pos);
--lobj->refcnt;
if (!lobj->refcnt) REMOVE_ELEM(&lobj->link);
}
static void
replace_destination(INSN *dobj, INSN *nobj)
{
VALUE n = OPERAND_AT(nobj, 0);
LABEL *dl = (LABEL *)OPERAND_AT(dobj, 0);
LABEL *nl = (LABEL *)n;
--dl->refcnt;
++nl->refcnt;
OPERAND_AT(dobj, 0) = n;
if (!dl->refcnt) REMOVE_ELEM(&dl->link);
}
static int
remove_unreachable_chunk(rb_iseq_t *iseq, LINK_ELEMENT *i)
{
int removed = 0;
while (i) {
if (IS_INSN(i)) {
struct rb_iseq_constant_body *body = iseq->body;
VALUE insn = INSN_OF(i);
int pos, len = insn_len(insn);
for (pos = 0; pos < len; ++pos) {
switch (insn_op_types(insn)[pos]) {
case TS_OFFSET:
unref_destination((INSN *)i, pos);
break;
case TS_CALLINFO:
if (((struct rb_call_info *)OPERAND_AT(i, pos))->flag & VM_CALL_KWARG)
--(body->ci_kw_size);
else
--(body->ci_size);
break;
}
}
}
else if (IS_LABEL(i)) {
if (((LABEL *)i)->refcnt > 0) break;
}
else break;
REMOVE_ELEM(i);
removed = 1;
i = i->next;
}
return removed;
}
static int
iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
{
INSN *iobj = (INSN *)list;
again:
if (IS_INSN_ID(iobj, 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:
*/
unref_destination(iobj, 0);
REMOVE_ELEM(&iobj->link);
}
else if (iobj != diobj && IS_INSN_ID(diobj, jump) &&
OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0)) {
replace_destination(iobj, diobj);
remove_unreachable_chunk(iseq, iobj->link.next);
goto again;
}
else if (IS_INSN_ID(diobj, leave)) {
/*
* jump LABEL
* ...
* LABEL:
* leave
* =>
* leave
* ...
* LABEL:
* leave
*/
INSN *popiobj = new_insn_core(iseq, iobj->line_no,
BIN(pop), 0, 0);
/* replace */
unref_destination(iobj, 0);
iobj->insn_id = BIN(leave);
iobj->operand_size = 0;
INSERT_ELEM_NEXT(&iobj->link, &popiobj->link);
goto again;
}
/*
* 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 &&
(IS_INSN_ID(piobj, branchif) ||
IS_INSN_ID(piobj, branchunless))) {
if (niobj == (INSN *)get_destination_insn(piobj)) {
piobj->insn_id = (IS_INSN_ID(piobj, branchif))
? BIN(branchunless) : BIN(branchif);
replace_destination(piobj, iobj);
REMOVE_ELEM(&iobj->link);
}
}
else if (remove_unreachable_chunk(iseq, iobj->link.next)) {
goto again;
}
}
if (IS_INSN_ID(iobj, leave)) {
remove_unreachable_chunk(iseq, iobj->link.next);
}
if (IS_INSN_ID(iobj, branchif) ||
IS_INSN_ID(iobj, branchnil) ||
IS_INSN_ID(iobj, branchunless)) {
/*
* if L1
* ...
* L1:
* jump L2
* =>
* if L2
*/
INSN *nobj = (INSN *)get_destination_insn(iobj);
INSN *pobj = (INSN *)iobj->link.prev;
int prev_dup = 0;
if (pobj) {
if (!IS_INSN(&pobj->link))
pobj = 0;
else if (IS_INSN_ID(pobj, dup))
prev_dup = 1;
}
for (;;) {
if (IS_INSN_ID(nobj, jump)) {
replace_destination(iobj, nobj);
}
else if (prev_dup && IS_INSN_ID(nobj, dup) &&
!!(nobj = (INSN *)nobj->link.next) &&
/* basic blocks, with no labels in the middle */
nobj->insn_id == iobj->insn_id) {
/*
* dup
* if L1
* ...
* L1:
* dup
* if L2
* =>
* dup
* if L2
* ...
* L1:
* dup
* if L2
*/
replace_destination(iobj, nobj);
}
else if (pobj) {
/*
* putnil
* if L1
* =>
* # nothing
*
* putobject true
* if L1
* =>
* jump L1
*
* putstring ".."
* if L1
* =>
* jump L1
*
* putstring ".."
* dup
* if L1
* =>
* putstring ".."
* jump L1
*
*/
int cond;
if (prev_dup && IS_INSN(pobj->link.prev)) {
pobj = (INSN *)pobj->link.prev;
}
if (IS_INSN_ID(pobj, putobject)) {
cond = (IS_INSN_ID(iobj, branchif) ?
OPERAND_AT(pobj, 0) != Qfalse :
IS_INSN_ID(iobj, branchunless) ?
OPERAND_AT(pobj, 0) == Qfalse :
FALSE);
}
else if (IS_INSN_ID(pobj, putstring)) {
cond = IS_INSN_ID(iobj, branchif);
}
else if (IS_INSN_ID(pobj, putnil)) {
cond = !IS_INSN_ID(iobj, branchif);
}
else break;
REMOVE_ELEM(iobj->link.prev);
if (cond) {
iobj->insn_id = BIN(jump);
goto again;
}
else {
unref_destination(iobj, 0);
REMOVE_ELEM(&iobj->link);
}
break;
}
else break;
nobj = (INSN *)get_destination_insn(nobj);
}
}
if (IS_INSN_ID(iobj, pop)) {
/*
* putself / putnil / putobject obj / putstring "..."
* pop
* =>
* # do nothing
*/
LINK_ELEMENT *prev = iobj->link.prev;
if (IS_INSN(prev)) {
enum ruby_vminsn_type previ = ((INSN *)prev)->insn_id;
if (previ == BIN(putobject) || previ == BIN(putnil) ||
previ == BIN(putself) || previ == BIN(putstring)) {
/* just push operand or static value and pop soon, no
* side effects */
REMOVE_ELEM(prev);
REMOVE_ELEM(&iobj->link);
}
}
}
if (do_tailcallopt &&
(IS_INSN_ID(iobj, send) ||
IS_INSN_ID(iobj, opt_aref_with) ||
IS_INSN_ID(iobj, opt_aset_with) ||
IS_INSN_ID(iobj, invokesuper))) {
/*
* send ...
* leave
* =>
* send ..., ... | VM_CALL_TAILCALL, ...
* leave # unreachable
*/
INSN *piobj = NULL;
if (iobj->link.next) {
LINK_ELEMENT *next = iobj->link.next;
do {
if (!IS_INSN(next)) {
next = next->next;
continue;
}
switch (INSN_OF(next)) {
case BIN(nop):
/*case BIN(trace):*/
next = next->next;
break;
case BIN(leave):
piobj = iobj;
default:
next = NULL;
break;
}
} while (next);
}
if (piobj) {
struct rb_call_info *ci = (struct rb_call_info *)piobj->operands[0];
if (IS_INSN_ID(piobj, send) || IS_INSN_ID(piobj, invokesuper)) {
if (piobj->operands[2] == 0) { /* no blockiseq */
ci->flag |= VM_CALL_TAILCALL;
}
}
else {
ci->flag |= VM_CALL_TAILCALL;
}
}
}
#define IS_TRACE_LINE(insn) \
(IS_INSN_ID(insn, trace) && \
OPERAND_AT(insn, 0) == INT2FIX(RUBY_EVENT_LINE))
if (IS_TRACE_LINE(iobj) && iobj->link.prev && IS_INSN(iobj->link.prev)) {
INSN *piobj = (INSN *)iobj->link.prev;
if (IS_TRACE_LINE(piobj)) {
REMOVE_ELEM(iobj->link.prev);
}
}
return COMPILE_OK;
}
static int
insn_set_specialized_instruction(rb_iseq_t *iseq, INSN *iobj, int insn_id)
{
iobj->insn_id = insn_id;
iobj->operand_size = insn_len(insn_id) - 1;
if (insn_id == BIN(opt_neq)) {
VALUE *old_operands = iobj->operands;
iobj->operand_size = 4;
iobj->operands = (VALUE *)compile_data_alloc(iseq, iobj->operand_size * sizeof(VALUE));
iobj->operands[0] = old_operands[0];
iobj->operands[1] = Qfalse; /* CALL_CACHE */
iobj->operands[2] = (VALUE)new_callinfo(iseq, idEq, 1, 0, NULL, FALSE);
iobj->operands[3] = Qfalse; /* CALL_CACHE */
}
return COMPILE_OK;
}
static int
iseq_specialized_instruction(rb_iseq_t *iseq, INSN *iobj)
{
if (IS_INSN_ID(iobj, newarray) && iobj->link.next &&
IS_INSN(iobj->link.next)) {
/*
* [a, b, ...].max/min -> a, b, c, opt_newarray_max/min
*/
INSN *niobj = (INSN *)iobj->link.next;
if (IS_INSN_ID(niobj, send)) {
struct rb_call_info *ci = (struct rb_call_info *)OPERAND_AT(niobj, 0);
if ((ci->flag & VM_CALL_ARGS_SIMPLE) && ci->orig_argc == 0) {
switch (ci->mid) {
case idMax:
iobj->insn_id = BIN(opt_newarray_max);
REMOVE_ELEM(&niobj->link);
return COMPILE_OK;
case idMin:
iobj->insn_id = BIN(opt_newarray_min);
REMOVE_ELEM(&niobj->link);
return COMPILE_OK;
}
}
}
}
if (IS_INSN_ID(iobj, send)) {
struct rb_call_info *ci = (struct rb_call_info *)OPERAND_AT(iobj, 0);
const rb_iseq_t *blockiseq = (rb_iseq_t *)OPERAND_AT(iobj, 2);
#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 && blockiseq == NULL) {
iobj->insn_id = BIN(opt_send_without_block);
iobj->operand_size = insn_len(iobj->insn_id) - 1;
}
}
#undef SP_INSN
return COMPILE_OK;
}
static inline int
tailcallable_p(rb_iseq_t *iseq)
{
switch (iseq->body->type) {
case ISEQ_TYPE_RESCUE:
case ISEQ_TYPE_ENSURE:
/* rescue block can't tail call because of errinfo */
return FALSE;
default:
return TRUE;
}
}
static int
iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *anchor)
{
LINK_ELEMENT *list;
const int do_peepholeopt = ISEQ_COMPILE_DATA(iseq)->option->peephole_optimization;
const int do_tailcallopt = tailcallable_p(iseq) &&
ISEQ_COMPILE_DATA(iseq)->option->tailcall_optimization;
const int do_si = ISEQ_COMPILE_DATA(iseq)->option->specialized_instruction;
const int do_ou = ISEQ_COMPILE_DATA(iseq)->option->operands_unification;
int rescue_level = 0;
int tailcallopt = do_tailcallopt;
list = FIRST_ELEMENT(anchor);
while (list) {
if (IS_INSN(list)) {
if (do_peepholeopt) {
iseq_peephole_optimize(iseq, list, tailcallopt);
}
if (do_si) {
iseq_specialized_instruction(iseq, (INSN *)list);
}
if (do_ou) {
insn_operands_unification((INSN *)list);
}
}
if (IS_LABEL(list)) {
switch (((LABEL *)list)->rescued) {
case LABEL_RESCUE_BEG:
rescue_level++;
tailcallopt = FALSE;
break;
case LABEL_RESCUE_END:
if (!--rescue_level) tailcallopt = do_tailcallopt;
break;
}
}
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 (IS_INSN(list)) {
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 (!IS_INSN(li) ||
((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);
COMPILE_ERROR(iseq, iobj->line_no,
"insn_set_sc_state error\n");
return COMPILE_NG;
}
}
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:
COMPILE_ERROR(iseq, iobj->line_no,
"unreachable");
return COMPILE_NG;
}
/* 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;
LINK_ELEMENT *first_lit = 0;
int cnt = 0;
debugp_param("nd_lit", lit);
if (!NIL_P(lit)) {
cnt++;
if (!RB_TYPE_P(lit, T_STRING)) {
compile_bug(ERROR_ARGS "dstr: must be string: %s",
rb_builtin_type_name(TYPE(lit)));
}
lit = node->nd_lit = rb_fstring(lit);
ADD_INSN1(ret, nd_line(node), putobject, lit);
if (RSTRING_LEN(lit) == 0) first_lit = LAST_ELEMENT(ret);
}
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);
lit = Qnil;
}
else {
COMPILE(ret, "each string", node);
}
cnt++;
list = list->nd_next;
}
if (NIL_P(lit) && first_lit) {
REMOVE_ELEM(first_lit);
--cnt;
}
*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:
case NODE_DSTR:
case NODE_XSTR:
case NODE_DXSTR:
case NODE_DREGX:
case NODE_DREGX_ONCE:
case NODE_DSYM:
case NODE_ARRAY:
case NODE_ZARRAY:
case NODE_HASH:
case NODE_LAMBDA:
case NODE_DEFN:
case NODE_DEFS:
/* 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, struct rb_call_info_kw_arg ** 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;
struct rb_call_info_kw_arg *kw_arg = (struct rb_call_info_kw_arg *)ruby_xmalloc(sizeof(struct rb_call_info_kw_arg) + 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, struct rb_call_info_kw_arg **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) {
EXPECT_NODE("compile_array", node, NODE_ARRAY);
}
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_NIL:
return Qnil;
case NODE_TRUE:
return Qtrue;
case NODE_FALSE:
return Qfalse;
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(ruby_sourcefile, 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;
struct rb_call_info *ci;
VALUE dupidx;
int line = nd_line(node);
COMPILE_POPED(ret, "masgn lhs (NODE_ATTRASGN)", node);
iobj = (INSN *)get_prev_insn((INSN *)LAST_ELEMENT(ret)); /* send insn */
ci = (struct rb_call_info *)iobj->operands[0];
ci->orig_argc += 1;
dupidx = INT2FIX(ci->orig_argc);
INSERT_BEFORE_INSN1(iobj, line, topn, dupidx);
if (ci->flag & VM_CALL_ARGS_SPLAT) {
--ci->orig_argc;
INSERT_BEFORE_INSN1(iobj, line, newarray, INT2FIX(1));
INSERT_BEFORE_INSN(iobj, line, concatarray);
}
ADD_INSN(ret, line, pop); /* result */
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)