summaryrefslogtreecommitdiff
path: root/insns.def
diff options
context:
space:
mode:
Diffstat (limited to 'insns.def')
-rw-r--r--insns.def1782
1 files changed, 1782 insertions, 0 deletions
diff --git a/insns.def b/insns.def
new file mode 100644
index 0000000000..3ad378081a
--- /dev/null
+++ b/insns.def
@@ -0,0 +1,1782 @@
+/* -*- 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)
+// attr bool zjit_profile = true;
+{
+ 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, IVC ic)
+()
+(VALUE val)
+/* Ractor crashes when it accesses class/module-level instances variables. */
+// attr bool leaf = false; /* has IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR() */
+// attr bool zjit_profile = true;
+{
+ val = vm_getinstancevariable(GET_ISEQ(), GET_SELF(), id, ic);
+}
+
+/* Set value of instance variable id of self to val. */
+DEFINE_INSN
+setinstancevariable
+(ID id, IVC ic)
+(VALUE val)
+()
+// attr bool leaf = false; /* has rb_check_frozen() */
+// attr bool zjit_profile = true;
+{
+ vm_setinstancevariable(GET_ISEQ(), GET_SELF(), id, val, ic);
+}
+
+/* Get value of class variable id of klass as val. */
+DEFINE_INSN
+getclassvariable
+(ID id, ICVARC ic)
+()
+(VALUE val)
+/* "class variable access from toplevel" warning can be hooked. */
+// attr bool leaf = false; /* has rb_warning() */
+{
+ rb_control_frame_t *cfp = GET_CFP();
+ val = vm_getclassvariable(GET_ISEQ(), cfp, id, ic);
+}
+
+/* Set value of class variable id of klass as val. */
+DEFINE_INSN
+setclassvariable
+(ID id, ICVARC ic)
+(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());
+ vm_setclassvariable(GET_ISEQ(), GET_CFP(), id, val, ic);
+}
+
+DEFINE_INSN
+opt_getconstant_path
+(IC ic)
+()
+(VALUE val)
+// attr bool leaf = false; /* may autoload or raise */
+{
+ val = rb_vm_opt_getconstant_path(ec, GET_CFP(), ic);
+}
+
+/* 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
+(ID gid)
+()
+(VALUE val)
+// attr bool leaf = false;
+{
+ val = rb_gvar_get(gid);
+}
+
+/* set global variable id as val. */
+DEFINE_INSN
+setglobal
+(ID gid)
+(VALUE val)
+()
+// attr bool leaf = false;
+{
+ rb_gvar_set(gid, 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)
+// attr bool leaf = (value_type == VM_SPECIAL_OBJECT_VMCORE); /* others may raise when allocating singleton */
+{
+ 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
+dupstring
+(VALUE str)
+()
+(VALUE val)
+{
+ val = rb_ec_str_resurrect(ec, str, false);
+}
+
+/* put chilled string val. string will be copied but frozen in the future. */
+DEFINE_INSN
+dupchilledstring
+(VALUE str)
+()
+(VALUE val)
+{
+ val = rb_ec_str_resurrect(ec, str, true);
+}
+
+/* put concatenate strings */
+DEFINE_INSN
+concatstrings
+(rb_num_t num)
+(...)
+(VALUE val)
+/* This instruction can concat UTF-8 and binary strings, resulting in
+ * Encoding::CompatibilityError. */
+// 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));
+}
+
+/* Convert the result to string if not already a string.
+ This is used as a backup if to_s does not return a string. */
+DEFINE_INSN
+anytostring
+()
+(VALUE val, VALUE str)
+(VALUE val)
+{
+ val = rb_obj_as_string_result(str, val);
+}
+
+/* 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;
+{
+ val = rb_reg_new_from_values(cnt, STACK_ADDR_FROM_TOP(cnt), (int)opt);
+}
+
+/* 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_ec_ary_new_from_values(ec, num, STACK_ADDR_FROM_TOP(num));
+}
+
+/* push hash onto array unless the hash is empty (as empty keyword
+ splats should be ignored).
+ */
+DEFINE_INSN
+pushtoarraykwsplat
+()
+(VALUE ary, VALUE hash)
+(VALUE ary)
+{
+ if (!RHASH_EMPTY_P(hash)) {
+ rb_ary_push(ary, hash);
+ }
+}
+
+/* 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 handles_sp = true;
+// 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_CFP(), ary, num, (int)flag);
+}
+
+/* concat two arrays, without modifying first array.
+ * attempts to convert both objects to arrays using to_a.
+ */
+DEFINE_INSN
+concatarray
+()
+(VALUE ary1, VALUE ary2)
+(VALUE ary)
+// attr bool leaf = false; /* has rb_check_array_type() */
+{
+ ary = vm_concat_array(ary1, ary2);
+}
+
+/* concat second array to first array.
+ * first argument must already be an array.
+ * attempts to convert second object to array using to_a.
+ */
+DEFINE_INSN
+concattoarray
+()
+(VALUE ary1, VALUE ary2)
+(VALUE ary)
+// attr bool leaf = false; /* has rb_check_array_type() */
+{
+ ary = vm_concat_to_array(ary1, ary2);
+}
+
+/* push given number of objects to array directly before. */
+DEFINE_INSN
+pushtoarray
+(rb_num_t num)
+(...)
+(VALUE val)
+// attr rb_snum_t sp_inc = -(rb_snum_t)num;
+{
+ const VALUE *objp = STACK_ADDR_FROM_TOP(num);
+ val = rb_ary_cat(*(objp-1), objp, num);
+}
+
+/* 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);
+}
+
+/* call to_hash on hash to keyword splat before converting block */
+DEFINE_INSN
+splatkw
+()
+(VALUE hash, VALUE block)
+(VALUE obj, VALUE block)
+// attr bool leaf = false; /* has rb_to_hash_type() */
+// attr bool zjit_profile = true;
+{
+ if (NIL_P(hash)) {
+ obj = Qnil;
+ }
+ else {
+ obj = rb_to_hash_type(hash);
+ }
+}
+
+/* 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);
+
+ if (num) {
+ val = rb_hash_new_with_bulk_insert(num, STACK_ADDR_FROM_TOP(num));
+ }
+ else {
+ val = rb_hash_new();
+ }
+}
+
+/* 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
+opt_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 pushval)
+(VALUE v)
+(VALUE val)
+// attr bool leaf = leafness_of_defined(op_type);
+{
+ val = Qnil;
+ if (vm_defined(ec, GET_CFP(), op_type, obj, v)) {
+ val = pushval;
+ }
+}
+
+/* defined?(@foo) */
+DEFINE_INSN
+definedivar
+(ID id, IVC ic, VALUE pushval)
+()
+(VALUE val)
+// attr bool leaf = false;
+// attr bool zjit_profile = true;
+{
+ val = Qnil;
+ if (!UNDEF_P(vm_getivar(GET_SELF(), id, GET_ISEQ(), ic, NULL, FALSE, Qundef))) {
+ val = pushval;
+ }
+}
+
+/* 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_of?(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 = RBOOL(TYPE(val) == (int)type);
+}
+
+/**********************************************************/
+/* deal with control flow 1: class/module */
+/**********************************************************/
+
+/* enter class definition scope. if super is Qfalse, and class
+ "klass" is defined, it's redefined. 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);
+ const rb_box_t *box = rb_current_box();
+
+ rb_iseq_check(class_iseq);
+
+ rb_cref_t *cref = vm_cref_push(ec, klass, NULL, FALSE, FALSE);
+
+ if (VM_DEFINECLASS_DYNAMIC_CREF_P(flags)) {
+ CREF_DYNAMIC_SET(cref);
+ }
+
+ /* enter scope */
+ vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass,
+ GC_GUARDED_PTR(box),
+ (VALUE)cref,
+ ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(),
+ ISEQ_BODY(class_iseq)->local_table_size,
+ ISEQ_BODY(class_iseq)->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 bool zjit_profile = 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_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, false);
+ val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
+ JIT_EXEC(ec, val);
+
+ if (UNDEF_P(val)) {
+ RESTORE_REGS();
+ NEXT_INSN();
+ }
+}
+
+/* invoke forward method. */
+DEFINE_INSN
+sendforward
+(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);
+{
+ struct rb_forwarding_call_data adjusted_cd;
+ struct rb_callinfo adjusted_ci;
+
+ VALUE bh = vm_caller_setup_fwd_args(ec, GET_CFP(), cd, blockiseq, 0, &adjusted_cd, &adjusted_ci);
+
+ val = vm_sendish(ec, GET_CFP(), &adjusted_cd.cd, bh, mexp_search_method);
+ JIT_EXEC(ec, val);
+
+ if (cd->cc != adjusted_cd.cd.cc && vm_cc_markable(adjusted_cd.cd.cc)) {
+ RB_OBJ_WRITE(GET_ISEQ(), &cd->cc, adjusted_cd.cd.cc);
+ }
+
+ if (UNDEF_P(val)) {
+ RESTORE_REGS();
+ NEXT_INSN();
+ }
+}
+
+/* Invoke method without block */
+DEFINE_INSN
+opt_send_without_block
+(CALL_DATA cd)
+(...)
+(VALUE val)
+// attr bool zjit_profile = true;
+// 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, mexp_search_method);
+ JIT_EXEC(ec, val);
+
+ if (UNDEF_P(val)) {
+ RESTORE_REGS();
+ NEXT_INSN();
+ }
+}
+
+/* Jump if "new" method has been defined by user */
+DEFINE_INSN
+opt_new
+(CALL_DATA cd, OFFSET dst)
+()
+()
+// attr bool leaf = false;
+{
+ VALUE argc = vm_ci_argc(cd->ci);
+ VALUE val = TOPN(argc);
+
+ // The bookkeeping slot should be empty.
+ RUBY_ASSERT(TOPN(argc + 1) == Qnil);
+
+ if (vm_method_cfunc_is(GET_CFP(), cd, val, rb_class_new_instance_pass_kw)) {
+ RB_DEBUG_COUNTER_INC(opt_new_hit);
+ val = rb_obj_alloc(val);
+ TOPN(argc) = val;
+ TOPN(argc + 1) = val;
+ }
+ else {
+ RB_DEBUG_COUNTER_INC(opt_new_miss);
+ JUMP(dst);
+ }
+}
+
+/* Convert object to string using to_s or equivalent. */
+DEFINE_INSN
+objtostring
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool leaf = false;
+// attr bool zjit_profile = true;
+{
+ val = vm_objtostring(GET_CFP(), recv, cd);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+DEFINE_INSN
+opt_ary_freeze
+(VALUE ary, CALL_DATA cd)
+()
+(VALUE val)
+{
+ val = vm_opt_ary_freeze(ary, BOP_FREEZE, idFreeze);
+
+ if (UNDEF_P(val)) {
+ RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary));
+ PUSH(rb_ary_resurrect(ary));
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+DEFINE_INSN
+opt_hash_freeze
+(VALUE hash, CALL_DATA cd)
+()
+(VALUE val)
+{
+ val = vm_opt_hash_freeze(hash, BOP_FREEZE, idFreeze);
+
+ if (UNDEF_P(val)) {
+ RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1);
+ PUSH(rb_hash_resurrect(hash));
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+DEFINE_INSN
+opt_str_freeze
+(VALUE str, CALL_DATA cd)
+()
+(VALUE val)
+{
+ val = vm_opt_str_freeze(str, BOP_FREEZE, idFreeze);
+
+ if (UNDEF_P(val)) {
+ PUSH(rb_str_resurrect(str));
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized nil? */
+DEFINE_INSN
+opt_nil_p
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_nil_p(GET_CFP(), cd, recv);
+
+ if (UNDEF_P(val)) {
+ 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 (UNDEF_P(val)) {
+ PUSH(rb_str_resurrect(str));
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+DEFINE_INSN
+opt_duparray_send
+(VALUE ary, ID method, rb_num_t argc)
+(...)
+(VALUE val)
+/* This instruction typically has no funcalls. But it may compare array
+ * contents to each other which may call methods when necessary.
+ * No way to detect such method calls beforehand.
+ * We must mark it as not leaf. */
+// attr bool leaf = false; /* has rb_funcall() */
+// attr rb_snum_t sp_inc = 1 - (rb_snum_t)argc;
+// attr rb_snum_t comptime_sp_inc = 1 - (rb_snum_t)argc;
+{
+ switch (method) {
+ case idIncludeP:
+ val = vm_opt_duparray_include_p(ec, ary, TOPN(0));
+ break;
+ default:
+ rb_bug("unreachable");
+ }
+}
+
+DEFINE_INSN
+opt_newarray_send
+(rb_num_t num, rb_num_t method)
+(...)
+(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;
+// attr rb_snum_t comptime_sp_inc = 1 - (rb_snum_t)num;
+{
+ switch (method) {
+ case VM_OPT_NEWARRAY_SEND_HASH:
+ val = vm_opt_newarray_hash(ec, num, STACK_ADDR_FROM_TOP(num));
+ break;
+ case VM_OPT_NEWARRAY_SEND_MIN:
+ val = vm_opt_newarray_min(ec, num, STACK_ADDR_FROM_TOP(num));
+ break;
+ case VM_OPT_NEWARRAY_SEND_MAX:
+ val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num));
+ break;
+ case VM_OPT_NEWARRAY_SEND_INCLUDE_P:
+ val = vm_opt_newarray_include_p(ec, (long)num-1, STACK_ADDR_FROM_TOP(num), TOPN(0));
+ break;
+ case VM_OPT_NEWARRAY_SEND_PACK:
+ val = vm_opt_newarray_pack_buffer(ec, (long)num-1, STACK_ADDR_FROM_TOP(num), TOPN(0), Qundef);
+ break;
+ case VM_OPT_NEWARRAY_SEND_PACK_BUFFER:
+ val = vm_opt_newarray_pack_buffer(ec, (long)num-2, STACK_ADDR_FROM_TOP(num), TOPN(1), TOPN(0));
+ break;
+ default:
+ rb_bug("unreachable");
+ }
+}
+
+/* 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);
+// attr bool zjit_profile = true;
+{
+ struct rb_callinfo adjusted_ci = VM_CI_ON_STACK(vm_ci_mid(cd->ci),
+ vm_ci_flag(cd->ci),
+ vm_ci_argc(cd->ci),
+ vm_ci_kwarg(cd->ci));
+ const struct rb_callcache *original_cc = rbimpl_atomic_ptr_load((void **)&cd->cc, RBIMPL_ATOMIC_ACQUIRE);
+ struct rb_call_data adjusted_cd = {
+ .ci = &adjusted_ci,
+ .cc = original_cc,
+ };
+
+ VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), adjusted_cd.ci, blockiseq, true);
+ val = vm_sendish(ec, GET_CFP(), &adjusted_cd, bh, mexp_search_super);
+ JIT_EXEC(ec, val);
+
+ if (original_cc != adjusted_cd.cc && vm_cc_markable(adjusted_cd.cc)) {
+ rbimpl_atomic_ptr_store((volatile void **)&cd->cc, (void *)adjusted_cd.cc, RBIMPL_ATOMIC_RELEASE);
+ RB_OBJ_WRITTEN(GET_ISEQ(), Qundef, adjusted_cd.cc);
+ }
+
+ if (UNDEF_P(val)) {
+ RESTORE_REGS();
+ NEXT_INSN();
+ }
+}
+
+/* super(args) # args.size => num */
+DEFINE_INSN
+invokesuperforward
+(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);
+{
+ struct rb_forwarding_call_data adjusted_cd;
+ struct rb_callinfo adjusted_ci;
+
+ VALUE bh = vm_caller_setup_fwd_args(ec, GET_CFP(), cd, blockiseq, 1, &adjusted_cd, &adjusted_ci);
+
+ val = vm_sendish(ec, GET_CFP(), &adjusted_cd.cd, bh, mexp_search_super);
+ JIT_EXEC(ec, val);
+
+ if (cd->cc != adjusted_cd.cd.cc && vm_cc_markable(adjusted_cd.cd.cc)) {
+ RB_OBJ_WRITE(GET_ISEQ(), &cd->cc, adjusted_cd.cd.cc);
+ }
+
+ if (UNDEF_P(val)) {
+ 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);
+// attr bool zjit_profile = true;
+{
+ VALUE bh = VM_BLOCK_HANDLER_NONE;
+ val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock);
+ JIT_EXEC(ec, val);
+
+ if (UNDEF_P(val)) {
+ 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);
+ }
+ }
+
+ 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() */
+{
+ 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);
+ }
+}
+
+/* same as jump, but without interrupt check */
+DEFINE_INSN
+jump_without_ints
+(OFFSET dst)
+()
+()
+// attr bool leaf = true;
+{
+ JUMP(dst);
+}
+
+/* same as branchif, but without interrupt check */
+DEFINE_INSN
+branchif_without_ints
+(OFFSET dst)
+(VALUE val)
+()
+// attr bool leaf = true;
+{
+ if (RTEST(val)) {
+ JUMP(dst);
+ }
+}
+
+/* same as branchunless, but without interrupt check */
+DEFINE_INSN
+branchunless_without_ints
+(OFFSET dst)
+(VALUE val)
+()
+// attr bool leaf = true;
+{
+ if (!RTEST(val)) {
+ JUMP(dst);
+ }
+}
+
+/* same as branchnil, but without interrupt check */
+DEFINE_INSN
+branchnil_without_ints
+(OFFSET dst)
+(VALUE val)
+()
+// attr bool leaf = true;
+{
+ if (NIL_P(val)) {
+ JUMP(dst);
+ }
+}
+
+/**********************************************************/
+/* for optimize */
+/**********************************************************/
+
+/* 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)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_plus(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X-Y. */
+DEFINE_INSN
+opt_minus
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_minus(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X*Y. */
+DEFINE_INSN
+opt_mult
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_mult(recv, obj);
+
+ if (UNDEF_P(val)) {
+ 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;
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_div(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X%Y. */
+DEFINE_INSN
+opt_mod
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+/* Same discussion as opt_div. */
+// attr bool leaf = false;
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_mod(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X==Y. */
+DEFINE_INSN
+opt_eq
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = opt_equality(GET_CFP(), recv, obj, cd);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X!=Y. */
+DEFINE_INSN
+opt_neq
+(CALL_DATA cd_eq, CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_neq(GET_CFP(), cd, cd_eq, recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X<Y. */
+DEFINE_INSN
+opt_lt
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_lt(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X<=Y. */
+DEFINE_INSN
+opt_le
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_le(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X>Y. */
+DEFINE_INSN
+opt_gt
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_gt(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X>=Y. */
+DEFINE_INSN
+opt_ge
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_ge(recv, obj);
+
+ if (UNDEF_P(val)) {
+ 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 */
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_ltlt(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X&Y. */
+DEFINE_INSN
+opt_and
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_and(recv, obj);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized X|Y. */
+DEFINE_INSN
+opt_or
+(CALL_DATA cd)
+(VALUE recv, VALUE obj)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_or(recv, obj);
+
+ if (UNDEF_P(val)) {
+ 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 */
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_aref(recv, obj);
+
+ if (UNDEF_P(val)) {
+ 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 */
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_aset(recv, obj, set);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized length */
+DEFINE_INSN
+opt_length
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_length(recv, BOP_LENGTH);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized size */
+DEFINE_INSN
+opt_size
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_length(recv, BOP_SIZE);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized empty? */
+DEFINE_INSN
+opt_empty_p
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_empty_p(recv);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized succ */
+DEFINE_INSN
+opt_succ
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_succ(recv);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* optimized not */
+DEFINE_INSN
+opt_not
+(CALL_DATA cd)
+(VALUE recv)
+(VALUE val)
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_not(GET_CFP(), cd, recv);
+
+ if (UNDEF_P(val)) {
+ 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() */
+// attr bool zjit_profile = true;
+{
+ val = vm_opt_regexpmatch2(obj2, obj1);
+
+ if (UNDEF_P(val)) {
+ CALL_SIMPLE_METHOD();
+ }
+}
+
+/* call specific function with args */
+DEFINE_INSN
+invokebuiltin
+(RB_BUILTIN bf)
+(...)
+(VALUE val)
+// attr bool leaf = false; /* anything can happen inside */
+// attr rb_snum_t sp_inc = 1 - bf->argc;
+{
+ val = 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, rb_num_t index)
+()
+(VALUE val)
+// attr bool leaf = false; /* anything can happen inside */
+{
+ val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
+}
+
+/* call specific function with args (same parameters) and leave */
+DEFINE_INSN
+opt_invokebuiltin_delegate_leave
+(RB_BUILTIN bf, rb_num_t index)
+()
+(VALUE val)
+// attr bool leaf = false; /* anything can happen inside */
+{
+ val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
+
+ /* leave fastpath */
+ /* TracePoint/return fallbacks this insn to opt_invokebuiltin_delegate */
+ 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);
+}