Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
11350 lines (10100 sloc) 325 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 "ruby/encoding.h"
#include "ruby/re.h"
#include "ruby/util.h"
#include "internal.h"
#include "encindex.h"
#include <math.h>
#include "vm_core.h"
#include "vm_debug.h"
#include "iseq.h"
#include "insns.inc"
#include "insns_info.inc"
#include "id_table.h"
#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_ANCHOR,
ISEQ_ELEMENT_LABEL,
ISEQ_ELEMENT_INSN,
ISEQ_ELEMENT_ADJUST,
ISEQ_ELEMENT_TRACE,
} 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;
unsigned int unremovable: 1;
} LABEL;
typedef struct iseq_insn_data {
LINK_ELEMENT link;
enum ruby_vminsn_type insn_id;
int operand_size;
int sc_state;
VALUE *operands;
struct {
int line_no;
rb_event_flag_t events;
} insn_info;
} INSN;
typedef struct iseq_adjust_data {
LINK_ELEMENT link;
LABEL *label;
int line_no;
} ADJUST;
typedef struct iseq_trace_data {
LINK_ELEMENT link;
rb_event_flag_t event;
long data;
} TRACE;
struct ensure_range {
LABEL *begin;
LABEL *end;
struct ensure_range *next;
};
struct iseq_compile_data_ensure_node_stack {
const NODE *ensure_node;
struct iseq_compile_data_ensure_node_stack *prev;
struct ensure_range *erange;
};
const ID rb_iseq_shared_exc_local_tbl[] = {idERROR_INFO};
/**
* 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, "", (const NODE *)(node)), gl_node_level)), \
gl_node_level++)
#define debug_node_end() gl_node_level --
#else
#define debugi(header, id) ((void)0)
#define debugp(header, value) ((void)0)
#define debugp_verbose(header, value) ((void)0)
#define debugp_verbose_node(header, value) ((void)0)
#define debugp_param(header, value) ((void)0)
#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
#define LVAR_ERRINFO (1)
/* create new label */
#define NEW_LABEL(l) new_label_body(iseq, (l))
#define LABEL_FORMAT "<L%03d>"
#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 next */
#define INSERT_BEFORE_INSN(next, line, insn) \
ELEM_INSERT_PREV(&(next)->link, (LINK_ELEMENT *) new_insn_body(iseq, (line), BIN(insn), 0))
/* insert an instruction after prev */
#define INSERT_AFTER_INSN(prev, line, insn) \
ELEM_INSERT_NEXT(&(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 next */
#define INSERT_BEFORE_INSN1(next, line, insn, op1) \
ELEM_INSERT_PREV(&(next)->link, (LINK_ELEMENT *) \
new_insn_body(iseq, (line), BIN(insn), 1, (VALUE)(op1)))
/* insert an instruction with some operands (1, 2, 3, 5) after prev */
#define INSERT_AFTER_INSN1(prev, line, insn, op1) \
ELEM_INSERT_NEXT(&(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, event) \
ADD_ELEM((seq), (LINK_ELEMENT *)new_trace_body(iseq, (event), 0))
#define ADD_TRACE_WITH_DATA(seq, event, data) \
ADD_ELEM((seq), (LINK_ELEMENT *)new_trace_body(iseq, (event), (data)))
#define DECL_BRANCH_BASE(branches, first_line, first_column, last_line, last_column, type) \
do { \
if (ISEQ_COVERAGE(iseq) && \
ISEQ_BRANCH_COVERAGE(iseq) && \
(first_line) > 0) { \
VALUE structure = RARRAY_AREF(ISEQ_BRANCH_COVERAGE(iseq), 0); \
branches = rb_ary_tmp_new(0); \
rb_ary_push(structure, branches); \
rb_ary_push(branches, ID2SYM(rb_intern(type))); \
rb_ary_push(branches, INT2FIX(first_line)); \
rb_ary_push(branches, INT2FIX(first_column)); \
rb_ary_push(branches, INT2FIX(last_line)); \
rb_ary_push(branches, INT2FIX(last_column)); \
} \
} while (0)
#define ADD_TRACE_BRANCH_COVERAGE(seq, first_line, first_column, last_line, last_column, type, branches) \
do { \
if (ISEQ_COVERAGE(iseq) && \
ISEQ_BRANCH_COVERAGE(iseq) && \
(first_line) > 0) { \
VALUE counters = RARRAY_AREF(ISEQ_BRANCH_COVERAGE(iseq), 1); \
long counter_idx = RARRAY_LEN(counters); \
rb_ary_push(counters, INT2FIX(0)); \
rb_ary_push(branches, ID2SYM(rb_intern(type))); \
rb_ary_push(branches, INT2FIX(first_line)); \
rb_ary_push(branches, INT2FIX(first_column)); \
rb_ary_push(branches, INT2FIX(last_line)); \
rb_ary_push(branches, INT2FIX(last_column)); \
rb_ary_push(branches, INT2FIX(counter_idx)); \
ADD_TRACE_WITH_DATA(seq, RUBY_EVENT_COVERAGE_BRANCH, counter_idx); \
ADD_INSN(seq, last_line, nop); \
} \
} while (0)
static void iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level);
static void iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level);
#define ADD_GETLOCAL(seq, line, idx, level) iseq_add_getlocal(iseq, (seq), (line), (idx), (level))
#define ADD_SETLOCAL(seq, line, idx, level) iseq_add_setlocal(iseq, (seq), (line), (idx), (level))
/* 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 LABEL_UNREMOVABLE(label) \
((label) ? (LABEL_REF(label), (label)->unremovable=1) : 0)
#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); \
LABEL_UNREMOVABLE(ls); \
LABEL_REF(le); \
LABEL_REF(lc); \
if (NIL_P(ISEQ_COMPILE_DATA(iseq)->catch_table_ary)) \
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->catch_table_ary, rb_ary_tmp_new(3)); \
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_POPPED(anchor, desc, node) \
(debug_compile("== " desc "\n", \
iseq_compile_each(iseq, (anchor), (node), 1)))
/* compile node, which is popped when 'popped' is true */
#define COMPILE_(anchor, desc, node, popped) \
(debug_compile("== " desc "\n", \
iseq_compile_each(iseq, (anchor), (node), (popped))))
#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 : -1)
#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_TRACE(link) ((link)->type == ISEQ_ELEMENT_TRACE)
#define IS_INSN_ID(iobj, insn) (INSN_OF(iobj) == BIN(insn))
#define IS_NEXT_INSN_ID(link, insn) \
((link)->next && IS_INSN((link)->next) && IS_INSN_ID((link)->next, insn))
/* error */
#if CPDEBUG > 0
NORETURN(static void append_compile_error(rb_iseq_t *iseq, int line, const char *fmt, ...));
#endif
static void
append_compile_error(const rb_iseq_t *iseq, int line, const char *fmt, ...)
{
VALUE err_info = ISEQ_COMPILE_DATA(iseq)->err_info;
VALUE file = rb_iseq_path(iseq);
VALUE err = err_info == Qtrue ? Qfalse : 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);
}
else if (!err_info) {
RB_OBJ_WRITE(iseq, &ISEQ_COMPILE_DATA(iseq)->err_info, Qtrue);
}
if (compile_debug) rb_exc_fatal(err);
}
#if 0
static void
compile_bug(rb_iseq_t *iseq, int line, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
rb_report_bug_valist(rb_iseq_path(iseq), line, fmt, args);
va_end(args);
abort();
}
#endif
#define COMPILE_ERROR append_compile_error
#define ERROR_ARGS_AT(n) iseq, nd_line(n),
#define ERROR_ARGS ERROR_ARGS_AT(node)
#define EXPECT_NODE(prefix, node, ndtype, errval) \
do { \
const NODE *error_node = (node); \
enum node_type error_type = nd_type(error_node); \
if (error_type != (ndtype)) { \
COMPILE_ERROR(ERROR_ARGS_AT(error_node) \
prefix ": " #ndtype " is expected, but %s", \
ruby_node_name(error_type)); \
return errval; \
} \
} while (0)
#define EXPECT_NODE_NONULL(prefix, parent, ndtype, errval) \
do { \
COMPILE_ERROR(ERROR_ARGS_AT(parent) \
prefix ": must be " #ndtype ", but 0"); \
return errval; \
} while (0)
#define UNKNOWN_NODE(prefix, node, errval) \
do { \
const NODE *error_node = (node); \
COMPILE_ERROR(ERROR_ARGS_AT(error_node) prefix ": unknown node (%s)", \
ruby_node_name(nd_type(error_node))); \
return errval; \
} while (0)
#define COMPILE_OK 1
#define COMPILE_NG 0
#define CHECK(sub) if (!(sub)) {BEFORE_RETURN;return COMPILE_NG;}
#define NO_CHECK(sub) (void)(sub)
#define BEFORE_RETURN
/* leave name uninitialized so that compiler warn if INIT_ANCHOR is
* missing */
#define DECL_ANCHOR(name) \
LINK_ANCHOR name[1] = {{{ISEQ_ELEMENT_ANCHOR,},}}
#define INIT_ANCHOR(name) \
(name->last = &name->anchor)
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_with_cursor(const LINK_ELEMENT *link, const LINK_ELEMENT *curr, const LABEL *dest);
static void dump_disasm_list(const 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 TRACE *new_trace_body(rb_iseq_t *iseq, rb_event_flag_t event, long data);
static int iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *anchor, const NODE *n, int);
static int iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *const anchor);
static int iseq_setup_insn(rb_iseq_t *iseq, LINK_ANCHOR *const anchor);
static int iseq_optimize(rb_iseq_t *iseq, LINK_ANCHOR *const anchor);
static int iseq_insns_unification(rb_iseq_t *iseq, LINK_ANCHOR *const 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 *const anchor, const NODE *const node);
static int iseq_set_sequence_stackcaching(rb_iseq_t *iseq, LINK_ANCHOR *const anchor);
static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor);
static int iseq_set_exception_table(rb_iseq_t *iseq);
static int iseq_set_optargs_table(rb_iseq_t *iseq);
static int compile_defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE needstr);
/*
* To make Array to LinkedList, use link_anchor
*/
static void
verify_list(ISEQ_ARG_DECLARE const char *info, LINK_ANCHOR *const 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 *const 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 *const 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
#define ISEQ_LAST_LINE(iseq) (ISEQ_COMPILE_DATA(iseq)->last_line)
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_sym2str((VALUE)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);
}
VALUE
rb_iseq_compile_callback(rb_iseq_t *iseq, const struct rb_iseq_new_with_callback_callback_func * ifunc)
{
DECL_ANCHOR(ret);
INIT_ANCHOR(ret);
(*ifunc->func)(iseq, ret, ifunc->data);
ADD_INSN(ret, ISEQ_COMPILE_DATA(iseq)->last_line, leave);
CHECK(iseq_setup_insn(iseq, ret));
return iseq_setup(iseq, ret);
}
VALUE
rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node)
{
DECL_ANCHOR(ret);
INIT_ANCHOR(ret);
if (imemo_type_p((VALUE)node, imemo_ifunc)) {
rb_raise(rb_eArgError, "unexpected imemo_ifunc");
}
if (node == 0) {
NO_CHECK(COMPILE(ret, "nil", node));
iseq_set_local_table(iseq, 0);
}
/* assume node is T_NODE */
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, RUBY_EVENT_B_CALL);
ADD_INSN (ret, FIX2INT(iseq->body->location.first_lineno), nop);
ADD_LABEL(ret, start);
CHECK(COMPILE(ret, "block body", node->nd_body));
ADD_LABEL(ret, end);
ADD_TRACE(ret, RUBY_EVENT_B_RETURN);
ISEQ_COMPILE_DATA(iseq)->last_line = iseq->body->location.code_location.end_pos.lineno;
/* wide range catch handler must put at last */
ADD_CATCH_ENTRY(CATCH_TYPE_REDO, start, end, NULL, start);
ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, NULL, end);
break;
}
case ISEQ_TYPE_CLASS:
{
ADD_TRACE(ret, RUBY_EVENT_CLASS);
CHECK(COMPILE(ret, "scoped node", node->nd_body));
ADD_TRACE(ret, RUBY_EVENT_END);
ISEQ_COMPILE_DATA(iseq)->last_line = nd_line(node);
break;
}
case ISEQ_TYPE_METHOD:
{
ADD_TRACE(ret, RUBY_EVENT_CALL);
CHECK(COMPILE(ret, "scoped node", node->nd_body));
ADD_TRACE(ret, RUBY_EVENT_RETURN);
ISEQ_COMPILE_DATA(iseq)->last_line = nd_line(node);
break;
}
default: {
CHECK(COMPILE(ret, "scoped node", node->nd_body));
break;
}
}
}
else {
const char *m;
#define INVALID_ISEQ_TYPE(type) \
ISEQ_TYPE_##type: m = #type; goto invalid_iseq_type
switch (iseq->body->type) {
case INVALID_ISEQ_TYPE(METHOD);
case INVALID_ISEQ_TYPE(CLASS);
case INVALID_ISEQ_TYPE(BLOCK);
case INVALID_ISEQ_TYPE(EVAL);
case INVALID_ISEQ_TYPE(MAIN);
case INVALID_ISEQ_TYPE(TOP);
#undef INVALID_ISEQ_TYPE /* invalid iseq types end */
case ISEQ_TYPE_RESCUE:
iseq_set_exception_local_table(iseq);
CHECK(COMPILE(ret, "rescue", node));
break;
case ISEQ_TYPE_ENSURE:
iseq_set_exception_local_table(iseq);
CHECK(COMPILE_POPPED(ret, "ensure", node));
break;
case ISEQ_TYPE_PLAIN:
CHECK(COMPILE(ret, "ensure", node));
break;
default:
COMPILE_ERROR(ERROR_ARGS "unknown scope: %d", iseq->body->type);
return COMPILE_NG;
invalid_iseq_type:
COMPILE_ERROR(ERROR_ARGS "compile/ISEQ_TYPE_%s should not be reached", m);
return COMPILE_NG;
}
}
if (iseq->body->type == ISEQ_TYPE_RESCUE || iseq->body->type == ISEQ_TYPE_ENSURE) {
ADD_GETLOCAL(ret, 0, LVAR_ERRINFO, 0);
ADD_INSN1(ret, 0, throw, INT2FIX(0) /* continue throw */ );
}
else {
ADD_INSN(ret, ISEQ_COMPILE_DATA(iseq)->last_line, leave);
}
#if OPT_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
CHECK(iseq_setup_insn(iseq, ret));
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;
}
FL_SET(iseq, ISEQ_TRANSLATED);
#endif
return COMPILE_OK;
}
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_with_arena(struct iseq_compile_data_storage **arena, size_t size)
{
void *ptr = 0;
struct iseq_compile_data_storage *storage = *arena;
#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 +
offsetof(struct iseq_compile_data_storage, buff));
storage = *arena = 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 void *
compile_data_alloc(rb_iseq_t *iseq, size_t size)
{
struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->node.storage_current;
return compile_data_alloc_with_arena(arena, size);
}
static inline void *
compile_data_alloc2(rb_iseq_t *iseq, size_t x, size_t y)
{
size_t size = rb_size_mul_or_raise(x, y, rb_eRuntimeError);
return compile_data_alloc(iseq, size);
}
static INSN *
compile_data_alloc_insn(rb_iseq_t *iseq)
{
struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->insn.storage_current;
return (INSN *)compile_data_alloc_with_arena(arena, 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));
}
static TRACE *
compile_data_alloc_trace(rb_iseq_t *iseq)
{
return (TRACE *)compile_data_alloc(iseq, sizeof(TRACE));
}
/*
* elem1, elemX => elem1, elem2, elemX
*/
static void
ELEM_INSERT_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
ELEM_INSERT_PREV(LINK_ELEMENT *elem1, LINK_ELEMENT *elem2)
{
elem2->prev = elem1->prev;
elem2->next = elem1;
elem1->prev = elem2;
if (elem2->prev) {
elem2->prev->next = elem2;
}
}
/*
* elemX, elem1, elemY => elemX, elem2, elemY
*/
static void
ELEM_REPLACE(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
ELEM_REMOVE(LINK_ELEMENT *elem)
{
elem->prev->next = elem->next;
if (elem->next) {
elem->next->prev = elem->prev;
}
}
static LINK_ELEMENT *
FIRST_ELEMENT(const LINK_ANCHOR *const anchor)
{
return anchor->anchor.next;
}
static LINK_ELEMENT *
LAST_ELEMENT(LINK_ANCHOR *const anchor)
{
return anchor->last;
}
static LINK_ELEMENT *
POP_ELEMENT(ISEQ_ARG_DECLARE LINK_ANCHOR *const 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 LINK_ELEMENT *
ELEM_FIRST_INSN(LINK_ELEMENT *elem)
{
while (elem) {
switch (elem->type) {
case ISEQ_ELEMENT_INSN:
case ISEQ_ELEMENT_ADJUST:
return elem;
default:
elem = elem->next;
}
}
return NULL;
}
static int
LIST_INSN_SIZE_ONE(const LINK_ANCHOR *const anchor)
{
LINK_ELEMENT *first_insn = ELEM_FIRST_INSN(FIRST_ELEMENT(anchor));
if (first_insn != NULL &&
ELEM_FIRST_INSN(first_insn->next) == NULL) {
return TRUE;
}
else {
return FALSE;
}
}
static int
LIST_INSN_SIZE_ZERO(const LINK_ANCHOR *const anchor)
{
if (ELEM_FIRST_INSN(FIRST_ELEMENT(anchor)) == NULL) {
return TRUE;
}
else {
return FALSE;
}
}
/*
* 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 *const anc1, LINK_ANCHOR *const 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
#if CPDEBUG && 0
static void
debug_list(ISEQ_ARG_DECLARE LINK_ANCHOR *const 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
#else
#define debug_list(anc) ((void)0)
#endif
static TRACE *
new_trace_body(rb_iseq_t *iseq, rb_event_flag_t event, long data)
{
TRACE *trace = compile_data_alloc_trace(iseq);
trace->link.type = ISEQ_ELEMENT_TRACE;
trace->link.next = NULL;
trace->event = event;
trace->data = data;
return trace;
}
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;
labelobj->unremovable = 0;
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;
LABEL_UNREMOVABLE(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->insn_info.line_no = line_no;
iobj->insn_info.events = 0;
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 = compile_data_alloc2(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 | VM_CALL_KW_SPLAT)) &&
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 = compile_data_alloc2(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, const NODE *const node,
VALUE name, const rb_iseq_t *parent, enum iseq_type type, int line_no)
{
rb_iseq_t *ret_iseq;
rb_ast_body_t ast;
ast.root = node;
ast.compile_option = 0;
ast.line_count = -1;
debugs("[new_child_iseq]> ---------------------------------------\n");
ret_iseq = rb_iseq_new_with_opt(&ast, name,
rb_iseq_path(iseq), rb_iseq_realpath(iseq),
INT2FIX(line_no), parent, type, ISEQ_COMPILE_DATA(iseq)->option);
debugs("[new_child_iseq]< ---------------------------------------\n");
return ret_iseq;
}
static rb_iseq_t *
new_child_iseq_with_callback(rb_iseq_t *iseq, const struct rb_iseq_new_with_callback_callback_func *ifunc,
VALUE name, const rb_iseq_t *parent, enum iseq_type type, int line_no)
{
rb_iseq_t *ret_iseq;
debugs("[new_child_iseq_with_callback]> ---------------------------------------\n");
ret_iseq = rb_iseq_new_with_callback(ifunc, name,
rb_iseq_path(iseq), rb_iseq_realpath(iseq),
INT2FIX(line_no), parent, type, ISEQ_COMPILE_DATA(iseq)->option);
debugs("[new_child_iseq_with_callback]< ---------------------------------------\n");
return ret_iseq;
}
static void
set_catch_except_p(struct rb_iseq_constant_body *body)
{
body->catch_except_p = TRUE;
if (body->parent_iseq != NULL) {
set_catch_except_p(body->parent_iseq->body);
}
}
/* Set body->catch_except_p to TRUE if the ISeq may catch an exception. If it is FALSE,
JIT-ed code may be optimized. If we are extremely conservative, we should set TRUE
if catch table exists. But we want to optimize while loop, which always has catch
table entries for break/next/redo.
So this function sets TRUE for limited ISeqs with break/next/redo catch table entries
whose child ISeq would really raise an exception. */
static void
update_catch_except_flags(struct rb_iseq_constant_body *body)
{
unsigned int pos;
size_t i;
int insn;
const struct iseq_catch_table *ct = body->catch_table;
/* This assumes that a block has parent_iseq which may catch an exception from the block, and that
BREAK/NEXT/REDO catch table entries are used only when `throw` insn is used in the block. */
pos = 0;
while (pos < body->iseq_size) {
#if OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE
insn = rb_vm_insn_addr2insn((void *)body->iseq_encoded[pos]);
#else
insn = (int)body->iseq_encoded[pos];
#endif
if (insn == BIN(throw)) {
set_catch_except_p(body);
break;
}
pos += insn_len(insn);
}
if (ct == NULL)
return;
for (i = 0; i < ct->size; i++) {
const struct iseq_catch_table_entry *entry =
UNALIGNED_MEMBER_PTR(ct, entries[i]);
if (entry->type != CATCH_TYPE_BREAK
&& entry->type != CATCH_TYPE_NEXT
&& entry->type != CATCH_TYPE_REDO) {
body->catch_except_p = TRUE;
break;
}
}
}
static void
iseq_insert_nop_between_end_and_cont(rb_iseq_t *iseq)
{
VALUE catch_table_ary = ISEQ_COMPILE_DATA(iseq)->catch_table_ary;
if (NIL_P(catch_table_ary)) return;
unsigned int i, tlen = (unsigned int)RARRAY_LEN(catch_table_ary);
const VALUE *tptr = RARRAY_CONST_PTR_TRANSIENT(catch_table_ary);
for (i = 0; i < tlen; i++) {
const VALUE *ptr = RARRAY_CONST_PTR_TRANSIENT(tptr[i]);
LINK_ELEMENT *end = (LINK_ELEMENT *)(ptr[2] & ~1);
LINK_ELEMENT *cont = (LINK_ELEMENT *)(ptr[4] & ~1);
LINK_ELEMENT *e;
for (e = end; e && (IS_LABEL(e) || IS_TRACE(e)); e = e->next) {
if (e == cont) {
INSN *nop = new_insn_core(iseq, 0, BIN(nop), 0, 0);
ELEM_INSERT_NEXT(end, &nop->link);
break;
}
}
}
}
static int
iseq_setup_insn(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
{
if (RTEST(ISEQ_COMPILE_DATA(iseq)->err_info))
return COMPILE_NG;
/* 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 3.4 (iseq_insert_nop_between_end_and_cont)]\n");
iseq_insert_nop_between_end_and_cont(iseq);
return COMPILE_OK;
}
static int
iseq_setup(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
{
if (RTEST(ISEQ_COMPILE_DATA(iseq)->err_info))
return COMPILE_NG;
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;
update_catch_except_flags(iseq->body);
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)
{
iseq->body->local_table_size = numberof(rb_iseq_shared_exc_local_tbl);
iseq->body->local_table = rb_iseq_shared_exc_local_tbl;
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) {
COMPILE_ERROR(iseq, ISEQ_LAST_LINE(iseq),
"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;
const rb_iseq_t *const topmost_iseq = iseq;
while (iseq) {
idx = get_dyna_var_idx_at_raw(iseq, id);
if (idx >= 0) {
break;
}
iseq = iseq->body->parent_iseq;
lv++;
}
if (idx < 0) {
COMPILE_ERROR(topmost_iseq, ISEQ_LAST_LINE(topmost_iseq),
"get_dyna_var_idx: -1");
}
*level = lv;
*ls = iseq->body->local_table_size;
return idx;
}
static int
iseq_local_block_param_p(const rb_iseq_t *iseq, unsigned int idx, unsigned int level)
{
const struct rb_iseq_constant_body *body;
while (level > 0) {
iseq = iseq->body->parent_iseq;
level--;
}
body = iseq->body;
if (body->local_iseq == iseq && /* local variables */
body->param.flags.has_block &&
body->local_table_size - body->param.block_start == idx) {
return TRUE;
}
else {
return FALSE;
}
}
static int
iseq_block_param_id_p(const rb_iseq_t *iseq, ID id, int *pidx, int *plevel)
{
int level, ls;
int idx = get_dyna_var_idx(iseq, id, &level, &ls);
if (iseq_local_block_param_p(iseq, ls - idx, level)) {
*pidx = ls - idx;
*plevel = level;
return TRUE;
}
else {
return FALSE;
}
}
static void
iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level)
{
if (iseq_local_block_param_p(iseq, idx, level)) {
ADD_INSN2(seq, line, getblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
}
else {
ADD_INSN2(seq, line, getlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
}
}
static void
iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int idx, int level)
{
if (iseq_local_block_param_p(iseq, idx, level)) {
ADD_INSN2(seq, line, setblockparam, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
}
else {
ADD_INSN2(seq, line, setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level));
}
}
static void
iseq_calc_param_size(rb_iseq_t *iseq)
{
struct rb_iseq_constant_body *const body = iseq->body;
if (body->param.flags.has_opt ||
body->param.flags.has_post ||
body->param.flags.has_rest ||
body->param.flags.has_block ||
body->param.flags.has_kw ||
body->param.flags.has_kwrest) {
if (body->param.flags.has_block) {
body->param.size = body->param.block_start + 1;
}
else if (body->param.flags.has_kwrest) {
body->param.size = body->param.keyword->rest_start + 1;
}
else if (body->param.flags.has_kw) {
body->param.size = body->param.keyword->bits_start + 1;
}
else if (body->param.flags.has_post) {
body->param.size = body->param.post_start + body->param.post_num;
}
else if (body->param.flags.has_rest) {
body->param.size = body->param.rest_start + 1;
}
else if (body->param.flags.has_opt) {
body->param.size = body->param.lead_num + body->param.opt_num;
}
else {
UNREACHABLE;
}
}
else {
body->param.size = body->param.lead_num;
}
}
static int
iseq_set_arguments_keywords(rb_iseq_t *iseq, LINK_ANCHOR *const optargs,
const struct rb_args_info *args, int arg_size)
{
const NODE *node = args->kw_args;
struct rb_iseq_constant_body *const body = iseq->body;
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;
body->param.flags.has_kw = TRUE;
body->param.keyword = keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
while (node) {
kw++;
node = node->nd_next;
}
arg_size += kw;
keyword->bits_start = arg_size++;
node = args->kw_args;
while (node) {
const NODE *val_node = node->nd_body->nd_value;
VALUE dv;
if (val_node == NODE_SPECIAL_REQUIRED_KEYWORD) {
++rkw;
}
else {
switch (nd_type(val_node)) {
case NODE_LIT:
dv = val_node->nd_lit;
break;
case NODE_NIL:
dv = Qnil;
break;
case NODE_TRUE:
dv = Qtrue;
break;
case NODE_FALSE:
dv = Qfalse;
break;
default:
NO_CHECK(COMPILE_POPPED(optargs, "kwarg", node)); /* nd_type(node) == NODE_KW_ARG */
dv = complex_mark;
}
keyword->num = ++di;
rb_ary_push(default_values, dv);
}
node = node->nd_next;
}
keyword->num = kw;
if (args->kw_rest_arg->nd_vid != 0) {
keyword->rest_start = arg_size++;
body->param.flags.has_kwrest = TRUE;
}
keyword->required_num = rkw;
keyword->table = &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;
if (!SPECIAL_CONST_P(dv)) {
RB_OBJ_WRITTEN(iseq, Qundef, dv);
}
dvs[i] = dv;
}
keyword->default_values = dvs;
}
return arg_size;
}
static int
iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const optargs, const NODE *const node_args)
{
debugs("iseq_set_arguments: %s\n", node_args ? "" : "0");
if (node_args) {
struct rb_iseq_constant_body *const body = iseq->body;
struct rb_args_info *args = node_args->nd_ainfo;
ID rest_id = 0;
int last_comma = 0;
ID block_id = 0;
int arg_size;
EXPECT_NODE("iseq_set_arguments", node_args, NODE_ARGS, COMPILE_NG);
body->param.lead_num = arg_size = (int)args->pre_args_num;
if (body->param.lead_num > 0) body->param.flags.has_lead = TRUE;
debugs(" - argc: %d\n", body->param.lead_num);
rest_id = args->rest_arg;
if (rest_id == NODE_SPECIAL_EXCESSIVE_COMMA) {
last_comma = 1;
rest_id = 0;
}
block_id = args->block_arg;
if (args->opt_args) {
const 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);
NO_CHECK(COMPILE_POPPED(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_TRANSIENT(labels), VALUE, i+1);
for (j = 0; j < i+1; j++) {
opt_table[j] &= ~1;
}
rb_ary_clear(labels);
body->param.flags.has_opt = TRUE;
body->param.opt_num = i;
body->param.opt_table = opt_table;
arg_size += i;
}
if (rest_id) {
body->param.rest_start = arg_size++;
body->param.flags.has_rest = TRUE;
assert(body->param.rest_start != -1);
}
if (args->first_post_arg) {
body->param.post_start = arg_size;
body->param.post_num = args->post_args_num;
body->param.flags.has_post = TRUE;
arg_size += args->post_args_num;
if (body->param.flags.has_rest) { /* TODO: why that? */
body->param.post_start = body->param.rest_start + 1;
}
}
if (args->kw_args) {
arg_size = iseq_set_arguments_keywords(iseq, optargs, args, arg_size);
}
else if (args->kw_rest_arg) {
struct rb_iseq_param_keyword *keyword = ZALLOC_N(struct rb_iseq_param_keyword, 1);
keyword->rest_start = arg_size++;
body->param.keyword = keyword;
body->param.flags.has_kwrest = TRUE;
}
else if (args->no_kwarg) {
body->param.flags.accepts_no_kwarg = TRUE;
}
if (block_id) {
body->param.block_start = arg_size++;
body->param.flags.has_block = TRUE;
}
iseq_calc_param_size(iseq);
body->param.size = arg_size;
if (args->pre_init) { /* m_init */
NO_CHECK(COMPILE_POPPED(optargs, "init arguments (m)", args->pre_init));
}
if (args->post_init) { /* p_init */
NO_CHECK(COMPILE_POPPED(optargs, "init arguments (p)", args->post_init));
}
if (body->type == ISEQ_TYPE_BLOCK) {
if (body->param.flags.has_opt == FALSE &&
body->param.flags.has_post == FALSE &&
body->param.flags.has_rest == FALSE &&
body->param.flags.has_kw == FALSE &&
body->param.flags.has_kwrest == FALSE) {
if (body->param.lead_num == 1 && last_comma == 0) {
/* {|a|} */
body->param.flags.ambiguous_param0 = TRUE;
}
}
}
}
return COMPILE_OK;
}
static int
iseq_set_local_table(rb_iseq_t *iseq, const ID *tbl)
{
unsigned int size;
if (tbl) {
size = (unsigned 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_table_size = size;
debugs("iseq_set_local_table: %u\n", iseq->body->local_table_size);
return COMPILE_OK;
}
static int
cdhash_cmp(VALUE val, VALUE lit)
{
int tval, tlit;
if (val == lit) {
return 0;
}
else if ((tlit = OBJ_BUILTIN_TYPE(lit)) == -1) {
return val != lit;
}
else if ((tval = OBJ_BUILTIN_TYPE(val)) == -1) {
return -1;
}
else if (tlit != tval) {
return -1;
}
else if (tlit == T_SYMBOL) {
return val != lit;
}
else if (tlit == T_STRING) {
return rb_str_hash_cmp(lit, val);
}
else if (tlit == T_BIGNUM) {
long x = FIX2LONG(rb_big_cmp(lit, val));
/* Given lit and val are both Bignum, x must be -1, 0, 1.
* There is no need to call rb_fix2int here. */
RUBY_ASSERT((x == 1) || (x == 0) || (x == -1));
return (int)x;
}
else if (tlit == T_FLOAT) {
return rb_float_cmp(lit, val);
}
else {
UNREACHABLE_RETURN(-1);
}
}
static st_index_t
cdhash_hash(VALUE a)
{
switch (OBJ_BUILTIN_TYPE(a)) {
case -1:
case T_SYMBOL:
return (st_index_t)a;
case T_STRING:
return rb_str_hash(a);
case T_BIGNUM:
return FIX2LONG(rb_big_hash(a));
case T_FLOAT:
return rb_dbl_long_hash(RFLOAT_VALUE(a));
default:
UNREACHABLE_RETURN(0);
}
}
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, VALUE 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;
}
static inline VALUE
get_ivar_ic_value(rb_iseq_t *iseq,ID id)
{
VALUE val;
struct rb_id_table *tbl = ISEQ_COMPILE_DATA(iseq)->ivar_cache_table;
if (tbl) {
if (rb_id_table_lookup(tbl,id,&val)) {
return val;
}
}
else {
tbl = rb_id_table_create(1);
ISEQ_COMPILE_DATA(iseq)->ivar_cache_table = tbl;
}
val = INT2FIX(iseq->body->is_size++);
rb_id_table_insert(tbl,id,val);
return val;
}
#define BADINSN_DUMP(anchor, list, dest) \
dump_disasm_list_with_cursor(FIRST_ELEMENT(anchor), list, dest)
#define BADINSN_ERROR \
(xfree(generated_iseq), \
xfree(insns_info), \
BADINSN_DUMP(anchor, list, NULL), \
COMPILE_ERROR)
static int
fix_sp_depth(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
{
int stack_max = 0, sp = 0, line = 0;
LINK_ELEMENT *list;
for (list = FIRST_ELEMENT(anchor); list; list = list->next) {
if (list->type == ISEQ_ELEMENT_LABEL) {
LABEL *lobj = (LABEL *)list;
lobj->set = TRUE;
}
}
for (list = FIRST_ELEMENT(anchor); list; list = list->next) {
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 < 0) {
BADINSN_DUMP(anchor, list, NULL);
COMPILE_ERROR(iseq, iobj->insn_info.line_no,
"argument stack underflow (%d)", sp);
return -1;
}
if (sp > stack_max) {
stack_max = sp;
}
line = iobj->insn_info.line_no;
/* fprintf(stderr, "insn: %-16s, sp: %d\n", insn_name(iobj->insn_id), sp); */
operands = iobj->operands;
insn = iobj->insn_id;
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); */
BADINSN_DUMP(anchor, list, NULL);
COMPILE_ERROR(iseq, iobj->insn_info.line_no,
"operand size miss! (%d for %d)",
iobj->operand_size, len - 1);
return -1;
}
for (j = 0; types[j]; j++) {
if (types[j] == TS_OFFSET) {
/* label(destination position) */
LABEL *lobj = (LABEL *)operands[j];
if (!lobj->set) {
BADINSN_DUMP(anchor, list, NULL);
COMPILE_ERROR(iseq, iobj->insn_info.line_no,
"unknown label: "LABEL_FORMAT, lobj->label_no);
return -1;
}
if (lobj->sp == -1) {
lobj->sp = sp;
}
}
}
break;
}
case ISEQ_ELEMENT_LABEL:
{
LABEL *lobj = (LABEL *)list;
if (lobj->sp == -1) {
lobj->sp = sp;
}
else {
sp = lobj->sp;
}
break;
}
case ISEQ_ELEMENT_TRACE:
{
/* ignore */
break;
}
case ISEQ_ELEMENT_ADJUST:
{
ADJUST *adjust = (ADJUST *)list;
int orig_sp = sp;
sp = adjust->label ? adjust->label->sp : 0;
if (adjust->line_no != -1 && orig_sp - sp < 0) {
BADINSN_DUMP(anchor, list, NULL);
COMPILE_ERROR(iseq, adjust->line_no,
"iseq_set_sequence: adjust bug %d < %d",
orig_sp, sp);
return -1;
}
break;
}
default:
BADINSN_DUMP(anchor, list, NULL);
COMPILE_ERROR(iseq, line, "unknown list type: %d", list->type);
return -1;
}
}
return stack_max;
}
static int
add_insn_info(struct iseq_insn_info_entry *insns_info, unsigned int *positions,
int insns_info_index, int code_index, const INSN *iobj)
{
if (insns_info_index == 0 ||
insns_info[insns_info_index-1].line_no != iobj->insn_info.line_no ||
insns_info[insns_info_index-1].events != iobj->insn_info.events) {
insns_info[insns_info_index].line_no = iobj->insn_info.line_no;
insns_info[insns_info_index].events = iobj->insn_info.events;
positions[insns_info_index] = code_index;
return TRUE;
}
return FALSE;
}
static int
add_adjust_info(struct iseq_insn_info_entry *insns_info, unsigned int *positions,
int insns_info_index, int code_index, const ADJUST *adjust)
{
if (insns_info_index > 0 ||
insns_info[insns_info_index-1].line_no != adjust->line_no) {
insns_info[insns_info_index].line_no = adjust->line_no;
insns_info[insns_info_index].events = 0;
positions[insns_info_index] = code_index;
return TRUE;
}
return FALSE;
}
/**
ruby insn object list -> raw instruction sequence
*/
static int
iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor)
{
struct iseq_insn_info_entry *insns_info;
struct rb_iseq_constant_body *const body = iseq->body;
unsigned int *positions;
LINK_ELEMENT *list;
VALUE *generated_iseq;
rb_event_flag_t events = 0;
long data = 0;
int insn_num, code_index, insns_info_index, sp = 0;
int stack_max = fix_sp_depth(iseq, anchor);
if (stack_max < 0) return COMPILE_NG;
/* 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;
/* update sp */
sp = calc_sp_depth(sp, iobj);
insn_num++;
events = iobj->insn_info.events |= events;
if (ISEQ_COVERAGE(iseq)) {
if (ISEQ_LINE_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_LINE) &&
!(rb_get_coverage_mode() & COVERAGE_TARGET_ONESHOT_LINES)) {
int line = iobj->insn_info.line_no;
if (line >= 1) {
RARRAY_ASET(ISEQ_LINE_COVERAGE(iseq), line - 1, INT2FIX(0));
}
}
if (ISEQ_BRANCH_COVERAGE(iseq) && (events & RUBY_EVENT_COVERAGE_BRANCH)) {
while (RARRAY_LEN(ISEQ_PC2BRANCHINDEX(iseq)) <= code_index) {
rb_ary_push(ISEQ_PC2BRANCHINDEX(iseq), Qnil);
}
RARRAY_ASET(ISEQ_PC2BRANCHINDEX(iseq), code_index, INT2FIX(data));
}
}
code_index += insn_data_length(iobj);
events = 0;
data = 0;
break;
}
case ISEQ_ELEMENT_LABEL:
{
LABEL *lobj = (LABEL *)list;
lobj->position = code_index;
sp = lobj->sp;
break;
}
case ISEQ_ELEMENT_TRACE:
{
TRACE *trace = (TRACE *)list;
events |= trace->event;
if (trace->event & RUBY_EVENT_COVERAGE_BRANCH) data = trace->data;
break;
}
case ISEQ_ELEMENT_ADJUST:
{
ADJUST *adjust = (ADJUST *)list;
if (adjust->line_no != -1) {
int orig_sp = sp;
sp = adjust->label ? adjust->label->sp : 0;
if (orig_sp - sp > 0) {
if (orig_sp - sp > 1) code_index++; /* 1 operand */
code_index++; /* insn */
insn_num++;
}
}
break;
}
default: break;
}
list = list->next;
}
/* make instruction sequence */
generated_iseq = ALLOC_N(VALUE, code_index);
insns_info = ALLOC_N(struct iseq_insn_info_entry, insn_num);
positions = ALLOC_N(unsigned int, insn_num);
body->is_entries = ZALLOC_N(union iseq_inline_storage_entry, body->is_size);
body->ci_entries =
rb_xmalloc_mul_add_mul(
sizeof(struct rb_call_info), body->ci_size,
sizeof(struct rb_call_info_with_kwarg), body->ci_kw_size);
MEMZERO(body->ci_entries + body->ci_size, struct rb_call_info_with_kwarg, body->ci_kw_size); /* need to clear ci_kw entries */
body->cc_entries = ZALLOC_N(struct rb_call_cache, body->ci_size + body->ci_kw_size);
ISEQ_COMPILE_DATA(iseq)->ci_index = ISEQ_COMPILE_DATA(iseq)->ci_kw_index = 0;
list = FIRST_ELEMENT(anchor);
insns_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);
/* 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);
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];
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;
RB_OBJ_WRITTEN(iseq, Qundef, map);
FL_SET(iseq, ISEQ_MARKABLE_ISEQ);
break;
}
case TS_LINDEX:
case TS_NUM: /* ulong */
generated_iseq[code_index + 1 + j] = FIX2INT(operands[j]);
break;
case TS_VALUE: /* VALUE */
case TS_ISEQ: /* iseq */
{
VALUE v = operands[j];
generated_iseq[code_index + 1 + j] = v;
/* to mark ruby object */
if (!SPECIAL_CONST_P(v)) {
RB_OBJ_WRITTEN(iseq, Qundef, v);
FL_SET(iseq, ISEQ_MARKABLE_ISEQ);
}
break;
}
case TS_ISE: /* inline storage entry */
/* Treated as an IC, but may contain a markable VALUE */
FL_SET(iseq, ISEQ_MARKABLE_ISEQ);
/* fall through */
case TS_IC: /* inline cache */
{
unsigned int ic_index = FIX2UINT(operands[j]);
IC ic = (IC)&body->is_entries[ic_index];
if (UNLIKELY(ic_index >= body->is_size)) {
BADINSN_DUMP(anchor, &iobj->link, 0);
COMPILE_ERROR(iseq, iobj->insn_info.line_no,
"iseq_set_sequence: ic_index overflow: index: %d, size: %d",
ic_index, 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 *)&body->ci_entries[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 <= body->ci_kw_size);
}
else {
ci = &body->ci_entries[ISEQ_COMPILE_DATA(iseq)->ci_index++];
*ci = *base_ci;
assert(ISEQ_COMPILE_DATA(iseq)->ci_index <= body->ci_size);
}
generated_iseq[code_index + 1 + j] = (VALUE)ci;
break;
}
case TS_CALLCACHE:
{
struct rb_call_cache *cc = &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:
BADINSN_ERROR(iseq, iobj->insn_info.line_no,
"unknown operand type: %c", type);
return COMPILE_NG;
}
}
if (add_insn_info(insns_info, positions, insns_info_index, code_index, iobj)) insns_info_index++;
code_index += len;
break;
}
case ISEQ_ELEMENT_LABEL:
{
LABEL *lobj = (LABEL *)list;
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) {
const int diff = orig_sp - sp;
if (diff > 0) {
if (add_adjust_info(insns_info, positions, insns_info_index, code_index, adjust)) insns_info_index++;
}
if (diff > 1) {
generated_iseq[code_index++] = BIN(adjuststack);
generated_iseq[code_index++] = orig_sp - sp;
}
else if (diff == 1) {
generated_iseq[code_index++] = BIN(pop);
}
else if (diff < 0) {
int label_no = adjust->label ? adjust->label->label_no : -1;
xfree(generated_iseq);
xfree(insns_info);
xfree(positions);
debug_list(anchor);
COMPILE_ERROR(iseq, adjust->line_no,
"iseq_set_sequence: adjust bug to %d %d < %d",
label_no, orig_sp, sp);
return COMPILE_NG;
}
}
break;
}
default:
/* ignore */
break;
}
list = list->next;
}
body->iseq_encoded = (void *)generated_iseq;
body->iseq_size = code_index;
body->stack_max = stack_max;
/* get rid of memory leak when REALLOC failed */
body->insns_info.body = insns_info;
body->insns_info.positions = positions;
REALLOC_N(insns_info, struct iseq_insn_info_entry, insns_info_index);
body->insns_info.body = insns_info;
REALLOC_N(positions, unsigned int, insns_info_index);
body->insns_info.positions = positions;
body->insns_info.size = insns_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;
if (NIL_P(ISEQ_COMPILE_DATA(iseq)->catch_table_ary)) goto no_catch_table;
tlen = (int)RARRAY_LEN(ISEQ_COMPILE_DATA(iseq)->catch_table_ary);
tptr = RARRAY_CONST_PTR_TRANSIENT(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_TRANSIENT(tptr[i]);
entry = UNALIGNED_MEMBER_PTR(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];
RB_OBJ_WRITTEN(iseq, Qundef, 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 {
no_catch_table:
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;
rb_event_flag_t events = 0;
list = lobj->link.next;
while (list) {
switch (list->type) {
case ISEQ_ELEMENT_INSN:
case ISEQ_ELEMENT_ADJUST:
goto found;
case ISEQ_ELEMENT_LABEL:
/* ignore */
break;
case ISEQ_ELEMENT_TRACE:
{
TRACE *trace = (TRACE *)list;
events |= trace->event;
}
break;
default: break;
}
list = list->next;
}
found:
if (list && IS_INSN(list)) {
INSN *iobj = (INSN *)list;
iobj->insn_info.events |= events;
}
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) ELEM_REMOVE(&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) ELEM_REMOVE(&dl->link);
}
static LABEL*
find_destination(INSN *i)
{
int pos, len = insn_len(i->insn_id);
for (pos = 0; pos < len; ++pos) {
if (insn_op_types(i->insn_id)[pos] == TS_OFFSET) {
return (LABEL *)OPERAND_AT(i, pos);
}
}
return 0;
}
static int
remove_unreachable_chunk(rb_iseq_t *iseq, LINK_ELEMENT *i)
{
LINK_ELEMENT *first = i, *end;
int *unref_counts = 0, nlabels = ISEQ_COMPILE_DATA(iseq)->label_no;
if (!i) return 0;
unref_counts = ALLOCA_N(int, nlabels);
MEMZERO(unref_counts, int, nlabels);
end = i;
do {
LABEL *lab;
if (IS_INSN(i)) {
if (IS_INSN_ID(i, leave)) {
end = i;
break;
}
else if ((lab = find_destination((INSN *)i)) != 0) {
if (lab->unremovable) break;
unref_counts[lab->label_no]++;
}
}
else if (IS_LABEL(i)) {
lab = (LABEL *)i;
if (lab->unremovable) return 0;
if (lab->refcnt > unref_counts[lab->label_no]) {
if (i == first) return 0;
break;
}
continue;
}
else if (IS_TRACE(i)) {
/* do nothing */
}
else if (IS_ADJUST(i)) {
LABEL *dest = ((ADJUST *)i)->label;
if (dest && dest->unremovable) return 0;
}
end = i;
} while ((i = i->next) != 0);
i = first;
do {
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;
}
}
}
ELEM_REMOVE(i);
} while ((i != end) && (i = i->next) != 0);
return 1;
}
static int
iseq_pop_newarray(rb_iseq_t *iseq, INSN *iobj)
{
switch (OPERAND_AT(iobj, 0)) {
case INT2FIX(0): /* empty array */
ELEM_REMOVE(&iobj->link);
return TRUE;
case INT2FIX(1): /* single element array */
ELEM_REMOVE(&iobj->link);
return FALSE;
default:
iobj->insn_id = BIN(adjuststack);
return TRUE;
}
}
static int
same_debug_pos_p(LINK_ELEMENT *iobj1, LINK_ELEMENT *iobj2)
{
VALUE debug1 = OPERAND_AT(iobj1, 0);
VALUE debug2 = OPERAND_AT(iobj2, 0);
if (debug1 == debug2) return TRUE;
if (!RB_TYPE_P(debug1, T_ARRAY)) return FALSE;
if (!RB_TYPE_P(debug2, T_ARRAY)) return FALSE;
if (RARRAY_LEN(debug1) != 2) return FALSE;
if (RARRAY_LEN(debug2) != 2) return FALSE;
if (RARRAY_AREF(debug1, 0) != RARRAY_AREF(debug2, 0)) return FALSE;
if (RARRAY_AREF(debug1, 1) != RARRAY_AREF(debug2, 1)) return FALSE;
return TRUE;
}
static int
is_frozen_putstring(INSN *insn, VALUE *op)
{
if (IS_INSN_ID(insn, putstring)) {
*op = OPERAND_AT(insn, 0);
return 1;
}
else if (IS_INSN_ID(insn, putobject)) { /* frozen_string_literal */
*op = OPERAND_AT(insn, 0);
return RB_TYPE_P(*op, T_STRING);
}
return 0;
}
static int
optimize_checktype(rb_iseq_t *iseq, INSN *iobj)
{
/*
* putobject obj
* dup
* checktype T_XXX
* branchif l1
* l2:
* ...
* l1:
*
* => obj is a T_XXX
*
* putobject obj (T_XXX)
* jump L1
* L1:
*
* => obj is not a T_XXX
*
* putobject obj (T_XXX)
* jump L2
* L2:
*/
int line;
INSN *niobj, *ciobj, *dup = 0;
LABEL *dest = 0;
VALUE type;
switch (INSN_OF(iobj)) {
case BIN(putstring):
type = INT2FIX(T_STRING);
break;
case BIN(putnil):
type = INT2FIX(T_NIL);
break;
case BIN(putobject):
type = INT2FIX(TYPE(OPERAND_AT(iobj, 0)));
break;
default: return FALSE;
}
ciobj = (INSN *)get_next_insn(iobj);
if (IS_INSN_ID(ciobj, jump)) {
ciobj = (INSN *)get_next_insn((INSN*)OPERAND_AT(ciobj, 0));
}
if (IS_INSN_ID(ciobj, dup)) {
ciobj = (INSN *)get_next_insn(dup = ciobj);
}
if (!ciobj || !IS_INSN_ID(ciobj, checktype)) return FALSE;
niobj = (INSN *)get_next_insn(ciobj);
if (!niobj) {
no_branch:
/* TODO: putobject true/false */
return FALSE;
}
switch (INSN_OF(niobj)) {
case BIN(branchif):
if (OPERAND_AT(ciobj, 0) == type) {
dest = (LABEL *)OPERAND_AT(niobj, 0);
}
break;
case BIN(branchunless):
if (OPERAND_AT(ciobj, 0) != type) {
dest = (LABEL *)OPERAND_AT(niobj, 0);
}
break;
default:
goto no_branch;
}
line = ciobj->insn_info.line_no;
if (!dest) {
if (niobj->link.next && IS_LABEL(niobj->link.next)) {
dest = (LABEL *)niobj->link.next; /* reuse label */
}
else {
dest = NEW_LABEL(line);
ELEM_INSERT_NEXT(&niobj->link, &dest->link);
}
}
INSERT_AFTER_INSN1(iobj, line, jump, dest);
LABEL_REF(dest);
if (!dup) INSERT_AFTER_INSN(iobj, line, pop);
return TRUE;
}
static int
iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcallopt)
{
INSN *const iobj = (INSN *)list;
again:
optimize_checktype(iseq, iobj);
if (IS_INSN_ID(iobj, jump)) {
INSN *niobj, *diobj, *piobj;
diobj = (INSN *)get_destination_insn(iobj);
niobj = (INSN *)get_next_insn(iobj);
if (diobj == niobj) {
/*
* jump LABEL
* LABEL:
* =>
* LABEL:
*/
unref_destination(iobj, 0);
ELEM_REMOVE(&iobj->link);
return COMPILE_OK;
}
else if (iobj != diobj && IS_INSN_ID(diobj, jump) &&
OPERAND_AT(iobj, 0) != OPERAND_AT(diobj, 0)) {
/*
* useless jump elimination:
* jump LABEL1
* ...
* LABEL1:
* jump LABEL2
*
* => in this case, first jump instruction should jump to
* LABEL2 directly
*/
replace_destination(iobj, diobj);
remove_unreachable_chunk(iseq, iobj->link.next);
goto again;
}
else if (IS_INSN_ID(diobj, leave)) {
INSN *pop;
/*
* jump LABEL
* ...
* LABEL:
* leave
* =>
* leave
* pop
* ...
* LABEL:
* leave
*/
/* replace */
unref_destination(iobj, 0);
iobj->insn_id = BIN(leave);
iobj->operand_size = 0;
iobj->insn_info = diobj->insn_info;
/* adjust stack depth */
pop = new_insn_body(iseq, diobj->insn_info.line_no, BIN(pop), 0);
ELEM_INSERT_NEXT(&iobj->link, &pop->link);
goto again;
}
else if (IS_INSN(iobj->link.prev) &&
(piobj = (INSN *)iobj->link.prev) &&
(IS_INSN_ID(piobj, branchif) ||
IS_INSN_ID(piobj, branchunless))) {
INSN *pdiobj = (INSN *)get_destination_insn(piobj);
if (niobj == pdiobj) {
int refcnt = IS_LABEL(piobj->link.next) ?
((LABEL *)piobj->link.next)->refcnt : 0;
/*
* useless jump elimination (if/unless destination):
* if L1
* jump L2
* L1:
* ...
* L2:
*
* ==>
* unless L2
* L1:
* ...
* L2:
*/
piobj->insn_id = (IS_INSN_ID(piobj, branchif))
? BIN(branchunless) : BIN(branchif);
replace_destination(piobj, iobj);
if (refcnt <= 1) {
ELEM_REMOVE(&iobj->link);
}
else {
/* TODO: replace other branch destinations too */
}
return COMPILE_OK;
}
else if (diobj == pdiobj) {
/*
* useless jump elimination (if/unless before jump):
* L1:
* ...
* if L1
* jump L1
*
* ==>
* L1:
* ...
* pop
* jump L1
*/
INSN *popiobj = new_insn_core(iseq, iobj->insn_info.line_no,
BIN(pop), 0, 0);
ELEM_REPLACE(&piobj->link, &popiobj->link);
}
}
if (remove_unreachable_chunk(iseq, iobj->link.next)) {
goto again;
}
}
/*
* putstring "beg"
* putstring "end"
* newrange excl
*
* ==>
*
* putobject "beg".."end"
*/
if (IS_INSN_ID(iobj, checkmatch)) {
INSN *range = (INSN *)get_prev_insn(iobj);
INSN *beg, *end;
VALUE str_beg, str_end;
if (range && IS_INSN_ID(range, newrange) &&
(end = (INSN *)get_prev_insn(range)) != 0 &&
is_frozen_putstring(end, &str_end) &&
(beg = (INSN *)get_prev_insn(end)) != 0 &&
is_frozen_putstring(beg, &str_beg)) {
int excl = FIX2INT(OPERAND_AT(range, 0));
VALUE lit_range = rb_range_new(str_beg, str_end, excl);
ELEM_REMOVE(&beg->link);
ELEM_REMOVE(&end->link);
range->insn_id = BIN(putobject);
OPERAND_AT(range, 0) = lit_range;
RB_OBJ_WRITTEN(iseq, Qundef, lit_range);
}
}
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) ||
IS_INSN_ID(pobj, duparray) ||
IS_INSN_ID(pobj, newarray)) {
cond = IS_INSN_ID(iobj, branchif);
}
else if (IS_INSN_ID(pobj, putnil)) {
cond = !IS_INSN_ID(iobj, branchif);
}
else break;
if (prev_dup || !IS_INSN_ID(pobj, newarray)) {
ELEM_REMOVE(iobj->link.prev);
}
else if (!iseq_pop_newarray(iseq, pobj)) {
pobj = new_insn_core(iseq, pobj->insn_info.line_no, BIN(pop), 0, NULL);
ELEM_INSERT_PREV(&iobj->link, &pobj->link);
}
if (cond) {
if (prev_dup) {
pobj = new_insn_core(iseq, pobj->insn_info.line_no, BIN(putnil), 0, NULL);
ELEM_INSERT_NEXT(&iobj->link, &pobj->link);
}
iobj->insn_id = BIN(jump);
goto again;
}
else {
unref_destination(iobj, 0);
ELEM_REMOVE(&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) ||
previ == BIN(dup) ||
previ == BIN(getlocal) ||
previ == BIN(getblockparam) ||
previ == BIN(getblockparamproxy) ||
/* getinstancevariable may issue a warning */
previ == BIN(duparray)) {
/* just push operand or static value and pop soon, no
* side effects */
ELEM_REMOVE(prev);
ELEM_REMOVE(&iobj->link);
}
else if (previ == BIN(newarray) && iseq_pop_newarray(iseq, (INSN*)prev)) {
ELEM_REMOVE(&iobj->link);
}
else if (previ == BIN(concatarray)) {
INSN *piobj = (INSN *)prev;
INSERT_BEFORE_INSN1(piobj, piobj->insn_info.line_no, splatarray, Qfalse);
INSN_OF(piobj) = BIN(pop);
}
else if (previ == BIN(concatstrings)) {
if (OPERAND_AT(prev, 0) == INT2FIX(1)) {
ELEM_REMOVE(prev);
}
else {
ELEM_REMOVE(&iobj->link);
INSN_OF(prev) = BIN(adjuststack);
}
}
}
}
if (IS_INSN_ID(iobj, newarray) ||
IS_INSN_ID(iobj, duparray) ||
IS_INSN_ID(iobj, expandarray) ||
IS_INSN_ID(iobj, concatarray) ||
IS_INSN_ID(iobj, splatarray) ||
0) {
/*
* newarray N
* splatarray
* =>
* newarray N
* newarray always puts an array
*/
LINK_ELEMENT *next = iobj->link.next;
if (IS_INSN(next) && IS_INSN_ID(next, splatarray)) {
/* remove splatarray following always-array insn */
ELEM_REMOVE(next);
}
}
if (IS_INSN_ID(iobj, tostring)) {
LINK_ELEMENT *next = iobj->link.next;
/*
* tostring
* concatstrings 1
* =>
* tostring
*/
if (IS_INSN(next) && IS_INSN_ID(next, concatstrings) &&
OPERAND_AT(next, 0) == INT2FIX(1)) {
ELEM_REMOVE(next);
}
}
if (IS_INSN_ID(iobj, putstring) ||
(IS_INSN_ID(iobj, putobject) && RB_TYPE_P(OPERAND_AT(iobj, 0), T_STRING))) {
/*
* putstring ""
* concatstrings N
* =>
* concatstrings N-1
*/
if (IS_NEXT_INSN_ID(&iobj->link, concatstrings) &&
RSTRING_LEN(OPERAND_AT(iobj, 0)) == 0) {
INSN *next = (INSN *)iobj->link.next;
if ((OPERAND_AT(next, 0) = FIXNUM_INC(OPERAND_AT(next, 0), -1)) == INT2FIX(1)) {
ELEM_REMOVE(&next->link);
}
ELEM_REMOVE(&iobj->link);
}
}
if (IS_INSN_ID(iobj, concatstrings)) {
/*
* concatstrings N
* concatstrings M
* =>
* concatstrings N+M-1
*/
LINK_ELEMENT *next = iobj->link.next, *freeze = 0;
INSN *jump = 0;
if (IS_INSN(next) && IS_INSN_ID(next, freezestring))
next = (freeze = next)->next;
if (IS_INSN(next) && IS_INSN_ID(next, jump))
next = get_destination_insn(jump = (INSN *)next);
if (IS_INSN(next) && IS_INSN_ID(next, concatstrings)) {
int n = FIX2INT(OPERAND_AT(iobj, 0)) + FIX2INT(OPERAND_AT(next, 0)) - 1;
OPERAND_AT(iobj, 0) = INT2FIX(n);
if (jump) {
LABEL *label = ((LABEL *)OPERAND_AT(jump, 0));
if (!--label->refcnt) {
ELEM_REMOVE(&label->link);
}
else {
label = NEW_LABEL(0);
OPERAND_AT(jump, 0) = (VALUE)label;
}
label->refcnt++;
if (freeze && IS_NEXT_INSN_ID(next, freezestring)) {
if (same_debug_pos_p(freeze, next->next)) {
ELEM_REMOVE(freeze);
}
else {
next = next->next;
}
}
ELEM_INSERT_NEXT(next, &label->link);
CHECK(iseq_peephole_optimize(iseq, get_next_insn(jump), do_tailcallopt));
}
else {
if (freeze) ELEM_REMOVE(freeze);
ELEM_REMOVE(next);
}
}
}
if (IS_INSN_ID(iobj, freezestring) &&
NIL_P(OPERAND_AT(iobj, 0)) &&
IS_NEXT_INSN_ID(&iobj->link, send)) {
INSN *niobj = (INSN *)iobj->link.next;
struct rb_call_info *ci = (struct rb_call_info *)OPERAND_AT(niobj, 0);
/*
* freezestring nil # no debug_info
* send <:+@, 0, ARG_SIMPLE> # :-@, too
* =>
* send <:+@, 0, ARG_SIMPLE> # :-@, too
*/
if ((ci->mid == idUPlus || ci->mid == idUMinus) &&
(ci->flag & VM_CALL_ARGS_SIMPLE) &&
ci->orig_argc == 0) {
ELEM_REMOVE(list);
return COMPILE_OK;
}
}
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):
next = next->next;
break;
case BIN(jump):
/* if cond
* return tailcall
* end
*/
next = get_destination_insn((INSN *)next);
break;
case BIN(leave):
piobj = iobj;
/* fall through */
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;
}
}
}
if (IS_INSN_ID(iobj, dup)) {
if (IS_NEXT_INSN_ID(&iobj->link, setlocal)) {
LINK_ELEMENT *set1 = iobj->link.next, *set2 = NULL;
if (IS_NEXT_INSN_ID(set1, setlocal)) {
set2 = set1->next;
if (OPERAND_AT(set1, 0) == OPERAND_AT(set2, 0) &&
OPERAND_AT(set1, 1) == OPERAND_AT(set2, 1)) {
ELEM_REMOVE(set1);
ELEM_REMOVE(&iobj->link);
}
}
else if (IS_NEXT_INSN_ID(set1, dup) &&
IS_NEXT_INSN_ID(set1->next, setlocal)) {
set2 = set1->next->next;
if (OPERAND_AT(set1, 0) == OPERAND_AT(set2, 0) &&
OPERAND_AT(set1, 1) == OPERAND_AT(set2, 1)) {
ELEM_REMOVE(set1->next);
ELEM_REMOVE(set2);
}
}
}
}
if (IS_INSN_ID(iobj, getlocal)) {
LINK_ELEMENT *niobj = &iobj->link;
if (IS_NEXT_INSN_ID(niobj, dup)) {
niobj = niobj->next;
}
if (IS_NEXT_INSN_ID(niobj, setlocal)) {
LINK_ELEMENT *set1 = niobj->next;
if (OPERAND_AT(iobj, 0) == OPERAND_AT(set1, 0) &&
OPERAND_AT(iobj, 1) == OPERAND_AT(set1, 1)) {
ELEM_REMOVE(set1);
ELEM_REMOVE(niobj);
}
}
}
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 = compile_data_alloc2(iseq, iobj->operand_size, sizeof(VALUE));
iobj->operands[0] = (VALUE)new_callinfo(iseq, idEq, 1, 0, NULL, FALSE);
iobj->operands[1] = Qfalse; /* CALL_CACHE */
iobj->operands[2] = old_operands[0];
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);
ELEM_REMOVE(&niobj->link);
return COMPILE_OK;
case idMin:
iobj->insn_id = BIN(opt_newarray_min);
ELEM_REMOVE(&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 idNilP: SP_INSN(nil_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 idEqTilde:SP_INSN(regexpmatch2);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;
case idAnd: SP_INSN(and); return COMPILE_OK;
case idOr: SP_INSN(or); 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_TOP:
case ISEQ_TYPE_EVAL:
case ISEQ_TYPE_MAIN:
/* not tail callable because cfp will be over popped */
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 *const 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 =
compile_data_alloc2(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->insn_info.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 *const 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, const LINK_ELEMENT *anchor, 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) {
BADINSN_DUMP(anchor, iobj, lobj);
COMPILE_ERROR(iseq, iobj->insn_info.line_no,
"insn_set_sc_state error: %d at "LABEL_FORMAT
", %d expected\n",
lobj->sc_state, lobj->label_no, nstate);
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 *const 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 */
ELEM_REPLACE(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);
ELEM_REMOVE(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->insn_info.line_no,
"unreachable");
return COMPILE_NG;
}
/* remove useless pop */
ELEM_REMOVE(list);
list = list->next;
goto redo_point;
}
default:;
/* none */
} /* end of switch */
normal_insn:
state = insn_set_sc_state(iseq, anchor, 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
all_string_result_p(const NODE *node)
{
if (!node) return FALSE;
switch (nd_type(node)) {
case NODE_STR: case NODE_DSTR:
return TRUE;
case NODE_IF: case NODE_UNLESS:
if (!node->nd_body || !node->nd_else) return FALSE;
if (all_string_result_p(node->nd_body))
return all_string_result_p(node->nd_else);
return FALSE;
case NODE_AND: case NODE_OR:
if (!node->nd_2nd)
return all_string_result_p(node->nd_1st);
if (!all_string_result_p(node->nd_1st))
return FALSE;
return all_string_result_p(node->nd_2nd);
default:
return FALSE;
}
}
static int
compile_dstr_fragments(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int *cntp)
{
const 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_ERROR(ERROR_ARGS "dstr: must be string: %s",
rb_builtin_type_name(TYPE(lit)));
return COMPILE_NG;
}
lit = rb_fstring(lit);
ADD_INSN1(ret, nd_line(node), putobject, lit);
RB_OBJ_WRITTEN(iseq, Qundef, lit);
if (RSTRING_LEN(lit) == 0) first_lit = LAST_ELEMENT(ret);
}
while (list) {
const NODE *const head = list->nd_head;
if (nd_type(head) == NODE_STR) {
lit = rb_fstring(head->nd_lit);
ADD_INSN1(ret, nd_line(head), putobject, lit);
RB_OBJ_WRITTEN(iseq, Qundef, lit);
lit = Qnil;
}
else {
CHECK(COMPILE(ret, "each string", head));
}
cnt++;
list = list->nd_next;
}
if (NIL_P(lit) && first_lit) {
ELEM_REMOVE(first_lit);
--cnt;
}
*cntp = cnt;
return COMPILE_OK;
}
static int
compile_dstr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node)
{
int cnt;
CHECK(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 *const ret, const NODE *const node)
{
int cnt;
CHECK(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_flip_flop(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int again,
LABEL *then_label, LABEL *else_label)
{
const int line = nd_line(node);
LABEL *lend = NEW_LABEL(line);
rb_num_t cnt = ISEQ_FLIP_CNT_INCREMENT(iseq->body->local_iseq)
+ VM_SVAR_FLIPFLOP_START;
VALUE key = INT2FIX(cnt);
ADD_INSN2(ret, line, getspecial, key, INT2FIX(0));
ADD_INSNL(ret, line, branchif, lend);
/* *flip == 0 */
CHECK(COMPILE(ret, "flip2 beg", node->nd_beg));
ADD_INSNL(ret, line, branchunless, else_label);
ADD_INSN1(ret, line, putobject, Qtrue);
ADD_INSN1(ret, line, setspecial, key);
if (!again) {
ADD_INSNL(ret, line, jump, then_label);
}
/* *flip == 1 */
ADD_LABEL(ret, lend);
CHECK(COMPILE(ret, "flip2 end", node->nd_end));
ADD_INSNL(ret, line, branchunless, then_label);
ADD_INSN1(ret, line, putobject, Qfalse);
ADD_INSN1(ret, line, setspecial, key);
ADD_INSNL(ret, line, jump, then_label);
return COMPILE_OK;
}
static int
compile_branch_condition(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *cond,
LABEL *then_label, LABEL *else_label)
{
again:
switch (nd_type(cond)) {
case NODE_AND:
{
LABEL *label = NEW_LABEL(nd_line(cond));
CHECK(compile_branch_condition(iseq, ret, cond->nd_1st, label,
else_label));
if (!label->refcnt) break;
ADD_LABEL(ret, label);
cond = cond->nd_2nd;
goto again;
}
case NODE_OR:
{
LABEL *label = NEW_LABEL(nd_line(cond));
CHECK(compile_branch_condition(iseq, ret, cond->nd_1st, then_label,
label));
if (!label->refcnt) break;
ADD_LABEL(ret, label);
cond = cond->nd_2nd;
goto again;
}
case NODE_LIT: /* NODE_LIT is always true */
case NODE_TRUE:
case NODE_STR:
case NODE_ZLIST:
case NODE_LAMBDA:
/* 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;
case NODE_LIST:
case NODE_ARGSCAT:
case NODE_DREGX:
case NODE_DSTR:
CHECK(COMPILE_POPPED(ret, "branch condition", cond));
ADD_INSNL(ret, nd_line(cond), jump, then_label);
break;
case NODE_FLIP2:
CHECK(compile_flip_flop(iseq, ret, cond, TRUE, then_label, else_label));
break;
case NODE_FLIP3:
CHECK(compile_flip_flop(iseq, ret, cond, FALSE, then_label, else_label));
break;
case NODE_DEFINED:
CHECK(compile_defined_expr(iseq, ret, cond, Qfalse));
goto branch;
default:
CHECK(COMPILE(ret, "branch condition", cond));
branch:
ADD_INSNL(ret, nd_line(cond), branchunless, else_label);
ADD_INSNL(ret, nd_line(cond), jump, then_label);
break;
}
return COMPILE_OK;
}
static int
keyword_node_p(const NODE *const node)
{
return nd_type(node) == NODE_HASH && node->nd_brace == FALSE;
}
static int
compile_keyword_arg(rb_iseq_t *iseq, LINK_ANCHOR *const ret,
const NODE *const root_node,
struct rb_call_info_kw_arg **const kw_arg_ptr,
unsigned int *flag)
{
if (kw_arg_ptr == NULL) return FALSE;
if (keyword_node_p(root_node) && root_node->nd_head && nd_type(root_node->nd_head) == NODE_LIST) {
const NODE *node = root_node->nd_head;
while (node) {
const NODE *key_node = node->nd_head;
assert(nd_type(node) == NODE_LIST);
if (!key_node) {
if (flag) *flag |= VM_CALL_KW_SPLAT;
return FALSE;
}
else if (nd_type(key_node) == NODE_LIT && RB_TYPE_P(key_node->nd_lit, T_SYMBOL)) {
/* can be keywords */
}
else {
if (flag) *flag |= VM_CALL_KW_SPLAT;
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 =
rb_xmalloc_mul_add(len - 1, sizeof(VALUE), sizeof(struct rb_call_info_kw_arg));
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) {
const NODE *key_node = node->nd_head;
const NODE *val_node = node->nd_next->nd_head;
keywords[i] = key_node->nd_lit;
NO_CHECK(COMPILE(ret, "keyword values", val_node));
}
assert(i == len);
return TRUE;
}
}
return FALSE;
}
static int
compile_args(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node,
struct rb_call_info_kw_arg **keywords_ptr, unsigned int *flag)
{
int len = 0;
for (; node; len++, node = node->nd_next) {
if (CPDEBUG > 0) {
EXPECT_NODE("compile_args", node, NODE_LIST, -1);
}
if (node->nd_next == NULL /* last node */ &&
compile_keyword_arg(iseq, ret, node->nd_head, keywords_ptr, flag)) {
len--;
}
else {
NO_CHECK(COMPILE_(ret, "array element", node->nd_head, FALSE));
}
}
return len;
}
static inline int
static_literal_node_p(const NODE *node, const rb_iseq_t *iseq)
{
node = node->nd_head;
switch (nd_type(node)) {
case NODE_LIT:
case NODE_NIL:
case NODE_TRUE:
case NODE_FALSE:
return TRUE;
case NODE_STR:
return ISEQ_COMPILE_DATA(iseq)->option->frozen_string_literal;
default:
return FALSE;
}
}
static inline VALUE
static_literal_value(const NODE *node, rb_iseq_t *iseq)
{
node = node->nd_head;
switch (nd_type(node)) {
case NODE_NIL:
return Qnil;
case NODE_TRUE:
return Qtrue;
case NODE_FALSE:
return Qfalse;
case NODE_STR:
if (ISEQ_COMPILE_DATA(iseq)->option->debug_frozen_string_literal || RTEST(ruby_debug)) {
VALUE lit;
VALUE debug_info = rb_ary_new_from_args(2, rb_iseq_path(iseq), INT2FIX((int)nd_line(node)));
lit = rb_str_dup(node->nd_lit);
rb_ivar_set(lit, id_debug_created_info, rb_obj_freeze(debug_info));
return rb_str_freeze(lit);
}
else {
return rb_fstring(node->nd_lit);
}
default:
return node->nd_lit;
}
}
static int
compile_array(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int popped)