diff options
Diffstat (limited to 'vm_eval.c')
| -rw-r--r-- | vm_eval.c | 760 |
1 files changed, 556 insertions, 204 deletions
@@ -1,6 +1,6 @@ /********************************************************************** - vm_eval.c - + vm_eval.c - Included into vm.c. $Author$ created at: Sat May 24 16:02:32 JST 2008 @@ -21,7 +21,7 @@ static inline VALUE vm_yield_with_cref(rb_execution_context_t *ec, int argc, con static inline VALUE vm_yield(rb_execution_context_t *ec, int argc, const VALUE *argv, int kw_splat); static inline VALUE vm_yield_with_block(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE block_handler, int kw_splat); static inline VALUE vm_yield_force_blockarg(rb_execution_context_t *ec, VALUE args); -VALUE vm_exec(rb_execution_context_t *ec, bool jit_enable_p); +VALUE vm_exec(rb_execution_context_t *ec); static void vm_set_eval_stack(rb_execution_context_t * th, const rb_iseq_t *iseq, const rb_cref_t *cref, const struct rb_block *base_block); static int vm_collect_local_variables_in_heap(const VALUE *dfp, const struct local_var_list *vars); @@ -29,36 +29,39 @@ static VALUE rb_eUncaughtThrow; static ID id_result, id_tag, id_value; #define id_mesg idMesg -typedef enum call_type { - CALL_PUBLIC, - CALL_FCALL, - CALL_VCALL, - CALL_PUBLIC_KW, - CALL_FCALL_KW, - CALL_TYPE_MAX -} call_type; - static VALUE send_internal(int argc, const VALUE *argv, VALUE recv, call_type scope); static VALUE vm_call0_body(rb_execution_context_t* ec, struct rb_calling_info *calling, const VALUE *argv); -#ifndef MJIT_HEADER +static VALUE * +vm_argv_ruby_array(VALUE *av, const VALUE *argv, int *flags, int *argc, int kw_splat) +{ + *flags |= VM_CALL_ARGS_SPLAT; + VALUE argv_ary = rb_ary_hidden_new(*argc); + rb_ary_cat(argv_ary, argv, *argc); + *argc = 2; + av[0] = argv_ary; + if (kw_splat) { + av[1] = rb_ary_pop(argv_ary); + } + else { + // Make sure flagged keyword hash passed as regular argument + // isn't treated as keywords + *flags |= VM_CALL_KW_SPLAT; + av[1] = rb_hash_new(); + } + return av; +} -MJIT_FUNC_EXPORTED VALUE +static inline VALUE vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat); + +VALUE rb_vm_call0(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const rb_callable_method_entry_t *cme, int kw_splat) { - struct rb_calling_info calling = { - .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), - .cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme), - .block_handler = vm_passed_block_handler(ec), - .recv = recv, - .argc = argc, - .kw_splat = kw_splat, - }; - - return vm_call0_body(ec, &calling, argv); + const struct rb_callcache cc = VM_CC_ON_STACK(Qundef, vm_call_general, {{ 0 }}, cme); + return vm_call0_cc(ec, recv, id, argc, argv, &cc, kw_splat); } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_vm_call_with_refinements(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, int kw_splat) { const rb_callable_method_entry_t *me = @@ -75,8 +78,19 @@ rb_vm_call_with_refinements(rb_execution_context_t *ec, VALUE recv, ID id, int a static inline VALUE vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE *argv, const struct rb_callcache *cc, int kw_splat) { + int flags = kw_splat ? VM_CALL_KW_SPLAT : 0; + VALUE *use_argv = (VALUE *)argv; + VALUE av[2]; + + if (UNLIKELY(vm_cc_cme(cc)->def->type == VM_METHOD_TYPE_ISEQ && argc > VM_ARGC_STACK_MAX)) { + use_argv = vm_argv_ruby_array(av, argv, &flags, &argc, kw_splat); + } + struct rb_calling_info calling = { - .ci = &VM_CI_ON_STACK(id, kw_splat ? VM_CALL_KW_SPLAT : 0, argc, NULL), + .cd = &(struct rb_call_data) { + .ci = &VM_CI_ON_STACK(id, flags, argc, NULL), + .cc = NULL, + }, .cc = cc, .block_handler = vm_passed_block_handler(ec), .recv = recv, @@ -84,20 +98,20 @@ vm_call0_cc(rb_execution_context_t *ec, VALUE recv, ID id, int argc, const VALUE .kw_splat = kw_splat, }; - return vm_call0_body(ec, &calling, argv); + return vm_call0_body(ec, &calling, use_argv); } static VALUE vm_call0_cme(rb_execution_context_t *ec, struct rb_calling_info *calling, const VALUE *argv, const rb_callable_method_entry_t *cme) { - calling->cc = &VM_CC_ON_STACK(Qfalse, vm_call_general, {{ 0 }}, cme); + calling->cc = &VM_CC_ON_STACK(Qundef, vm_call_general, {{ 0 }}, cme); return vm_call0_body(ec, calling, argv); } static VALUE vm_call0_super(rb_execution_context_t *ec, struct rb_calling_info *calling, const VALUE *argv, VALUE klass, enum method_missing_reason ex) { - ID mid = vm_ci_mid(calling->ci); + ID mid = vm_ci_mid(calling->cd->ci); klass = RCLASS_SUPER(klass); if (klass) { @@ -116,7 +130,7 @@ vm_call0_super(rb_execution_context_t *ec, struct rb_calling_info *calling, cons static VALUE vm_call0_cfunc_with_frame(rb_execution_context_t* ec, struct rb_calling_info *calling, const VALUE *argv) { - const struct rb_callinfo *ci = calling->ci; + const struct rb_callinfo *ci = calling->cd->ci; VALUE val; const rb_callable_method_entry_t *me = vm_cc_cme(calling->cc); const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(me->def, body.cfunc); @@ -181,7 +195,7 @@ vm_call_check_arity(struct rb_calling_info *calling, int argc, const VALUE *argv static VALUE vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const VALUE *argv) { - const struct rb_callinfo *ci = calling->ci; + const struct rb_callinfo *ci = calling->cd->ci; const struct rb_callcache *cc = calling->cc; VALUE ret; @@ -201,9 +215,14 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const *reg_cfp->sp++ = argv[i]; } - vm_call_iseq_setup(ec, reg_cfp, calling); + if (ISEQ_BODY(def_iseq_ptr(vm_cc_cme(cc)->def))->param.flags.forwardable) { + vm_call_iseq_fwd_setup(ec, reg_cfp, calling); + } + else { + vm_call_iseq_setup(ec, reg_cfp, calling); + } VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH); - return vm_exec(ec, true); /* CHECK_INTS in this function */ + return vm_exec(ec); // CHECK_INTS in this function } case VM_METHOD_TYPE_NOTIMPLEMENTED: case VM_METHOD_TYPE_CFUNC: @@ -276,11 +295,15 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const } case OPTIMIZED_METHOD_TYPE_STRUCT_AREF: vm_call_check_arity(calling, 0, argv); - ret = vm_call_opt_struct_aref0(ec, calling); + VM_CALL_METHOD_ATTR(ret, + vm_call_opt_struct_aref0(ec, calling), + (void)0); goto success; case OPTIMIZED_METHOD_TYPE_STRUCT_ASET: vm_call_check_arity(calling, 1, argv); - ret = vm_call_opt_struct_aset0(ec, calling, argv[0]); + VM_CALL_METHOD_ATTR(ret, + vm_call_opt_struct_aset0(ec, calling, argv[0]), + (void)0); goto success; default: rb_bug("vm_call0: unsupported optimized method type (%d)", vm_cc_cme(cc)->def->body.optimized.type); @@ -297,7 +320,7 @@ vm_call0_body(rb_execution_context_t *ec, struct rb_calling_info *calling, const return ret; } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_vm_call_kw(rb_execution_context_t *ec, VALUE recv, VALUE id, int argc, const VALUE *argv, const rb_callable_method_entry_t *me, int kw_splat) { return rb_vm_call0(ec, recv, id, argc, argv, me, kw_splat); @@ -352,20 +375,16 @@ rb_current_receiver(void) return cfp->self; } -#endif /* #ifndef MJIT_HEADER */ - static inline void stack_check(rb_execution_context_t *ec) { if (!rb_ec_raised_p(ec, RAISED_STACKOVERFLOW) && rb_ec_stack_check(ec)) { rb_ec_raised_set(ec, RAISED_STACKOVERFLOW); - rb_ec_stack_overflow(ec, FALSE); + rb_ec_stack_overflow(ec, 0); } } -#ifndef MJIT_HEADER - void rb_check_stack_overflow(void) { @@ -380,73 +399,56 @@ NORETURN(static void uncallable_object(VALUE recv, ID mid)); static inline const rb_callable_method_entry_t *rb_search_method_entry(VALUE recv, ID mid); static inline enum method_missing_reason rb_method_call_status(rb_execution_context_t *ec, const rb_callable_method_entry_t *me, call_type scope, VALUE self); -static const struct rb_callcache * -cc_new(VALUE klass, ID mid, int argc, const rb_callable_method_entry_t *cme) +static VALUE +gccct_hash(VALUE klass, VALUE box_value, ID mid) { - const struct rb_callcache *cc = NULL; - - RB_VM_LOCK_ENTER(); - { - struct rb_class_cc_entries *ccs; - struct rb_id_table *cc_tbl = RCLASS_CC_TBL(klass); - VALUE ccs_data; - - if (rb_id_table_lookup(cc_tbl, mid, &ccs_data)) { - // ok - ccs = (struct rb_class_cc_entries *)ccs_data; - } - else { - ccs = vm_ccs_create(klass, cc_tbl, mid, cme); - } + return ((klass ^ box_value) >> 3) ^ (VALUE)mid; +} - for (int i=0; i<ccs->len; i++) { - cc = ccs->entries[i].cc; - if (vm_cc_cme(cc) == cme) { - break; - } - cc = NULL; - } +NOINLINE(static const struct rb_callcache *gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, unsigned int index, const struct rb_callinfo * ci)); - if (cc == NULL) { - const struct rb_callinfo *ci = vm_ci_new(mid, 0, argc, NULL); // TODO: proper ci - cc = vm_cc_new(klass, cme, vm_call_general); - METHOD_ENTRY_CACHED_SET((struct rb_callable_method_entry_struct *)cme); - vm_ccs_push(klass, ccs, ci, cc); - } - } - RB_VM_LOCK_LEAVE(); +static const struct rb_callcache * +gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, unsigned int index, const struct rb_callinfo *ci) +{ + struct rb_call_data cd = { + .ci = ci, + .cc = NULL + }; - return cc; -} + vm_search_method_slowpath0(vm->self, &cd, klass); -static VALUE -gccct_hash(VALUE klass, ID mid) -{ - return (klass >> 3) ^ (VALUE)mid; + return vm->global_cc_cache_table[index] = cd.cc; } -NOINLINE(static const struct rb_callcache *gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, ID mid, int argc, unsigned int index)); - -static const struct rb_callcache * -gccct_method_search_slowpath(rb_vm_t *vm, VALUE klass, ID mid, int argc, unsigned int index) +static void +scope_to_ci(call_type scope, ID mid, int argc, struct rb_callinfo *ci) { - const rb_callable_method_entry_t *cme = rb_callable_method_entry(klass, mid); - const struct rb_callcache *cc; + int flags = 0; - if (cme != NULL) { - cc = cc_new(klass, mid, argc, cme); - } - else { - cc = NULL; + switch(scope) { + case CALL_PUBLIC: + break; + case CALL_FCALL: + flags |= VM_CALL_FCALL; + break; + case CALL_VCALL: + flags |= VM_CALL_VCALL; + break; + case CALL_PUBLIC_KW: + flags |= VM_CALL_KWARG; + break; + case CALL_FCALL_KW: + flags |= (VM_CALL_KWARG | VM_CALL_FCALL); + break; } - - return vm->global_cc_cache_table[index] = cc; + *ci = VM_CI_ON_STACK(mid, flags, argc, NULL); } static inline const struct rb_callcache * -gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, int argc) +gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, const struct rb_callinfo *ci) { - VALUE klass; + VALUE klass, box_value; + const rb_box_t *box = rb_current_box(); if (!SPECIAL_CONST_P(recv)) { klass = RBASIC_CLASS(recv); @@ -456,8 +458,14 @@ gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, int argc) klass = CLASS_OF(recv); } + if (BOX_USER_P(box)) { + box_value = box->box_object; + } + else { + box_value = 0; + } // search global method cache - unsigned int index = (unsigned int)(gccct_hash(klass, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); + unsigned int index = (unsigned int)(gccct_hash(klass, box_value, mid) % VM_GLOBAL_CC_CACHE_TABLE_SIZE); rb_vm_t *vm = rb_ec_vm_ptr(ec); const struct rb_callcache *cc = vm->global_cc_cache_table[index]; @@ -479,24 +487,35 @@ gccct_method_search(rb_execution_context_t *ec, VALUE recv, ID mid, int argc) } RB_DEBUG_COUNTER_INC(gccct_miss); - return gccct_method_search_slowpath(vm, klass, mid, argc, index); + return gccct_method_search_slowpath(vm, klass, index, ci); +} + +VALUE +rb_gccct_clear_table(VALUE _self) +{ + int i; + rb_vm_t *vm = GET_VM(); + for (i=0; i<VM_GLOBAL_CC_CACHE_TABLE_SIZE; i++) { + vm->global_cc_cache_table[i] = NULL; + } + return Qnil; } -/*! - * \internal +/** + * @internal * calls the specified method. * * This function is called by functions in rb_call* family. - * \param ec current execution context - * \param recv receiver of the method - * \param mid an ID that represents the name of the method - * \param argc the number of method arguments - * \param argv a pointer to an array of method arguments - * \param scope - * \param self self in the caller. Qundef means no self is considered and + * @param ec current execution context + * @param recv receiver of the method + * @param mid an ID that represents the name of the method + * @param argc the number of method arguments + * @param argv a pointer to an array of method arguments + * @param scope + * @param self self in the caller. Qundef means no self is considered and * protected methods cannot be called * - * \note \a self is used in order to controlling access to protected methods. + * @note `self` is used in order to controlling access to protected methods. */ static inline VALUE rb_call0(rb_execution_context_t *ec, @@ -520,13 +539,16 @@ rb_call0(rb_execution_context_t *ec, break; } - const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, argc); + struct rb_callinfo ci; + scope_to_ci(scope, mid, argc, &ci); + + const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, &ci); if (scope == CALL_PUBLIC) { RB_DEBUG_COUNTER_INC(call0_public); const rb_callable_method_entry_t *cc_cme = cc ? vm_cc_cme(cc) : NULL; - const rb_callable_method_entry_t *cme = callable_method_entry_refeinements0(CLASS_OF(recv), mid, NULL, true, cc_cme); + const rb_callable_method_entry_t *cme = callable_method_entry_refinements0(CLASS_OF(recv), mid, NULL, true, cc_cme); call_status = rb_method_call_status(ec, cme, scope, self); if (UNLIKELY(call_status != MISSING_NONE)) { @@ -722,13 +744,6 @@ rb_check_funcall_with_hook_kw(VALUE recv, ID mid, int argc, const VALUE *argv, return rb_vm_call_kw(ec, recv, mid, argc, argv, me, kw_splat); } -VALUE -rb_check_funcall_with_hook(VALUE recv, ID mid, int argc, const VALUE *argv, - rb_check_funcall_hook *hook, VALUE arg) -{ - return rb_check_funcall_with_hook_kw(recv, mid, argc, argv, hook, arg, RB_NO_KEYWORDS); -} - const char * rb_type_str(enum ruby_value_type type) { @@ -777,29 +792,29 @@ uncallable_object(VALUE recv, ID mid) if (SPECIAL_CONST_P(recv)) { rb_raise(rb_eNotImpError, - "method `%"PRIsVALUE"' called on unexpected immediate object (%p)", + "method '%"PRIsVALUE"' called on unexpected immediate object (%p)", mname, (void *)recv); } else if ((flags = RBASIC(recv)->flags) == 0) { rb_raise(rb_eNotImpError, - "method `%"PRIsVALUE"' called on terminated object (%p)", + "method '%"PRIsVALUE"' called on terminated object (%p)", mname, (void *)recv); } else if (!(typestr = rb_type_str(type = BUILTIN_TYPE(recv)))) { rb_raise(rb_eNotImpError, - "method `%"PRIsVALUE"' called on broken T_?""?""?(0x%02x) object" + "method '%"PRIsVALUE"' called on broken T_?""?""?(0x%02x) object" " (%p flags=0x%"PRIxVALUE")", mname, type, (void *)recv, flags); } else if (T_OBJECT <= type && type < T_NIL) { rb_raise(rb_eNotImpError, - "method `%"PRIsVALUE"' called on hidden %s object" + "method '%"PRIsVALUE"' called on hidden %s object" " (%p flags=0x%"PRIxVALUE")", mname, typestr, (void *)recv, flags); } else { rb_raise(rb_eNotImpError, - "method `%"PRIsVALUE"' called on unexpected %s object" + "method '%"PRIsVALUE"' called on unexpected %s object" " (%p flags=0x%"PRIxVALUE")", mname, typestr, (void *)recv, flags); } @@ -858,16 +873,16 @@ rb_method_call_status(rb_execution_context_t *ec, const rb_callable_method_entry } -/*! - * \internal +/** + * @internal * calls the specified method. * * This function is called by functions in rb_call* family. - * \param recv receiver - * \param mid an ID that represents the name of the method - * \param argc the number of method arguments - * \param argv a pointer to an array of method arguments - * \param scope + * @param recv receiver + * @param mid an ID that represents the name of the method + * @param argc the number of method arguments + * @param argv a pointer to an array of method arguments + * @param scope */ static inline VALUE rb_call(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope) @@ -926,14 +941,14 @@ rb_method_missing(int argc, const VALUE *argv, VALUE obj) UNREACHABLE_RETURN(Qnil); } -MJIT_FUNC_EXPORTED VALUE +VALUE rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj, int argc, const VALUE *argv, int priv) { VALUE name = argv[0]; if (!format) { - format = rb_fstring_lit("undefined method `%s' for %s%s%s"); + format = rb_fstring_lit("undefined method '%1$s' for %3$s%4$s"); } if (exc == rb_eNoMethodError) { VALUE args = rb_ary_new4(argc - 1, argv + 1); @@ -944,8 +959,6 @@ rb_make_no_method_exception(VALUE exc, VALUE format, VALUE obj, } } -#endif /* #ifndef MJIT_HEADER */ - static void raise_method_missing(rb_execution_context_t *ec, int argc, const VALUE *argv, VALUE obj, enum method_missing_reason last_call_status) @@ -965,17 +978,17 @@ raise_method_missing(rb_execution_context_t *ec, int argc, const VALUE *argv, VA stack_check(ec); if (last_call_status & MISSING_PRIVATE) { - format = rb_fstring_lit("private method `%s' called for %s%s%s"); + format = rb_fstring_lit("private method '%1$s' called for %3$s%4$s"); } else if (last_call_status & MISSING_PROTECTED) { - format = rb_fstring_lit("protected method `%s' called for %s%s%s"); + format = rb_fstring_lit("protected method '%1$s' called for %3$s%4$s"); } else if (last_call_status & MISSING_VCALL) { - format = rb_fstring_lit("undefined local variable or method `%s' for %s%s%s"); + format = rb_fstring_lit("undefined local variable or method '%1$s' for %3$s%4$s"); exc = rb_eNameError; } else if (last_call_status & MISSING_SUPER) { - format = rb_fstring_lit("super: no superclass method `%s' for %s%s%s"); + format = rb_fstring_lit("super: no superclass method '%1$s' for %3$s%4$s"); } { @@ -1035,13 +1048,15 @@ method_missing(rb_execution_context_t *ec, VALUE obj, ID id, int argc, const VAL UNREACHABLE_RETURN(Qundef); } -#ifndef MJIT_HEADER - static inline VALUE rb_funcallv_scope(VALUE recv, ID mid, int argc, const VALUE *argv, call_type scope) { rb_execution_context_t *ec = GET_EC(); - const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, argc); + + struct rb_callinfo ci; + scope_to_ci(scope, mid, argc, &ci); + + const struct rb_callcache *cc = gccct_method_search(ec, recv, mid, &ci); VALUE self = ec->cfp->self; if (LIKELY(cc) && @@ -1089,7 +1104,7 @@ rb_apply(VALUE recv, ID mid, VALUE args) return ret; } argv = ALLOCA_N(VALUE, argc); - MEMCPY(argv, RARRAY_CONST_PTR_TRANSIENT(args), VALUE, argc); + MEMCPY(argv, RARRAY_CONST_PTR(args), VALUE, argc); return rb_funcallv(recv, mid, argc, argv); } @@ -1122,15 +1137,15 @@ rb_funcall(VALUE recv, ID mid, int n, ...) return rb_funcallv(recv, mid, n, argv); } -/*! +/** * Calls a method only if it is the basic method of `ancestor` * otherwise returns Qundef; - * \param recv receiver of the method - * \param mid an ID that represents the name of the method - * \param ancestor the Class that defined the basic method - * \param argc the number of arguments - * \param argv pointer to an array of method arguments - * \param kw_splat bool + * @param recv receiver of the method + * @param mid an ID that represents the name of the method + * @param ancestor the Class that defined the basic method + * @param argc the number of arguments + * @param argv pointer to an array of method arguments + * @param kw_splat bool */ VALUE rb_check_funcall_basic_kw(VALUE recv, ID mid, VALUE ancestor, int argc, const VALUE *argv, int kw_splat) @@ -1364,6 +1379,17 @@ rb_yield(VALUE val) } } +VALUE +rb_ec_yield(rb_execution_context_t *ec, VALUE val) +{ + if (UNDEF_P(val)) { + return vm_yield(ec, 0, NULL, RB_NO_KEYWORDS); + } + else { + return vm_yield(ec, 1, &val, RB_NO_KEYWORDS); + } +} + #undef rb_yield_values VALUE rb_yield_values(int n, ...) @@ -1507,13 +1533,6 @@ rb_iterate_internal(VALUE (* it_proc)(VALUE), VALUE data1, GET_EC()); } -VALUE -rb_iterate(VALUE (* it_proc)(VALUE), VALUE data1, - rb_block_call_func_t bl_proc, VALUE data2) -{ - return rb_iterate_internal(it_proc, data1, bl_proc, data2); -} - struct iter_method_arg { VALUE obj; ID mid; @@ -1554,6 +1573,37 @@ rb_block_call_kw(VALUE obj, ID mid, int argc, const VALUE * argv, return rb_iterate_internal(iterate_method, (VALUE)&arg, bl_proc, data2); } +/* + * A flexible variant of rb_block_call and rb_block_call_kw. + * This function accepts flags: + * + * RB_NO_KEYWORDS, RB_PASS_KEYWORDS, RB_PASS_CALLED_KEYWORDS: + * Works as the same as rb_block_call_kw. + * + * RB_BLOCK_NO_USE_PACKED_ARGS: + * The given block ("bl_proc") does not use "yielded_arg" of rb_block_call_func_t. + * Instead, the block accesses the yielded arguments via "argc" and "argv". + * This flag allows the called method to yield arguments without allocating an Array. + */ +VALUE +rb_block_call2(VALUE obj, ID mid, int argc, const VALUE *argv, + rb_block_call_func_t bl_proc, VALUE data2, long flags) +{ + struct iter_method_arg arg; + + arg.obj = obj; + arg.mid = mid; + arg.argc = argc; + arg.argv = argv; + arg.kw_splat = flags & 1; + + struct vm_ifunc *ifunc = rb_vm_ifunc_proc_new(bl_proc, (void *)data2); + if (flags & RB_BLOCK_NO_USE_PACKED_ARGS) + ifunc->flags |= IFUNC_YIELD_OPTIMIZABLE; + + return rb_iterate0(iterate_method, (VALUE)&arg, ifunc, GET_EC()); +} + VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, int min_argc, int max_argc, @@ -1601,40 +1651,289 @@ rb_each(VALUE obj) return rb_call(obj, idEach, 0, 0, CALL_FCALL); } -void rb_parser_warn_location(VALUE, int); +static VALUE eval_default_path = Qfalse; + +#define EVAL_LOCATION_MARK "eval at " +#define EVAL_LOCATION_MARK_LEN (int)rb_strlen_lit(EVAL_LOCATION_MARK) + +static VALUE +get_eval_default_path(void) +{ + int location_lineno; + VALUE location_path = rb_source_location(&location_lineno); + if (!NIL_P(location_path)) { + return rb_fstring(rb_sprintf("("EVAL_LOCATION_MARK"%"PRIsVALUE":%d)", + location_path, location_lineno)); + } + + if (!eval_default_path) { + eval_default_path = rb_fstring_lit("(eval)"); + rb_vm_register_global_object(eval_default_path); + } + return eval_default_path; +} + +static inline int +compute_isolated_depth_from_ep(const VALUE *ep) +{ + int depth = 1; + while (1) { + if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) return depth; + if (VM_ENV_LOCAL_P(ep)) return 0; + ep = VM_ENV_PREV_EP(ep); + depth++; + } +} -static VALUE eval_default_path; +static inline int +compute_isolated_depth_from_block(const struct rb_block *blk) +{ + return compute_isolated_depth_from_ep(vm_block_ep(blk)); +} static const rb_iseq_t * -eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, - const struct rb_block *base_block) +pm_eval_make_iseq(VALUE src, VALUE fname, int line, + const struct rb_block *base_block) { - const VALUE parser = rb_parser_new(); const rb_iseq_t *const parent = vm_block_iseq(base_block); - rb_iseq_t *iseq = NULL; - rb_ast_t *ast; - int isolated_depth = 0; + const rb_iseq_t *iseq = parent; + VALUE name = rb_fstring_lit("<compiled>"); - // Conditionally enable coverage depending on the current mode: - VALUE coverage_enabled = RBOOL(rb_get_coverage_mode() & COVERAGE_TARGET_EVAL); + int coverage_enabled = ((rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0) ? 1 : 0; + int isolated_depth = compute_isolated_depth_from_block(base_block); - { - int depth = 1; - const VALUE *ep = vm_block_ep(base_block); + if (!fname) { + fname = rb_source_location(&line); + } - while (1) { - if (VM_ENV_FLAGS(ep, VM_ENV_FLAG_ISOLATED)) { - isolated_depth = depth; - break; + if (!UNDEF_P(fname)) { + if (!NIL_P(fname)) fname = rb_fstring(fname); + } + else { + fname = get_eval_default_path(); + coverage_enabled = 0; + } + + pm_parse_result_t result = { 0 }; + pm_options_line_set(&result.options, line); + result.node.coverage_enabled = coverage_enabled; + + // Cout scopes, one for each parent iseq, plus one for our local scope + int scopes_count = 0; + do { + scopes_count++; + } while ((iseq = ISEQ_BODY(iseq)->parent_iseq)); + pm_options_scopes_init(&result.options, scopes_count + 1); + + // Walk over the scope tree, adding known locals at the correct depths. The + // scope array should be deepest -> shallowest. so lower indexes in the + // scopes array refer to root nodes on the tree, and higher indexes are the + // leaf nodes. + iseq = parent; + rb_encoding *encoding = rb_enc_get(src); + +#define FORWARDING_POSITIONALS_CHR '*' +#define FORWARDING_POSITIONALS_STR "*" +#define FORWARDING_KEYWORDS_CHR ':' +#define FORWARDING_KEYWORDS_STR ":" +#define FORWARDING_BLOCK_CHR '&' +#define FORWARDING_BLOCK_STR "&" +#define FORWARDING_ALL_CHR '.' +#define FORWARDING_ALL_STR "." + + for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { + VALUE iseq_value = (VALUE)iseq; + int locals_count = ISEQ_BODY(iseq)->local_table_size; + + pm_options_scope_t *options_scope = &result.options.scopes[scopes_count - scopes_index - 1]; + pm_options_scope_init(options_scope, locals_count); + + uint8_t forwarding = PM_OPTIONS_SCOPE_FORWARDING_NONE; + + for (int local_index = 0; local_index < locals_count; local_index++) { + pm_string_t *scope_local = &options_scope->locals[local_index]; + ID local = ISEQ_BODY(iseq)->local_table[local_index]; + + if (rb_is_local_id(local)) { + VALUE name_obj = rb_id2str(local); + const char *name = RSTRING_PTR(name_obj); + size_t length = strlen(name); + + // Explicitly skip numbered parameters. These should not be sent + // into the eval. + if (length == 2 && name[0] == '_' && name[1] >= '1' && name[1] <= '9') { + continue; + } + + // Check here if this local can be represented validly in the + // encoding of the source string. If it _cannot_, then it should + // not be added to the constant pool as it would not be able to + // be referenced anyway. + if (rb_enc_str_coderange_scan(name_obj, encoding) == ENC_CODERANGE_BROKEN) { + continue; + } + + /* We need to duplicate the string because the Ruby string may + * be embedded so compaction could move the string and the pointer + * will change. */ + char *name_dup = xmalloc(length + 1); + strlcpy(name_dup, name, length + 1); + + RB_GC_GUARD(name_obj); + + pm_string_owned_init(scope_local, (uint8_t *) name_dup, length); } - else if (VM_ENV_LOCAL_P(ep)) { - break; + else if (local == idMULT) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_POSITIONALS; + pm_string_constant_init(scope_local, FORWARDING_POSITIONALS_STR, 1); } - ep = VM_ENV_PREV_EP(ep); - depth++; + else if (local == idPow) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_KEYWORDS; + pm_string_constant_init(scope_local, FORWARDING_KEYWORDS_STR, 1); + } + else if (local == idAnd) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_BLOCK; + pm_string_constant_init(scope_local, FORWARDING_BLOCK_STR, 1); + } + else if (local == idDot3) { + forwarding |= PM_OPTIONS_SCOPE_FORWARDING_ALL; + pm_string_constant_init(scope_local, FORWARDING_ALL_STR, 1); + } + } + + pm_options_scope_forwarding_set(options_scope, forwarding); + iseq = ISEQ_BODY(iseq)->parent_iseq; + + /* We need to GC guard the iseq because the code above malloc memory + * which could trigger a GC. Since we only use ISEQ_BODY, the compiler + * may optimize out the iseq local variable so we need to GC guard it. */ + RB_GC_GUARD(iseq_value); + } + + // Add our empty local scope at the very end of the array for our eval + // scope's locals. + pm_options_scope_init(&result.options.scopes[scopes_count], 0); + + VALUE script_lines; + VALUE error = pm_parse_string(&result, src, fname, ruby_vm_keep_script_lines ? &script_lines : NULL); + + // If the parse failed, clean up and raise. + if (error != Qnil) { + pm_parse_result_free(&result); + rb_exc_raise(error); + } + + // Create one scope node for each scope passed in, initialize the local + // lookup table with all the local variable information attached to the + // scope used by the parser. + pm_scope_node_t *node = &result.node; + iseq = parent; + + for (int scopes_index = 0; scopes_index < scopes_count; scopes_index++) { + pm_scope_node_t *parent_scope = ruby_xcalloc(1, sizeof(pm_scope_node_t)); + RUBY_ASSERT(parent_scope != NULL); + + pm_options_scope_t *options_scope = &result.options.scopes[scopes_count - scopes_index - 1]; + parent_scope->coverage_enabled = coverage_enabled; + parent_scope->parser = &result.parser; + parent_scope->index_lookup_table = st_init_numtable(); + + int locals_count = ISEQ_BODY(iseq)->local_table_size; + parent_scope->local_table_for_iseq_size = locals_count; + pm_constant_id_list_init(&parent_scope->locals); + + for (int local_index = 0; local_index < locals_count; local_index++) { + const pm_string_t *scope_local = &options_scope->locals[local_index]; + pm_constant_id_t constant_id = 0; + + const uint8_t *source = pm_string_source(scope_local); + size_t length = pm_string_length(scope_local); + + if (length > 0) { + if (length == 1) { + switch (*source) { + case FORWARDING_POSITIONALS_CHR: + constant_id = PM_CONSTANT_MULT; + break; + case FORWARDING_KEYWORDS_CHR: + constant_id = PM_CONSTANT_POW; + break; + case FORWARDING_BLOCK_CHR: + constant_id = PM_CONSTANT_AND; + break; + case FORWARDING_ALL_CHR: + constant_id = PM_CONSTANT_DOT3; + break; + default: + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + break; + } + } + else { + constant_id = pm_constant_pool_insert_constant(&result.parser.constant_pool, source, length); + } + + st_insert(parent_scope->index_lookup_table, (st_data_t) constant_id, (st_data_t) local_index); + } + + pm_constant_id_list_append(&parent_scope->locals, constant_id); } + + node->previous = parent_scope; + node = parent_scope; + iseq = ISEQ_BODY(iseq)->parent_iseq; } +#undef FORWARDING_POSITIONALS_CHR +#undef FORWARDING_POSITIONALS_STR +#undef FORWARDING_KEYWORDS_CHR +#undef FORWARDING_KEYWORDS_STR +#undef FORWARDING_BLOCK_CHR +#undef FORWARDING_BLOCK_STR +#undef FORWARDING_ALL_CHR +#undef FORWARDING_ALL_STR + + int error_state; + iseq = pm_iseq_new_eval(&result.node, name, fname, Qnil, line, parent, isolated_depth, &error_state); + + pm_scope_node_t *prev = result.node.previous; + while (prev) { + pm_scope_node_t *next = prev->previous; + pm_constant_id_list_free(&prev->locals); + pm_scope_node_destroy(prev); + ruby_xfree(prev); + prev = next; + } + + pm_parse_result_free(&result); + + // If there was an error, raise it after memory has been cleaned up + if (error_state) { + RUBY_ASSERT(iseq == NULL); + rb_jump_tag(error_state); + } + + rb_exec_event_hook_script_compiled(GET_EC(), iseq, src); + + return iseq; +} + +static const rb_iseq_t * +eval_make_iseq(VALUE src, VALUE fname, int line, + const struct rb_block *base_block) +{ + if (rb_ruby_prism_p()) { + return pm_eval_make_iseq(src, fname, line, base_block); + } + const VALUE parser = rb_parser_new(); + const rb_iseq_t *const parent = vm_block_iseq(base_block); + rb_iseq_t *iseq = NULL; + VALUE ast_value; + rb_ast_t *ast; + + int coverage_enabled = (rb_get_coverage_mode() & COVERAGE_TARGET_EVAL) != 0; + int isolated_depth = compute_isolated_depth_from_block(base_block); + if (!fname) { fname = rb_source_location(&line); } @@ -1643,24 +1942,19 @@ eval_make_iseq(VALUE src, VALUE fname, int line, const rb_binding_t *bind, if (!NIL_P(fname)) fname = rb_fstring(fname); } else { - fname = rb_fstring_lit("(eval)"); - if (!eval_default_path) { - eval_default_path = rb_fstring_lit("(eval)"); - rb_gc_register_mark_object(eval_default_path); - } - fname = eval_default_path; - coverage_enabled = Qfalse; + fname = get_eval_default_path(); + coverage_enabled = FALSE; } rb_parser_set_context(parser, parent, FALSE); - ast = rb_parser_compile_string_path(parser, fname, src, line); - if (ast->body.root) { - if (ast->body.compile_option == Qnil) { - ast->body.compile_option = rb_obj_hide(rb_ident_hash_new()); - } - rb_hash_aset(ast->body.compile_option, rb_sym_intern_ascii_cstr("coverage_enabled"), coverage_enabled); + if (ruby_vm_keep_script_lines) rb_parser_set_script_lines(parser); + ast_value = rb_parser_compile_string_path(parser, fname, src, line); + + ast = rb_ruby_ast_data_get(ast_value); - iseq = rb_iseq_new_eval(&ast->body, + if (ast->body.root) { + ast->body.coverage_enabled = coverage_enabled; + iseq = rb_iseq_new_eval(ast_value, ISEQ_BODY(parent)->location.label, fname, Qnil, line, parent, isolated_depth); @@ -1695,7 +1989,11 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li block.as.captured.code.iseq = cfp->iseq; block.type = block_type_iseq; - iseq = eval_make_iseq(src, file, line, NULL, &block); + // EP is not escaped to the heap here, but captured and reused by another frame. + // ZJIT's locals are incompatible with it unlike YJIT's, so invalidate the ISEQ for ZJIT. + rb_zjit_invalidate_no_ep_escape(cfp->iseq); + + iseq = eval_make_iseq(src, file, line, &block); if (!iseq) { rb_exc_raise(ec->errinfo); } @@ -1708,7 +2006,7 @@ eval_string_with_cref(VALUE self, VALUE src, rb_cref_t *cref, VALUE file, int li vm_set_eval_stack(ec, iseq, cref, &block); /* kick */ - return vm_exec(ec, true); + return vm_exec(ec); } static VALUE @@ -1716,7 +2014,7 @@ eval_string_with_scope(VALUE scope, VALUE src, VALUE file, int line) { rb_execution_context_t *ec = GET_EC(); rb_binding_t *bind = Check_TypedStruct(scope, &ruby_binding_data_type); - const rb_iseq_t *iseq = eval_make_iseq(src, file, line, bind, &bind->block); + const rb_iseq_t *iseq = eval_make_iseq(src, file, line, &bind->block); if (!iseq) { rb_exc_raise(ec->errinfo); } @@ -1729,7 +2027,7 @@ eval_string_with_scope(VALUE scope, VALUE src, VALUE file, int line) } /* kick */ - return vm_exec(ec, true); + return vm_exec(ec); } /* @@ -1758,7 +2056,7 @@ rb_f_eval(int argc, const VALUE *argv, VALUE self) int line = 1; rb_scan_args(argc, argv, "13", &src, &scope, &vfile, &vline); - SafeStringValue(src); + StringValue(src); if (argc >= 3) { StringValue(vfile); } @@ -1854,6 +2152,17 @@ rb_eval_string_wrap(const char *str, int *pstate) VALUE rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat) { + Check_Type(arg, T_ARRAY); + int argc = RARRAY_LENINT(arg); + const VALUE *argv = RARRAY_CONST_PTR(arg); + VALUE val = rb_eval_cmd_call_kw(cmd, argc, argv, kw_splat); + RB_GC_GUARD(arg); + return val; +} + +VALUE +rb_eval_cmd_call_kw(VALUE cmd, int argc, const VALUE *argv, int kw_splat) +{ enum ruby_tag_type state; volatile VALUE val = Qnil; /* OK */ rb_execution_context_t * volatile ec = GET_EC(); @@ -1861,8 +2170,7 @@ rb_eval_cmd_kw(VALUE cmd, VALUE arg, int kw_splat) EC_PUSH_TAG(ec); if ((state = EC_EXEC_TAG()) == TAG_NONE) { if (!RB_TYPE_P(cmd, T_STRING)) { - val = rb_funcallv_kw(cmd, idCall, RARRAY_LENINT(arg), - RARRAY_CONST_PTR(arg), kw_splat); + val = rb_funcallv_kw(cmd, idCall, argc, argv, kw_splat); } else { val = eval_string_with_cref(rb_vm_top_self(), cmd, NULL, 0, 0); @@ -1936,13 +2244,14 @@ rb_yield_refine_block(VALUE refinement, VALUE refinements) else { const struct rb_captured_block *captured = VM_BH_TO_ISEQ_BLOCK(block_handler); struct rb_captured_block new_captured = *captured; + const VALUE *const argv = &new_captured.self; /* dummy to suppress nonnull warning from gcc */ VALUE new_block_handler = VM_BH_FROM_ISEQ_BLOCK(&new_captured); const VALUE *ep = captured->ep; rb_cref_t *cref = vm_cref_push(ec, refinement, ep, TRUE, FALSE); CREF_REFINEMENTS_SET(cref, refinements); VM_FORCE_WRITE_SPECIAL_CONST(&VM_CF_LEP(ec->cfp)[VM_ENV_DATA_INDEX_SPECVAL], new_block_handler); new_captured.self = refinement; - return vm_yield_with_cref(ec, 0, NULL, RB_NO_KEYWORDS, cref, FALSE); + return vm_yield_with_cref(ec, 0, argv, RB_NO_KEYWORDS, cref, FALSE); } } @@ -1951,7 +2260,7 @@ static VALUE eval_under(VALUE self, int singleton, VALUE src, VALUE file, int line) { rb_cref_t *cref = vm_cref_push(GET_EC(), self, NULL, FALSE, singleton); - SafeStringValue(src); + StringValue(src); return eval_string_with_cref(self, src, cref, file, line); } @@ -1964,19 +2273,24 @@ specific_eval(int argc, const VALUE *argv, VALUE self, int singleton, int kw_spl return yield_under(self, singleton, 1, &self, kw_splat); } else { - VALUE file = Qundef; + VALUE file = Qnil; int line = 1; VALUE code; rb_check_arity(argc, 1, 3); code = argv[0]; - SafeStringValue(code); + StringValue(code); if (argc > 2) line = NUM2INT(argv[2]); if (argc > 1) { file = argv[1]; if (!NIL_P(file)) StringValue(file); } + + if (NIL_P(file)) { + file = get_eval_default_path(); + } + return eval_under(self, singleton, code, file, line); } } @@ -2387,11 +2701,31 @@ local_var_list_update(st_data_t *key, st_data_t *value, st_data_t arg, int exist return ST_CONTINUE; } +extern int rb_numparam_id_p(ID id); + static void local_var_list_add(const struct local_var_list *vars, ID lid) { - if (lid && rb_is_local_id(lid)) { - /* should skip temporary variable */ + /* should skip temporary variable */ + if (!lid) return; + if (!rb_is_local_id(lid)) return; + + /* should skip numbered parameters as well */ + if (rb_numparam_id_p(lid)) return; + + st_data_t idx = 0; /* tbl->num_entries */ + rb_hash_stlike_update(vars->tbl, ID2SYM(lid), local_var_list_update, idx); +} + +static void +numparam_list_add(const struct local_var_list *vars, ID lid) +{ + /* should skip temporary variable */ + if (!lid) return; + if (!rb_is_local_id(lid)) return; + + /* should skip anything but numbered parameters */ + if (rb_numparam_id_p(lid)) { st_data_t idx = 0; /* tbl->num_entries */ rb_hash_stlike_update(vars->tbl, ID2SYM(lid), local_var_list_update, idx); } @@ -2503,13 +2837,33 @@ rb_current_realfilepath(void) if (path == eval_default_path) { return Qnil; } - else { - return path; + + // [Feature #19755] implicit eval location is "(eval at #{__FILE__}:#{__LINE__})" + const long len = RSTRING_LEN(path); + if (len > EVAL_LOCATION_MARK_LEN+1) { + const char *const ptr = RSTRING_PTR(path); + if (ptr[len - 1] == ')' && + memcmp(ptr, "("EVAL_LOCATION_MARK, EVAL_LOCATION_MARK_LEN+1) == 0) { + return Qnil; + } } + + return path; } return Qnil; } +// Assert that an internal function is running and return +// the imemo object that represents it. +struct vm_ifunc * +rb_current_ifunc(void) +{ + // Search VM_FRAME_MAGIC_IFUNC to see ifunc imemos put on the iseq field. + VALUE ifunc = (VALUE)GET_EC()->cfp->iseq; + RUBY_ASSERT_ALWAYS(imemo_type_p(ifunc, imemo_ifunc)); + return (struct vm_ifunc *)ifunc; +} + void Init_vm_eval(void) { @@ -2551,5 +2905,3 @@ Init_vm_eval(void) id_tag = rb_intern_const("tag"); id_value = rb_intern_const("value"); } - -#endif /* #ifndef MJIT_HEADER */ |
