mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
43ceedecc0
vm_invoke_builtin() accesses VM stack via cfp->sp. However, MJIT can use their own stack. To access them appropriately, we need to use STACK_ADDR_FROM_TOP().
1557 lines
31 KiB
C
1557 lines
31 KiB
C
/* -*- C -*-
|
|
insns.def - YARV instruction definitions
|
|
|
|
$Author: $
|
|
created at: 04/01/01 01:17:55 JST
|
|
|
|
Copyright (C) 2004-2007 Koichi Sasada
|
|
Massive rewrite by @shyouhei in 2017.
|
|
*/
|
|
|
|
/* Some comments about this file's contents:
|
|
|
|
- The new format aims to be editable by C editor of your choice;
|
|
your mileage might vary of course.
|
|
|
|
- Each instructions are in following format:
|
|
|
|
DEFINE_INSN
|
|
instruction_name
|
|
(type operand, type operand, ..)
|
|
(pop_values, ..)
|
|
(return values ..)
|
|
// attr type name contents..
|
|
{
|
|
.. // insn body
|
|
}
|
|
|
|
- Unlike the old format which was line-oriented, you can now place
|
|
newlines and comments at liberal positions.
|
|
|
|
- `DEFINE_INSN` is a keyword.
|
|
|
|
- An instruction name must be a valid C identifier.
|
|
|
|
- Operands, pop values, return values are series of either variable
|
|
declarations, keyword `void`, or keyword `...`. They are much
|
|
like C function declarations.
|
|
|
|
- Attribute pragmas are optional, and can include arbitrary C
|
|
expressions. You can write anything there but as of writing,
|
|
supported attributes are:
|
|
|
|
* sp_inc: Used to dynamically calculate sp increase in
|
|
`insn_stack_increase`.
|
|
|
|
* handles_sp: If it is true, VM deals with sp in the insn.
|
|
Default is if the instruction takes ISEQ operand or not.
|
|
|
|
* leaf: indicates that the instruction is "leaf" i.e. it does
|
|
not introduce new stack frame on top of it.
|
|
If an instruction handles sp, that can never be a leaf.
|
|
|
|
- Attributes can access operands, but not stack (push/pop) variables.
|
|
|
|
- An instruction's body is a pure C block, copied verbatimly into
|
|
the generated C source code.
|
|
*/
|
|
|
|
/* nop */
|
|
DEFINE_INSN
|
|
nop
|
|
()
|
|
()
|
|
()
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with variables */
|
|
/**********************************************************/
|
|
|
|
/* Get local variable (pointed by `idx' and `level').
|
|
'level' indicates the nesting depth from the current block.
|
|
*/
|
|
DEFINE_INSN
|
|
getlocal
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = *(vm_get_ep(GET_EP(), level) - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
|
|
/* Set a local variable (pointed to by 'idx') as val.
|
|
'level' indicates the nesting depth from the current block.
|
|
*/
|
|
DEFINE_INSN
|
|
setlocal
|
|
(lindex_t idx, rb_num_t level)
|
|
(VALUE val)
|
|
()
|
|
{
|
|
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
|
|
RB_DEBUG_COUNTER_INC(lvar_set);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
|
|
}
|
|
|
|
/* Get a block parameter. */
|
|
DEFINE_INSN
|
|
getblockparam
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
|
|
val = rb_vm_bh_to_procval(ec, VM_ENV_BLOCK_HANDLER(ep));
|
|
vm_env_write(ep, -(int)idx, val);
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
else {
|
|
val = *(ep - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
}
|
|
|
|
/* Set block parameter. */
|
|
DEFINE_INSN
|
|
setblockparam
|
|
(lindex_t idx, rb_num_t level)
|
|
(VALUE val)
|
|
()
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
vm_env_write(ep, -(int)idx, val);
|
|
RB_DEBUG_COUNTER_INC(lvar_set);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
|
|
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
|
|
/* Get special proxy object which only responds to `call` method if the block parameter
|
|
represents a iseq/ifunc block. Otherwise, same as `getblockparam`.
|
|
*/
|
|
DEFINE_INSN
|
|
getblockparamproxy
|
|
(lindex_t idx, rb_num_t level)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
const VALUE *ep = vm_get_ep(GET_EP(), level);
|
|
VM_ASSERT(VM_ENV_LOCAL_P(ep));
|
|
|
|
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
|
|
VALUE block_handler = VM_ENV_BLOCK_HANDLER(ep);
|
|
|
|
if (block_handler) {
|
|
switch (vm_block_handler_type(block_handler)) {
|
|
case block_handler_type_iseq:
|
|
case block_handler_type_ifunc:
|
|
val = rb_block_param_proxy;
|
|
break;
|
|
case block_handler_type_symbol:
|
|
val = rb_sym_to_proc(VM_BH_TO_SYMBOL(block_handler));
|
|
goto INSN_LABEL(set);
|
|
case block_handler_type_proc:
|
|
val = VM_BH_TO_PROC(block_handler);
|
|
goto INSN_LABEL(set);
|
|
default:
|
|
VM_UNREACHABLE(getblockparamproxy);
|
|
}
|
|
}
|
|
else {
|
|
val = Qnil;
|
|
INSN_LABEL(set):
|
|
vm_env_write(ep, -(int)idx, val);
|
|
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
|
|
}
|
|
}
|
|
else {
|
|
val = *(ep - idx);
|
|
RB_DEBUG_COUNTER_INC(lvar_get);
|
|
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
|
|
}
|
|
}
|
|
|
|
/* Get value of special local variable ($~, $_, ..). */
|
|
DEFINE_INSN
|
|
getspecial
|
|
(rb_num_t key, rb_num_t type)
|
|
()
|
|
(VALUE val)
|
|
/* `$~ = MatchData.allocate; $&` can raise. */
|
|
// attr bool leaf = (type == 0) ? true : false;
|
|
{
|
|
val = vm_getspecial(ec, GET_LEP(), key, type);
|
|
}
|
|
|
|
/* Set value of special local variable ($~, $_, ...) to obj. */
|
|
DEFINE_INSN
|
|
setspecial
|
|
(rb_num_t key)
|
|
(VALUE obj)
|
|
()
|
|
{
|
|
lep_svar_set(ec, GET_LEP(), key, obj);
|
|
}
|
|
|
|
/* Get value of instance variable id of self. */
|
|
DEFINE_INSN
|
|
getinstancevariable
|
|
(ID id, IC ic)
|
|
()
|
|
(VALUE val)
|
|
/* "instance variable not initialized" warning can be hooked. */
|
|
// attr bool leaf = false; /* has rb_warning() */
|
|
{
|
|
val = vm_getinstancevariable(GET_SELF(), id, ic);
|
|
}
|
|
|
|
/* Set value of instance variable id of self to val. */
|
|
DEFINE_INSN
|
|
setinstancevariable
|
|
(ID id, IC ic)
|
|
(VALUE val)
|
|
()
|
|
// attr bool leaf = false; /* has rb_check_frozen_internal() */
|
|
{
|
|
vm_setinstancevariable(GET_SELF(), id, val, ic);
|
|
}
|
|
|
|
/* Get value of class variable id of klass as val. */
|
|
DEFINE_INSN
|
|
getclassvariable
|
|
(ID id)
|
|
()
|
|
(VALUE val)
|
|
/* "class variable access from toplevel" warning can be hooked. */
|
|
// attr bool leaf = false; /* has rb_warning() */
|
|
{
|
|
val = rb_cvar_get(vm_get_cvar_base(vm_get_cref(GET_EP()), GET_CFP()), id);
|
|
}
|
|
|
|
/* Set value of class variable id of klass as val. */
|
|
DEFINE_INSN
|
|
setclassvariable
|
|
(ID id)
|
|
(VALUE val)
|
|
()
|
|
/* "class variable access from toplevel" warning can be hooked. */
|
|
// attr bool leaf = false; /* has rb_warning() */
|
|
{
|
|
vm_ensure_not_refinement_module(GET_SELF());
|
|
rb_cvar_set(vm_get_cvar_base(vm_get_cref(GET_EP()), GET_CFP()), id, val);
|
|
}
|
|
|
|
/* Get constant variable id. If klass is Qnil and allow_nil is Qtrue, constants
|
|
are searched in the current scope. Otherwise, get constant under klass
|
|
class or module.
|
|
*/
|
|
DEFINE_INSN
|
|
getconstant
|
|
(ID id)
|
|
(VALUE klass, VALUE allow_nil)
|
|
(VALUE val)
|
|
/* getconstant can kick autoload */
|
|
// attr bool leaf = false; /* has rb_autoload_load() */
|
|
{
|
|
val = vm_get_ev_const(ec, klass, id, allow_nil == Qtrue, 0);
|
|
}
|
|
|
|
/* Set constant variable id under cbase class or module.
|
|
*/
|
|
DEFINE_INSN
|
|
setconstant
|
|
(ID id)
|
|
(VALUE val, VALUE cbase)
|
|
()
|
|
/* Assigning an object to a constant is basically a leaf operation.
|
|
* The problem is, assigning a Module instance to a constant _names_
|
|
* that module. Naming involves string manipulations, which are
|
|
* method calls. */
|
|
// attr bool leaf = false; /* has StringValue() */
|
|
{
|
|
vm_check_if_namespace(cbase);
|
|
vm_ensure_not_refinement_module(GET_SELF());
|
|
rb_const_set(cbase, id, val);
|
|
}
|
|
|
|
/* get global variable id. */
|
|
DEFINE_INSN
|
|
getglobal
|
|
(GENTRY entry)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = leafness_of_getglobal(entry);
|
|
{
|
|
struct rb_global_entry *gentry = (void *)entry;
|
|
val = rb_gvar_get(gentry);
|
|
}
|
|
|
|
/* set global variable id as val. */
|
|
DEFINE_INSN
|
|
setglobal
|
|
(GENTRY entry)
|
|
(VALUE val)
|
|
()
|
|
// attr bool leaf = leafness_of_setglobal(entry);
|
|
{
|
|
struct rb_global_entry *gentry = (void *)entry;
|
|
rb_gvar_set(gentry, val);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with values */
|
|
/**********************************************************/
|
|
|
|
/* put nil to stack. */
|
|
DEFINE_INSN
|
|
putnil
|
|
()
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = Qnil;
|
|
}
|
|
|
|
/* put self. */
|
|
DEFINE_INSN
|
|
putself
|
|
()
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = GET_SELF();
|
|
}
|
|
|
|
/* put some object.
|
|
i.e. Fixnum, true, false, nil, and so on.
|
|
*/
|
|
DEFINE_INSN
|
|
putobject
|
|
(VALUE val)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
/* */
|
|
}
|
|
|
|
/* put special object. "value_type" is for expansion. */
|
|
DEFINE_INSN
|
|
putspecialobject
|
|
(rb_num_t value_type)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
enum vm_special_object_type type;
|
|
|
|
type = (enum vm_special_object_type)value_type;
|
|
val = vm_get_special_object(GET_EP(), type);
|
|
}
|
|
|
|
/* put string val. string will be copied. */
|
|
DEFINE_INSN
|
|
putstring
|
|
(VALUE str)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = rb_str_resurrect(str);
|
|
}
|
|
|
|
/* put concatenate strings */
|
|
DEFINE_INSN
|
|
concatstrings
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction can concat UTF-8 and binary strings, resulting in
|
|
* Encoding::CompatiblityError. */
|
|
// attr bool leaf = false; /* has rb_enc_cr_str_buf_cat() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = rb_str_concat_literals(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
/* push the result of to_s. */
|
|
DEFINE_INSN
|
|
tostring
|
|
()
|
|
(VALUE val, VALUE str)
|
|
(VALUE val)
|
|
{
|
|
val = rb_obj_as_string_result(str, val);
|
|
}
|
|
|
|
/* Freeze (dynamically) created strings. if debug_info is given, set it. */
|
|
DEFINE_INSN
|
|
freezestring
|
|
(VALUE debug_info)
|
|
(VALUE str)
|
|
(VALUE str)
|
|
{
|
|
vm_freezestring(str, debug_info);
|
|
}
|
|
|
|
/* compile str to Regexp and push it.
|
|
opt is the option for the Regexp.
|
|
*/
|
|
DEFINE_INSN
|
|
toregexp
|
|
(rb_num_t opt, rb_num_t cnt)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction can raise RegexpError, thus can call
|
|
* RegexpError#initialize */
|
|
// attr bool leaf = false;
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)cnt;
|
|
{
|
|
const VALUE ary = rb_ary_tmp_new_from_values(0, cnt, STACK_ADDR_FROM_TOP(cnt));
|
|
val = rb_reg_new_ary(ary, (int)opt);
|
|
rb_ary_clear(ary);
|
|
}
|
|
|
|
/* intern str to Symbol and push it. */
|
|
DEFINE_INSN
|
|
intern
|
|
()
|
|
(VALUE str)
|
|
(VALUE sym)
|
|
{
|
|
sym = rb_str_intern(str);
|
|
}
|
|
|
|
/* put new array initialized with num values on the stack. */
|
|
DEFINE_INSN
|
|
newarray
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = rb_ary_new4(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
/* put new array initialized with num values on the stack. There
|
|
should be at least one element on the stack, and the top element
|
|
should be a hash. If the top element is empty, it is not
|
|
included in the array.
|
|
*/
|
|
DEFINE_INSN
|
|
newarraykwsplat
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
if (RHASH_EMPTY_P(*STACK_ADDR_FROM_TOP(1))) {
|
|
val = rb_ary_new4(num-1, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
else {
|
|
val = rb_ary_new4(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
}
|
|
|
|
/* dup array */
|
|
DEFINE_INSN
|
|
duparray
|
|
(VALUE ary)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary));
|
|
val = rb_ary_resurrect(ary);
|
|
}
|
|
|
|
/* dup hash */
|
|
DEFINE_INSN
|
|
duphash
|
|
(VALUE hash)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1);
|
|
val = rb_hash_resurrect(hash);
|
|
}
|
|
|
|
/* if TOS is an array expand, expand it to num objects.
|
|
if the number of the array is less than num, push nils to fill.
|
|
if it is greater than num, exceeding elements are dropped.
|
|
unless TOS is an array, push num - 1 nils.
|
|
if flags is non-zero, push the array of the rest elements.
|
|
flag: 0x01 - rest args array
|
|
flag: 0x02 - for postarg
|
|
flag: 0x04 - reverse?
|
|
*/
|
|
DEFINE_INSN
|
|
expandarray
|
|
(rb_num_t num, rb_num_t flag)
|
|
(..., VALUE ary)
|
|
(...)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
// attr rb_snum_t sp_inc = (rb_snum_t)num - 1 + (flag & 1 ? 1 : 0);
|
|
{
|
|
vm_expandarray(GET_SP(), ary, num, (int)flag);
|
|
}
|
|
|
|
/* concat two arrays */
|
|
DEFINE_INSN
|
|
concatarray
|
|
()
|
|
(VALUE ary1, VALUE ary2)
|
|
(VALUE ary)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
{
|
|
ary = vm_concat_array(ary1, ary2);
|
|
}
|
|
|
|
/* call to_a on array ary to splat */
|
|
DEFINE_INSN
|
|
splatarray
|
|
(VALUE flag)
|
|
(VALUE ary)
|
|
(VALUE obj)
|
|
// attr bool leaf = false; /* has rb_check_array_type() */
|
|
{
|
|
obj = vm_splat_array(flag, ary);
|
|
}
|
|
|
|
/* put new Hash from n elements. n must be an even number. */
|
|
DEFINE_INSN
|
|
newhash
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* has rb_hash_key_str() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
RUBY_DTRACE_CREATE_HOOK(HASH, num);
|
|
|
|
val = rb_hash_new_with_size(num / 2);
|
|
|
|
if (num) {
|
|
rb_hash_bulk_insert(num, STACK_ADDR_FROM_TOP(num), val);
|
|
}
|
|
}
|
|
|
|
/* put new Range object.(Range.new(low, high, flag)) */
|
|
DEFINE_INSN
|
|
newrange
|
|
(rb_num_t flag)
|
|
(VALUE low, VALUE high)
|
|
(VALUE val)
|
|
/* rb_range_new() exercises "bad value for range" check. */
|
|
// attr bool leaf = false; /* see also: range.c:range_init() */
|
|
{
|
|
val = rb_range_new(low, high, (int)flag);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with stack operation */
|
|
/**********************************************************/
|
|
|
|
/* pop from stack. */
|
|
DEFINE_INSN
|
|
pop
|
|
()
|
|
(VALUE val)
|
|
()
|
|
{
|
|
(void)val;
|
|
/* none */
|
|
}
|
|
|
|
/* duplicate stack top. */
|
|
DEFINE_INSN
|
|
dup
|
|
()
|
|
(VALUE val)
|
|
(VALUE val1, VALUE val2)
|
|
{
|
|
val1 = val2 = val;
|
|
}
|
|
|
|
/* duplicate stack top n elements */
|
|
DEFINE_INSN
|
|
dupn
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = n;
|
|
{
|
|
void *dst = GET_SP();
|
|
void *src = STACK_ADDR_FROM_TOP(n);
|
|
|
|
MEMCPY(dst, src, VALUE, n);
|
|
}
|
|
|
|
/* swap top 2 vals */
|
|
DEFINE_INSN
|
|
swap
|
|
()
|
|
(VALUE val, VALUE obj)
|
|
(VALUE obj, VALUE val)
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/* reverse stack top N order. */
|
|
DEFINE_INSN
|
|
reverse
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
rb_num_t i;
|
|
VALUE *sp = STACK_ADDR_FROM_TOP(n);
|
|
|
|
for (i=0; i<n/2; i++) {
|
|
VALUE v0 = sp[i];
|
|
VALUE v1 = TOPN(i);
|
|
sp[i] = v1;
|
|
TOPN(i) = v0;
|
|
}
|
|
}
|
|
|
|
/* for stack caching. */
|
|
DEFINE_INSN_IF(STACK_CACHING)
|
|
reput
|
|
()
|
|
(..., VALUE val)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/* get nth stack value from stack top */
|
|
DEFINE_INSN
|
|
topn
|
|
(rb_num_t n)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 1;
|
|
{
|
|
val = TOPN(n);
|
|
}
|
|
|
|
/* set Nth stack entry to stack top */
|
|
DEFINE_INSN
|
|
setn
|
|
(rb_num_t n)
|
|
(..., VALUE val)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = 0;
|
|
{
|
|
TOPN(n) = val;
|
|
}
|
|
|
|
/* empty current stack */
|
|
DEFINE_INSN
|
|
adjuststack
|
|
(rb_num_t n)
|
|
(...)
|
|
(...)
|
|
// attr rb_snum_t sp_inc = -(rb_snum_t)n;
|
|
{
|
|
/* none */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with setting */
|
|
/**********************************************************/
|
|
|
|
/* defined? */
|
|
DEFINE_INSN
|
|
defined
|
|
(rb_num_t op_type, VALUE obj, VALUE needstr)
|
|
(VALUE v)
|
|
(VALUE val)
|
|
// attr bool leaf = leafness_of_defined(op_type);
|
|
{
|
|
val = vm_defined(ec, GET_CFP(), op_type, obj, needstr, v);
|
|
}
|
|
|
|
/* check `target' matches `pattern'.
|
|
`flag & VM_CHECKMATCH_TYPE_MASK' describe how to check pattern.
|
|
VM_CHECKMATCH_TYPE_WHEN: ignore target and check pattern is truthy.
|
|
VM_CHECKMATCH_TYPE_CASE: check `patten === target'.
|
|
VM_CHECKMATCH_TYPE_RESCUE: check `pattern.kind_op?(Module) && pattern === target'.
|
|
if `flag & VM_CHECKMATCH_ARRAY' is not 0, then `patten' is array of patterns.
|
|
*/
|
|
DEFINE_INSN
|
|
checkmatch
|
|
(rb_num_t flag)
|
|
(VALUE target, VALUE pattern)
|
|
(VALUE result)
|
|
// attr bool leaf = leafness_of_checkmatch(flag);
|
|
{
|
|
result = vm_check_match(ec, target, pattern, flag);
|
|
}
|
|
|
|
/* check keywords are specified or not. */
|
|
DEFINE_INSN
|
|
checkkeyword
|
|
(lindex_t kw_bits_index, lindex_t keyword_index)
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = vm_check_keyword(kw_bits_index, keyword_index, GET_EP());
|
|
}
|
|
|
|
/* check if val is type. */
|
|
DEFINE_INSN
|
|
checktype
|
|
(rb_num_t type)
|
|
(VALUE val)
|
|
(VALUE ret)
|
|
{
|
|
ret = (TYPE(val) == (int)type) ? Qtrue : Qfalse;
|
|
}
|
|
|
|
/* get frozen method reference. */
|
|
DEFINE_INSN
|
|
methodref
|
|
(ID id)
|
|
(VALUE val)
|
|
(VALUE ret)
|
|
{
|
|
ret = rb_obj_method(val, ID2SYM(id));
|
|
RB_OBJ_FREEZE_RAW(ret);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 1: class/module */
|
|
/**********************************************************/
|
|
|
|
/* enter class definition scope. if super is Qfalse, and class
|
|
"klass" is defined, it's redefine. otherwise, define "klass" class.
|
|
*/
|
|
DEFINE_INSN
|
|
defineclass
|
|
(ID id, ISEQ class_iseq, rb_num_t flags)
|
|
(VALUE cbase, VALUE super)
|
|
(VALUE val)
|
|
{
|
|
VALUE klass = vm_find_or_create_class_by_id(id, flags, cbase, super);
|
|
|
|
rb_iseq_check(class_iseq);
|
|
|
|
/* enter scope */
|
|
vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass,
|
|
GET_BLOCK_HANDLER(),
|
|
(VALUE)vm_cref_push(ec, klass, NULL, FALSE),
|
|
class_iseq->body->iseq_encoded, GET_SP(),
|
|
class_iseq->body->local_table_size,
|
|
class_iseq->body->stack_max);
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
|
|
DEFINE_INSN
|
|
definemethod
|
|
(ID id, ISEQ iseq)
|
|
()
|
|
()
|
|
{
|
|
vm_define_method(ec, Qnil, id, (VALUE)iseq, FALSE);
|
|
}
|
|
|
|
DEFINE_INSN
|
|
definesmethod
|
|
(ID id, ISEQ iseq)
|
|
(VALUE obj)
|
|
()
|
|
{
|
|
vm_define_method(ec, obj, id, (VALUE)iseq, TRUE);
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 2: method/iterator */
|
|
/**********************************************************/
|
|
|
|
/* invoke method. */
|
|
DEFINE_INSN
|
|
send
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(&cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), &cd->ci, blockiseq, false);
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, vm_search_method_wrap);
|
|
|
|
if (val == Qundef) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* Invoke method without block */
|
|
DEFINE_INSN
|
|
opt_send_without_block
|
|
(CALL_DATA cd)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool handles_sp = true;
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(&cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = VM_BLOCK_HANDLER_NONE;
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, vm_search_method_wrap);
|
|
|
|
if (val == Qundef) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_str_freeze
|
|
(VALUE str, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_str_freeze(str, BOP_FREEZE, idFreeze);
|
|
|
|
if (val == Qundef) {
|
|
PUSH(rb_str_resurrect(str));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized nil? */
|
|
DEFINE_INSN
|
|
opt_nil_p
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_nil_p(cd, recv);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_str_uminus
|
|
(VALUE str, CALL_DATA cd)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_str_freeze(str, BOP_UMINUS, idUMinus);
|
|
|
|
if (val == Qundef) {
|
|
PUSH(rb_str_resurrect(str));
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_newarray_max
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
/* This instruction typically has no funcalls. But it compares array
|
|
* contents each other by nature. That part could call methods when
|
|
* necessary. No way to detect such method calls beforehand. We
|
|
* cannot but mark it being not leaf. */
|
|
// attr bool leaf = false; /* has rb_funcall() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = vm_opt_newarray_max(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
DEFINE_INSN
|
|
opt_newarray_min
|
|
(rb_num_t num)
|
|
(...)
|
|
(VALUE val)
|
|
/* Same discussion as opt_newarray_max. */
|
|
// attr bool leaf = false; /* has rb_funcall() */
|
|
// attr rb_snum_t sp_inc = 1 - (rb_snum_t)num;
|
|
{
|
|
val = vm_opt_newarray_min(num, STACK_ADDR_FROM_TOP(num));
|
|
}
|
|
|
|
/* super(args) # args.size => num */
|
|
DEFINE_INSN
|
|
invokesuper
|
|
(CALL_DATA cd, ISEQ blockiseq)
|
|
(...)
|
|
(VALUE val)
|
|
// attr rb_snum_t sp_inc = sp_inc_of_sendish(&cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
|
|
{
|
|
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), &cd->ci, blockiseq, true);
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, vm_search_super_method);
|
|
|
|
if (val == Qundef) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* yield(args) */
|
|
DEFINE_INSN
|
|
invokeblock
|
|
(CALL_DATA cd)
|
|
(...)
|
|
(VALUE val)
|
|
// attr bool handles_sp = true;
|
|
// attr rb_snum_t sp_inc = sp_inc_of_invokeblock(&cd->ci);
|
|
// attr rb_snum_t comptime_sp_inc = sp_inc_of_invokeblock(ci);
|
|
{
|
|
if (UNLIKELY(cd->cc.call != vm_invokeblock_i)) {
|
|
cd->cc.call = vm_invokeblock_i; // check before setting to avoid CoW
|
|
}
|
|
|
|
VALUE bh = VM_BLOCK_HANDLER_NONE;
|
|
val = vm_sendish(ec, GET_CFP(), cd, bh, vm_search_invokeblock);
|
|
|
|
if (val == Qundef) {
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
}
|
|
|
|
/* return from this scope. */
|
|
DEFINE_INSN
|
|
leave
|
|
()
|
|
(VALUE val)
|
|
(VALUE val)
|
|
/* This is super surprising but when leaving from a frame, we check
|
|
* for interrupts. If any, that should be executed on top of the
|
|
* current execution context. This is a method call. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
// attr bool handles_sp = true;
|
|
{
|
|
if (OPT_CHECKED_RUN) {
|
|
const VALUE *const bp = vm_base_ptr(GET_CFP());
|
|
if (GET_SP() != bp) {
|
|
vm_stack_consistency_error(ec, GET_CFP(), bp);
|
|
}
|
|
}
|
|
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
|
|
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
|
|
#if OPT_CALL_THREADED_CODE
|
|
rb_ec_thread_ptr(ec)->retval = val;
|
|
return 0;
|
|
#else
|
|
return val;
|
|
#endif
|
|
}
|
|
else {
|
|
RESTORE_REGS();
|
|
}
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 3: exception */
|
|
/**********************************************************/
|
|
|
|
/* longjump */
|
|
DEFINE_INSN
|
|
throw
|
|
(rb_num_t throw_state)
|
|
(VALUE throwobj)
|
|
(VALUE val)
|
|
/* Same discussion as leave. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
val = vm_throw(ec, GET_CFP(), throw_state, throwobj);
|
|
THROW_EXCEPTION(val);
|
|
/* unreachable */
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* deal with control flow 4: local jump */
|
|
/**********************************************************/
|
|
|
|
/* set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
jump
|
|
(OFFSET dst)
|
|
()
|
|
()
|
|
/* Same discussion as leave. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
|
|
/* if val is not false or nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchif
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (RTEST(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/* if val is false or nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchunless
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (!RTEST(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/* if val is nil, set PC to (PC + dst). */
|
|
DEFINE_INSN
|
|
branchnil
|
|
(OFFSET dst)
|
|
(VALUE val)
|
|
()
|
|
/* Same discussion as jump. */
|
|
// attr bool leaf = false; /* has rb_threadptr_execute_interrupts() */
|
|
{
|
|
if (NIL_P(val)) {
|
|
RUBY_VM_CHECK_INTS(ec);
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/**********************************************************/
|
|
/* for optimize */
|
|
/**********************************************************/
|
|
|
|
/* push inline-cached value and go to dst if it is valid */
|
|
DEFINE_INSN
|
|
opt_getinlinecache
|
|
(OFFSET dst, IC ic)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
if (vm_ic_hit_p(ic, GET_EP())) {
|
|
val = ic->ic_value.value;
|
|
JUMP(dst);
|
|
}
|
|
else {
|
|
val = Qnil;
|
|
}
|
|
}
|
|
|
|
/* set inline cache */
|
|
DEFINE_INSN
|
|
opt_setinlinecache
|
|
(IC ic)
|
|
(VALUE val)
|
|
(VALUE val)
|
|
{
|
|
vm_ic_update(ic, val, GET_EP());
|
|
}
|
|
|
|
/* run iseq only once */
|
|
DEFINE_INSN
|
|
once
|
|
(ISEQ iseq, ISE ise)
|
|
()
|
|
(VALUE val)
|
|
{
|
|
val = vm_once_dispatch(ec, iseq, ise);
|
|
}
|
|
|
|
/* case dispatcher, jump by table if possible */
|
|
DEFINE_INSN
|
|
opt_case_dispatch
|
|
(CDHASH hash, OFFSET else_offset)
|
|
(..., VALUE key)
|
|
()
|
|
// attr rb_snum_t sp_inc = -1;
|
|
{
|
|
OFFSET dst = vm_case_dispatch(hash, else_offset, key);
|
|
|
|
if (dst) {
|
|
JUMP(dst);
|
|
}
|
|
}
|
|
|
|
/** simple functions */
|
|
|
|
/* optimized X+Y. */
|
|
DEFINE_INSN
|
|
opt_plus
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_plus(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X-Y. */
|
|
DEFINE_INSN
|
|
opt_minus
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_minus(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X*Y. */
|
|
DEFINE_INSN
|
|
opt_mult
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_mult(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X/Y. */
|
|
DEFINE_INSN
|
|
opt_div
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* In case of division by zero, it raises. Thus
|
|
* ZeroDivisionError#initialize is called. */
|
|
// attr bool leaf = false;
|
|
{
|
|
val = vm_opt_div(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X%Y. */
|
|
DEFINE_INSN
|
|
opt_mod
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* Same discussion as opt_mod. */
|
|
// attr bool leaf = false;
|
|
{
|
|
val = vm_opt_mod(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X==Y. */
|
|
DEFINE_INSN
|
|
opt_eq
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = opt_eq_func(recv, obj, cd);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X!=Y. */
|
|
DEFINE_INSN
|
|
opt_neq
|
|
(CALL_DATA cd_eq, CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_neq(cd, cd_eq, recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X<Y. */
|
|
DEFINE_INSN
|
|
opt_lt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_lt(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X<=Y. */
|
|
DEFINE_INSN
|
|
opt_le
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_le(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X>Y. */
|
|
DEFINE_INSN
|
|
opt_gt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_gt(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X>=Y. */
|
|
DEFINE_INSN
|
|
opt_ge
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_ge(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* << */
|
|
DEFINE_INSN
|
|
opt_ltlt
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* This instruction can append an integer, as a codepoint, into a
|
|
* string. Then what happens if that codepoint does not exist in the
|
|
* string's encoding? Of course an exception. That's not a leaf. */
|
|
// attr bool leaf = false; /* has "invalid codepoint" exception */
|
|
{
|
|
val = vm_opt_ltlt(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X&Y. */
|
|
DEFINE_INSN
|
|
opt_and
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_and(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized X|Y. */
|
|
DEFINE_INSN
|
|
opt_or
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_or(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* [] */
|
|
DEFINE_INSN
|
|
opt_aref
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj)
|
|
(VALUE val)
|
|
/* This is complicated. In case of hash, vm_opt_aref() resorts to
|
|
* rb_hash_aref(). If `recv` has no `obj`, this function then yields
|
|
* default_proc. This is a method call. So opt_aref is
|
|
* (surprisingly) not leaf. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */
|
|
{
|
|
val = vm_opt_aref(recv, obj);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* recv[obj] = set */
|
|
DEFINE_INSN
|
|
opt_aset
|
|
(CALL_DATA cd)
|
|
(VALUE recv, VALUE obj, VALUE set)
|
|
(VALUE val)
|
|
/* This is another story than opt_aref. When vm_opt_aset() resorts
|
|
* to rb_hash_aset(), which should call #hash for `obj`. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #hash */
|
|
{
|
|
val = vm_opt_aset(recv, obj, set);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* recv[str] = set */
|
|
DEFINE_INSN
|
|
opt_aset_with
|
|
(VALUE key, CALL_DATA cd)
|
|
(VALUE recv, VALUE val)
|
|
(VALUE val)
|
|
/* Same discussion as opt_aset. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #hash */
|
|
{
|
|
VALUE tmp = vm_opt_aset_with(recv, key, val);
|
|
|
|
if (tmp != Qundef) {
|
|
val = tmp;
|
|
}
|
|
else {
|
|
#ifndef MJIT_HEADER
|
|
TOPN(0) = rb_str_resurrect(key);
|
|
PUSH(val);
|
|
#endif
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* recv[str] */
|
|
DEFINE_INSN
|
|
opt_aref_with
|
|
(VALUE key, CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
/* Same discussion as opt_aref. */
|
|
// attr bool leaf = false; /* has rb_funcall() */ /* calls #yield */
|
|
{
|
|
val = vm_opt_aref_with(recv, key);
|
|
|
|
if (val == Qundef) {
|
|
#ifndef MJIT_HEADER
|
|
PUSH(rb_str_resurrect(key));
|
|
#endif
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized length */
|
|
DEFINE_INSN
|
|
opt_length
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_length(recv, BOP_LENGTH);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized size */
|
|
DEFINE_INSN
|
|
opt_size
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_length(recv, BOP_SIZE);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized empty? */
|
|
DEFINE_INSN
|
|
opt_empty_p
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_empty_p(recv);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized succ */
|
|
DEFINE_INSN
|
|
opt_succ
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_succ(recv);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized not */
|
|
DEFINE_INSN
|
|
opt_not
|
|
(CALL_DATA cd)
|
|
(VALUE recv)
|
|
(VALUE val)
|
|
{
|
|
val = vm_opt_not(cd, recv);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* optimized regexp match 2 */
|
|
DEFINE_INSN
|
|
opt_regexpmatch2
|
|
(CALL_DATA cd)
|
|
(VALUE obj2, VALUE obj1)
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* match_at() has rb_thread_check_ints() */
|
|
{
|
|
val = vm_opt_regexpmatch2(obj2, obj1);
|
|
|
|
if (val == Qundef) {
|
|
CALL_SIMPLE_METHOD();
|
|
}
|
|
}
|
|
|
|
/* call native compiled method */
|
|
DEFINE_INSN
|
|
opt_call_c_function
|
|
(rb_insn_func_t funcptr)
|
|
()
|
|
()
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
// attr bool handles_sp = true;
|
|
{
|
|
reg_cfp = (funcptr)(ec, reg_cfp);
|
|
|
|
if (reg_cfp == 0) {
|
|
VALUE err = ec->errinfo;
|
|
ec->errinfo = Qnil;
|
|
THROW_EXCEPTION(err);
|
|
}
|
|
|
|
RESTORE_REGS();
|
|
NEXT_INSN();
|
|
}
|
|
|
|
/* call specific function with args */
|
|
DEFINE_INSN
|
|
invokebuiltin
|
|
(RB_BUILTIN bf)
|
|
(...)
|
|
(VALUE ret)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
// attr rb_snum_t sp_inc = 1 - bf->argc;
|
|
{
|
|
ret = vm_invoke_builtin(ec, reg_cfp, bf, STACK_ADDR_FROM_TOP(bf->argc));
|
|
}
|
|
|
|
/* call specific function with args (same parameters) */
|
|
DEFINE_INSN
|
|
opt_invokebuiltin_delegate
|
|
(RB_BUILTIN bf)
|
|
()
|
|
(VALUE ret)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
{
|
|
ret = vm_invoke_builtin_delegate(ec, reg_cfp, bf);
|
|
}
|
|
|
|
/* call specific function with args (same parameters) and leave */
|
|
DEFINE_INSN
|
|
opt_invokebuiltin_delegate_leave
|
|
(RB_BUILTIN bf)
|
|
()
|
|
(VALUE val)
|
|
// attr bool leaf = false; /* anything can happen inside */
|
|
{
|
|
val = vm_invoke_builtin_delegate(ec, reg_cfp, bf);
|
|
|
|
/* leave fastpath */
|
|
/* TracePoint/return should fallback this insn to invokecfuncwparam */
|
|
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
|
|
#if OPT_CALL_THREADED_CODE
|
|
rb_ec_thread_ptr(ec)->retval = val;
|
|
return 0;
|
|
#else
|
|
return val;
|
|
#endif
|
|
}
|
|
else {
|
|
RESTORE_REGS();
|
|
}
|
|
}
|
|
|
|
/* BLT */
|
|
DEFINE_INSN_IF(SUPPORT_JOKE)
|
|
bitblt
|
|
()
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = rb_str_new2("a bit of bacon, lettuce and tomato");
|
|
}
|
|
|
|
/* The Answer to Life, the Universe, and Everything */
|
|
DEFINE_INSN_IF(SUPPORT_JOKE)
|
|
answer
|
|
()
|
|
()
|
|
(VALUE ret)
|
|
{
|
|
ret = INT2FIX(42);
|
|
}
|