diff --git a/common.mk b/common.mk index c2bf43194a..fda660b69c 100644 --- a/common.mk +++ b/common.mk @@ -3002,6 +3002,7 @@ class.$(OBJEXT): {$(VPATH)}vm_debug.h class.$(OBJEXT): {$(VPATH)}vm_opts.h class.$(OBJEXT): {$(VPATH)}vm_sync.h compar.$(OBJEXT): $(hdrdir)/ruby/ruby.h +compar.$(OBJEXT): $(hdrdir)/ruby/version.h compar.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h compar.$(OBJEXT): $(top_srcdir)/internal/compar.h compar.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -3185,6 +3186,7 @@ compile.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h compile.$(OBJEXT): $(CCAN_DIR)/list/list.h compile.$(OBJEXT): $(CCAN_DIR)/str/str.h compile.$(OBJEXT): $(hdrdir)/ruby/ruby.h +compile.$(OBJEXT): $(hdrdir)/ruby/version.h compile.$(OBJEXT): $(top_srcdir)/internal/array.h compile.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h compile.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -3653,6 +3655,7 @@ cont.$(OBJEXT): $(CCAN_DIR)/list/list.h cont.$(OBJEXT): $(CCAN_DIR)/str/str.h cont.$(OBJEXT): $(hdrdir)/ruby.h cont.$(OBJEXT): $(hdrdir)/ruby/ruby.h +cont.$(OBJEXT): $(hdrdir)/ruby/version.h cont.$(OBJEXT): $(top_srcdir)/internal/array.h cont.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h cont.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -4238,6 +4241,7 @@ dir.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h dir.$(OBJEXT): $(CCAN_DIR)/list/list.h dir.$(OBJEXT): $(CCAN_DIR)/str/str.h dir.$(OBJEXT): $(hdrdir)/ruby/ruby.h +dir.$(OBJEXT): $(hdrdir)/ruby/version.h dir.$(OBJEXT): $(top_srcdir)/internal/array.h dir.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h dir.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -5739,14 +5743,22 @@ enc/utf_8.$(OBJEXT): {$(VPATH)}oniguruma.h enc/utf_8.$(OBJEXT): {$(VPATH)}regenc.h enc/utf_8.$(OBJEXT): {$(VPATH)}st.h enc/utf_8.$(OBJEXT): {$(VPATH)}subst.h +encoding.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h +encoding.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h +encoding.$(OBJEXT): $(CCAN_DIR)/list/list.h +encoding.$(OBJEXT): $(CCAN_DIR)/str/str.h encoding.$(OBJEXT): $(hdrdir)/ruby.h encoding.$(OBJEXT): $(hdrdir)/ruby/ruby.h +encoding.$(OBJEXT): $(hdrdir)/ruby/version.h +encoding.$(OBJEXT): $(top_srcdir)/internal/array.h +encoding.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h encoding.$(OBJEXT): $(top_srcdir)/internal/class.h encoding.$(OBJEXT): $(top_srcdir)/internal/compilers.h encoding.$(OBJEXT): $(top_srcdir)/internal/enc.h encoding.$(OBJEXT): $(top_srcdir)/internal/encoding.h encoding.$(OBJEXT): $(top_srcdir)/internal/error.h encoding.$(OBJEXT): $(top_srcdir)/internal/gc.h +encoding.$(OBJEXT): $(top_srcdir)/internal/imemo.h encoding.$(OBJEXT): $(top_srcdir)/internal/inits.h encoding.$(OBJEXT): $(top_srcdir)/internal/load.h encoding.$(OBJEXT): $(top_srcdir)/internal/object.h @@ -5757,6 +5769,7 @@ encoding.$(OBJEXT): $(top_srcdir)/internal/variable.h encoding.$(OBJEXT): $(top_srcdir)/internal/vm.h encoding.$(OBJEXT): $(top_srcdir)/internal/warnings.h encoding.$(OBJEXT): {$(VPATH)}assert.h +encoding.$(OBJEXT): {$(VPATH)}atomic.h encoding.$(OBJEXT): {$(VPATH)}backward/2/assume.h encoding.$(OBJEXT): {$(VPATH)}backward/2/attributes.h encoding.$(OBJEXT): {$(VPATH)}backward/2/bool.h @@ -5773,6 +5786,7 @@ encoding.$(OBJEXT): {$(VPATH)}defines.h encoding.$(OBJEXT): {$(VPATH)}encindex.h encoding.$(OBJEXT): {$(VPATH)}encoding.c encoding.$(OBJEXT): {$(VPATH)}encoding.h +encoding.$(OBJEXT): {$(VPATH)}id.h encoding.$(OBJEXT): {$(VPATH)}id_table.h encoding.$(OBJEXT): {$(VPATH)}intern.h encoding.$(OBJEXT): {$(VPATH)}internal.h @@ -5924,16 +5938,24 @@ encoding.$(OBJEXT): {$(VPATH)}internal/value_type.h encoding.$(OBJEXT): {$(VPATH)}internal/variable.h encoding.$(OBJEXT): {$(VPATH)}internal/warning_push.h encoding.$(OBJEXT): {$(VPATH)}internal/xmalloc.h +encoding.$(OBJEXT): {$(VPATH)}method.h encoding.$(OBJEXT): {$(VPATH)}missing.h +encoding.$(OBJEXT): {$(VPATH)}node.h encoding.$(OBJEXT): {$(VPATH)}onigmo.h encoding.$(OBJEXT): {$(VPATH)}oniguruma.h encoding.$(OBJEXT): {$(VPATH)}regenc.h encoding.$(OBJEXT): {$(VPATH)}ruby_assert.h +encoding.$(OBJEXT): {$(VPATH)}ruby_atomic.h +encoding.$(OBJEXT): {$(VPATH)}rubyparser.h encoding.$(OBJEXT): {$(VPATH)}shape.h encoding.$(OBJEXT): {$(VPATH)}st.h encoding.$(OBJEXT): {$(VPATH)}subst.h +encoding.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h +encoding.$(OBJEXT): {$(VPATH)}thread_native.h encoding.$(OBJEXT): {$(VPATH)}util.h +encoding.$(OBJEXT): {$(VPATH)}vm_core.h encoding.$(OBJEXT): {$(VPATH)}vm_debug.h +encoding.$(OBJEXT): {$(VPATH)}vm_opts.h encoding.$(OBJEXT): {$(VPATH)}vm_sync.h enum.$(OBJEXT): $(hdrdir)/ruby/ruby.h enum.$(OBJEXT): $(top_srcdir)/internal/array.h @@ -6139,6 +6161,7 @@ enumerator.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h enumerator.$(OBJEXT): $(CCAN_DIR)/list/list.h enumerator.$(OBJEXT): $(CCAN_DIR)/str/str.h enumerator.$(OBJEXT): $(hdrdir)/ruby/ruby.h +enumerator.$(OBJEXT): $(hdrdir)/ruby/version.h enumerator.$(OBJEXT): $(top_srcdir)/internal/array.h enumerator.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h enumerator.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -6352,6 +6375,7 @@ error.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h error.$(OBJEXT): $(CCAN_DIR)/list/list.h error.$(OBJEXT): $(CCAN_DIR)/str/str.h error.$(OBJEXT): $(hdrdir)/ruby/ruby.h +error.$(OBJEXT): $(hdrdir)/ruby/version.h error.$(OBJEXT): $(top_srcdir)/internal/array.h error.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h error.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -6567,6 +6591,7 @@ eval.$(OBJEXT): $(CCAN_DIR)/list/list.h eval.$(OBJEXT): $(CCAN_DIR)/str/str.h eval.$(OBJEXT): $(hdrdir)/ruby.h eval.$(OBJEXT): $(hdrdir)/ruby/ruby.h +eval.$(OBJEXT): $(hdrdir)/ruby/version.h eval.$(OBJEXT): $(top_srcdir)/internal/array.h eval.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h eval.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -6806,6 +6831,7 @@ file.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h file.$(OBJEXT): $(CCAN_DIR)/list/list.h file.$(OBJEXT): $(CCAN_DIR)/str/str.h file.$(OBJEXT): $(hdrdir)/ruby/ruby.h +file.$(OBJEXT): $(hdrdir)/ruby/version.h file.$(OBJEXT): $(top_srcdir)/internal/array.h file.$(OBJEXT): $(top_srcdir)/internal/class.h file.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -7010,6 +7036,7 @@ gc.$(OBJEXT): $(CCAN_DIR)/list/list.h gc.$(OBJEXT): $(CCAN_DIR)/str/str.h gc.$(OBJEXT): $(hdrdir)/ruby.h gc.$(OBJEXT): $(hdrdir)/ruby/ruby.h +gc.$(OBJEXT): $(hdrdir)/ruby/version.h gc.$(OBJEXT): $(top_srcdir)/internal/array.h gc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h gc.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -7457,6 +7484,7 @@ hash.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h hash.$(OBJEXT): $(CCAN_DIR)/list/list.h hash.$(OBJEXT): $(CCAN_DIR)/str/str.h hash.$(OBJEXT): $(hdrdir)/ruby/ruby.h +hash.$(OBJEXT): $(hdrdir)/ruby/version.h hash.$(OBJEXT): $(top_srcdir)/internal/array.h hash.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h hash.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -7839,6 +7867,7 @@ io.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h io.$(OBJEXT): $(CCAN_DIR)/list/list.h io.$(OBJEXT): $(CCAN_DIR)/str/str.h io.$(OBJEXT): $(hdrdir)/ruby/ruby.h +io.$(OBJEXT): $(hdrdir)/ruby/version.h io.$(OBJEXT): $(top_srcdir)/internal/array.h io.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h io.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -8061,6 +8090,7 @@ io_buffer.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h io_buffer.$(OBJEXT): $(CCAN_DIR)/list/list.h io_buffer.$(OBJEXT): $(CCAN_DIR)/str/str.h io_buffer.$(OBJEXT): $(hdrdir)/ruby/ruby.h +io_buffer.$(OBJEXT): $(hdrdir)/ruby/version.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/array.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bignum.h io_buffer.$(OBJEXT): $(top_srcdir)/internal/bits.h @@ -8252,6 +8282,7 @@ iseq.$(OBJEXT): $(CCAN_DIR)/list/list.h iseq.$(OBJEXT): $(CCAN_DIR)/str/str.h iseq.$(OBJEXT): $(hdrdir)/ruby.h iseq.$(OBJEXT): $(hdrdir)/ruby/ruby.h +iseq.$(OBJEXT): $(hdrdir)/ruby/version.h iseq.$(OBJEXT): $(top_srcdir)/internal/array.h iseq.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h iseq.$(OBJEXT): $(top_srcdir)/internal/bits.h @@ -8501,6 +8532,7 @@ load.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h load.$(OBJEXT): $(CCAN_DIR)/list/list.h load.$(OBJEXT): $(CCAN_DIR)/str/str.h load.$(OBJEXT): $(hdrdir)/ruby/ruby.h +load.$(OBJEXT): $(hdrdir)/ruby/version.h load.$(OBJEXT): $(top_srcdir)/internal/array.h load.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h load.$(OBJEXT): $(top_srcdir)/internal/bits.h @@ -9203,6 +9235,7 @@ marshal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h marshal.$(OBJEXT): $(CCAN_DIR)/list/list.h marshal.$(OBJEXT): $(CCAN_DIR)/str/str.h marshal.$(OBJEXT): $(hdrdir)/ruby/ruby.h +marshal.$(OBJEXT): $(hdrdir)/ruby/version.h marshal.$(OBJEXT): $(top_srcdir)/internal/array.h marshal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h marshal.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -10636,6 +10669,7 @@ object.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h object.$(OBJEXT): $(CCAN_DIR)/list/list.h object.$(OBJEXT): $(CCAN_DIR)/str/str.h object.$(OBJEXT): $(hdrdir)/ruby/ruby.h +object.$(OBJEXT): $(hdrdir)/ruby/version.h object.$(OBJEXT): $(top_srcdir)/internal/array.h object.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h object.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -11061,6 +11095,7 @@ parse.$(OBJEXT): $(CCAN_DIR)/list/list.h parse.$(OBJEXT): $(CCAN_DIR)/str/str.h parse.$(OBJEXT): $(hdrdir)/ruby.h parse.$(OBJEXT): $(hdrdir)/ruby/ruby.h +parse.$(OBJEXT): $(hdrdir)/ruby/version.h parse.$(OBJEXT): $(top_srcdir)/internal/array.h parse.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h parse.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -12318,6 +12353,7 @@ proc.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h proc.$(OBJEXT): $(CCAN_DIR)/list/list.h proc.$(OBJEXT): $(CCAN_DIR)/str/str.h proc.$(OBJEXT): $(hdrdir)/ruby/ruby.h +proc.$(OBJEXT): $(hdrdir)/ruby/version.h proc.$(OBJEXT): $(top_srcdir)/internal/array.h proc.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h proc.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -12530,6 +12566,7 @@ process.$(OBJEXT): $(CCAN_DIR)/list/list.h process.$(OBJEXT): $(CCAN_DIR)/str/str.h process.$(OBJEXT): $(hdrdir)/ruby.h process.$(OBJEXT): $(hdrdir)/ruby/ruby.h +process.$(OBJEXT): $(hdrdir)/ruby/version.h process.$(OBJEXT): $(top_srcdir)/internal/array.h process.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h process.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -12755,6 +12792,7 @@ ractor.$(OBJEXT): $(CCAN_DIR)/list/list.h ractor.$(OBJEXT): $(CCAN_DIR)/str/str.h ractor.$(OBJEXT): $(hdrdir)/ruby.h ractor.$(OBJEXT): $(hdrdir)/ruby/ruby.h +ractor.$(OBJEXT): $(hdrdir)/ruby/version.h ractor.$(OBJEXT): $(top_srcdir)/internal/array.h ractor.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h ractor.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -13181,6 +13219,7 @@ random.$(OBJEXT): {$(VPATH)}thread_native.h random.$(OBJEXT): {$(VPATH)}vm_core.h random.$(OBJEXT): {$(VPATH)}vm_opts.h range.$(OBJEXT): $(hdrdir)/ruby/ruby.h +range.$(OBJEXT): $(hdrdir)/ruby/version.h range.$(OBJEXT): $(top_srcdir)/internal/array.h range.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h range.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -15842,6 +15881,7 @@ shape.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h shape.$(OBJEXT): $(CCAN_DIR)/list/list.h shape.$(OBJEXT): $(CCAN_DIR)/str/str.h shape.$(OBJEXT): $(hdrdir)/ruby/ruby.h +shape.$(OBJEXT): $(hdrdir)/ruby/version.h shape.$(OBJEXT): $(top_srcdir)/internal/array.h shape.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h shape.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -16050,6 +16090,7 @@ signal.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h signal.$(OBJEXT): $(CCAN_DIR)/list/list.h signal.$(OBJEXT): $(CCAN_DIR)/str/str.h signal.$(OBJEXT): $(hdrdir)/ruby/ruby.h +signal.$(OBJEXT): $(hdrdir)/ruby/version.h signal.$(OBJEXT): $(top_srcdir)/internal/array.h signal.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h signal.$(OBJEXT): $(top_srcdir)/internal/compilers.h @@ -16255,6 +16296,7 @@ signal.$(OBJEXT): {$(VPATH)}vm_core.h signal.$(OBJEXT): {$(VPATH)}vm_debug.h signal.$(OBJEXT): {$(VPATH)}vm_opts.h sprintf.$(OBJEXT): $(hdrdir)/ruby/ruby.h +sprintf.$(OBJEXT): $(hdrdir)/ruby/version.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bignum.h sprintf.$(OBJEXT): $(top_srcdir)/internal/bits.h sprintf.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -16798,6 +16840,7 @@ string.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h string.$(OBJEXT): $(CCAN_DIR)/list/list.h string.$(OBJEXT): $(CCAN_DIR)/str/str.h string.$(OBJEXT): $(hdrdir)/ruby/ruby.h +string.$(OBJEXT): $(hdrdir)/ruby/version.h string.$(OBJEXT): $(top_srcdir)/internal/array.h string.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h string.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -17050,6 +17093,7 @@ struct.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h struct.$(OBJEXT): $(CCAN_DIR)/list/list.h struct.$(OBJEXT): $(CCAN_DIR)/str/str.h struct.$(OBJEXT): $(hdrdir)/ruby/ruby.h +struct.$(OBJEXT): $(hdrdir)/ruby/version.h struct.$(OBJEXT): $(top_srcdir)/internal/array.h struct.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h struct.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -17260,6 +17304,7 @@ symbol.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h symbol.$(OBJEXT): $(CCAN_DIR)/list/list.h symbol.$(OBJEXT): $(CCAN_DIR)/str/str.h symbol.$(OBJEXT): $(hdrdir)/ruby/ruby.h +symbol.$(OBJEXT): $(hdrdir)/ruby/version.h symbol.$(OBJEXT): $(top_srcdir)/internal/array.h symbol.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h symbol.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -17476,6 +17521,7 @@ thread.$(OBJEXT): $(CCAN_DIR)/list/list.h thread.$(OBJEXT): $(CCAN_DIR)/str/str.h thread.$(OBJEXT): $(hdrdir)/ruby.h thread.$(OBJEXT): $(hdrdir)/ruby/ruby.h +thread.$(OBJEXT): $(hdrdir)/ruby/version.h thread.$(OBJEXT): $(top_srcdir)/internal/array.h thread.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h thread.$(OBJEXT): $(top_srcdir)/internal/bits.h @@ -18269,6 +18315,7 @@ variable.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h variable.$(OBJEXT): $(CCAN_DIR)/list/list.h variable.$(OBJEXT): $(CCAN_DIR)/str/str.h variable.$(OBJEXT): $(hdrdir)/ruby/ruby.h +variable.$(OBJEXT): $(hdrdir)/ruby/version.h variable.$(OBJEXT): $(top_srcdir)/internal/array.h variable.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h variable.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -18691,6 +18738,7 @@ vm.$(OBJEXT): $(CCAN_DIR)/list/list.h vm.$(OBJEXT): $(CCAN_DIR)/str/str.h vm.$(OBJEXT): $(hdrdir)/ruby.h vm.$(OBJEXT): $(hdrdir)/ruby/ruby.h +vm.$(OBJEXT): $(hdrdir)/ruby/version.h vm.$(OBJEXT): $(top_srcdir)/internal/array.h vm.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm.$(OBJEXT): $(top_srcdir)/internal/bignum.h @@ -18944,6 +18992,7 @@ vm_backtrace.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/list/list.h vm_backtrace.$(OBJEXT): $(CCAN_DIR)/str/str.h vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/ruby.h +vm_backtrace.$(OBJEXT): $(hdrdir)/ruby/version.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/array.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h vm_backtrace.$(OBJEXT): $(top_srcdir)/internal/class.h @@ -19562,6 +19611,7 @@ vm_trace.$(OBJEXT): $(hdrdir)/ruby.h vm_trace.$(OBJEXT): $(hdrdir)/ruby/ruby.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/array.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/basic_operators.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/bits.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/class.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/compilers.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/gc.h @@ -19570,6 +19620,7 @@ vm_trace.$(OBJEXT): $(top_srcdir)/internal/imemo.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/serial.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/static_assert.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/symbol.h +vm_trace.$(OBJEXT): $(top_srcdir)/internal/thread.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/variable.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/vm.h vm_trace.$(OBJEXT): $(top_srcdir)/internal/warnings.h diff --git a/ext/-test-/postponed_job/postponed_job.c b/ext/-test-/postponed_job/postponed_job.c index fa57bef6f5..844412aebe 100644 --- a/ext/-test-/postponed_job/postponed_job.c +++ b/ext/-test-/postponed_job/postponed_job.c @@ -1,6 +1,29 @@ #include "ruby.h" #include "ruby/debug.h" +// We're testing deprecated things, don't print the compiler warnings +#if 0 + +#elif defined(_MSC_VER) +#pragma warning(disable : 4996) + +#elif defined(__INTEL_COMPILER) +#pragma warning(disable : 1786) + +#elif defined(__clang__) +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +#elif defined(__GNUC__) +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +#elif defined(__SUNPRO_CC) +#pragma error_messages (off,symdeprecated) + +#else +// :FIXME: improve here for your compiler. + +#endif + static int counter; static void @@ -58,6 +81,22 @@ pjob_call_direct(VALUE self, VALUE obj) return self; } +static void pjob_noop_callback(void *data) { } + +static VALUE +pjob_register_one_same(VALUE self) +{ + rb_gc_start(); + int r1 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); + int r2 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); + int r3 = rb_postponed_job_register_one(0, pjob_noop_callback, NULL); + VALUE ary = rb_ary_new(); + rb_ary_push(ary, INT2FIX(r1)); + rb_ary_push(ary, INT2FIX(r2)); + rb_ary_push(ary, INT2FIX(r3)); + return ary; +} + #ifdef HAVE_PTHREAD_H #include @@ -86,6 +125,57 @@ pjob_register_in_c_thread(VALUE self, VALUE obj) } #endif +static void +pjob_preregistered_callback(void *data) +{ + VALUE ary = (VALUE)data; + Check_Type(ary, T_ARRAY); + rb_ary_push(ary, INT2FIX(counter)); +} + +static VALUE +pjob_preregister_and_call_with_sleep(VALUE self, VALUE obj) +{ + counter = 0; + rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj); + counter++; + rb_postponed_job_trigger(h); + rb_thread_sleep(0); + counter++; + rb_postponed_job_trigger(h); + rb_thread_sleep(0); + counter++; + rb_postponed_job_trigger(h); + rb_thread_sleep(0); + return self; +} + +static VALUE +pjob_preregister_and_call_without_sleep(VALUE self, VALUE obj) +{ + counter = 0; + rb_postponed_job_handle_t h = rb_postponed_job_preregister(pjob_preregistered_callback, (void *)obj); + counter = 3; + rb_postponed_job_trigger(h); + rb_postponed_job_trigger(h); + rb_postponed_job_trigger(h); + return self; +} + +static VALUE +pjob_preregister_multiple_times(VALUE self) +{ + int r1 = rb_postponed_job_preregister(pjob_noop_callback, NULL); + int r2 = rb_postponed_job_preregister(pjob_noop_callback, NULL); + int r3 = rb_postponed_job_preregister(pjob_noop_callback, NULL); + VALUE ary = rb_ary_new(); + rb_ary_push(ary, INT2FIX(r1)); + rb_ary_push(ary, INT2FIX(r2)); + rb_ary_push(ary, INT2FIX(r3)); + return ary; + +} + void Init_postponed_job(VALUE self) { @@ -93,8 +183,12 @@ Init_postponed_job(VALUE self) rb_define_module_function(mBug, "postponed_job_register", pjob_register, 1); rb_define_module_function(mBug, "postponed_job_register_one", pjob_register_one, 1); rb_define_module_function(mBug, "postponed_job_call_direct", pjob_call_direct, 1); + rb_define_module_function(mBug, "postponed_job_register_one_same", pjob_register_one_same, 0); #ifdef HAVE_PTHREAD_H rb_define_module_function(mBug, "postponed_job_register_in_c_thread", pjob_register_in_c_thread, 1); #endif + rb_define_module_function(mBug, "postponed_job_preregister_and_call_with_sleep", pjob_preregister_and_call_with_sleep, 1); + rb_define_module_function(mBug, "postponed_job_preregister_and_call_without_sleep", pjob_preregister_and_call_without_sleep, 1); + rb_define_module_function(mBug, "postponed_job_preregister_multiple_times", pjob_preregister_multiple_times, 0); } diff --git a/ext/-test-/tracepoint/gc_hook.c b/ext/-test-/tracepoint/gc_hook.c index a3f4e7f68a..76417e4007 100644 --- a/ext/-test-/tracepoint/gc_hook.c +++ b/ext/-test-/tracepoint/gc_hook.c @@ -2,6 +2,11 @@ #include "ruby/debug.h" static int invoking; /* TODO: should not be global variable */ +static VALUE gc_start_proc; +static VALUE gc_end_proc; +static rb_postponed_job_handle_t invoking_proc_pjob; +static bool pjob_execute_gc_start_proc_p; +static bool pjob_execute_gc_end_proc_p; static VALUE invoke_proc_ensure(VALUE _) @@ -17,23 +22,35 @@ invoke_proc_begin(VALUE proc) } static void -invoke_proc(void *data) +invoke_proc(void *unused) { - VALUE proc = (VALUE)data; - invoking += 1; - rb_ensure(invoke_proc_begin, proc, invoke_proc_ensure, 0); + if (pjob_execute_gc_start_proc_p) { + pjob_execute_gc_start_proc_p = false; + invoking += 1; + rb_ensure(invoke_proc_begin, gc_start_proc, invoke_proc_ensure, 0); + } + if (pjob_execute_gc_end_proc_p) { + pjob_execute_gc_end_proc_p = false; + invoking += 1; + rb_ensure(invoke_proc_begin, gc_end_proc, invoke_proc_ensure, 0); + } } static void gc_start_end_i(VALUE tpval, void *data) { + rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); if (0) { - rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); fprintf(stderr, "trace: %s\n", rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START ? "gc_start" : "gc_end"); } if (invoking == 0) { - rb_postponed_job_register(0, invoke_proc, data); + if (rb_tracearg_event_flag(tparg) == RUBY_INTERNAL_EVENT_GC_START) { + pjob_execute_gc_start_proc_p = true; + } else { + pjob_execute_gc_end_proc_p = true; + } + rb_postponed_job_trigger(invoking_proc_pjob); } } @@ -54,8 +71,13 @@ set_gc_hook(VALUE module, VALUE proc, rb_event_flag_t event, const char *tp_str, if (!rb_obj_is_proc(proc)) { rb_raise(rb_eTypeError, "trace_func needs to be Proc"); } + if (event == RUBY_INTERNAL_EVENT_GC_START) { + gc_start_proc = proc; + } else { + gc_end_proc = proc; + } - tpval = rb_tracepoint_new(0, event, gc_start_end_i, (void *)proc); + tpval = rb_tracepoint_new(0, event, gc_start_end_i, 0); rb_ivar_set(module, tp_key, tpval); rb_tracepoint_enable(tpval); } @@ -82,4 +104,10 @@ Init_gc_hook(VALUE module) { rb_define_module_function(module, "after_gc_start_hook=", set_after_gc_start, 1); rb_define_module_function(module, "after_gc_exit_hook=", start_after_gc_exit, 1); + rb_gc_register_address(&gc_start_proc); + rb_gc_register_address(&gc_end_proc); + invoking_proc_pjob = rb_postponed_job_preregister(invoke_proc, NULL); + if (invoking_proc_pjob == POSTPONED_JOB_HANDLE_INVALID) { + rb_raise(rb_eStandardError, "could not preregister invoke_proc"); + } } diff --git a/ext/ripper/depend b/ext/ripper/depend index a2de09f280..a18b67abe3 100644 --- a/ext/ripper/depend +++ b/ext/ripper/depend @@ -566,6 +566,7 @@ ripper.o: $(hdrdir)/ruby/st.h ripper.o: $(hdrdir)/ruby/subst.h ripper.o: $(hdrdir)/ruby/thread_native.h ripper.o: $(hdrdir)/ruby/util.h +ripper.o: $(hdrdir)/ruby/version.h ripper.o: $(top_srcdir)/ccan/check_type/check_type.h ripper.o: $(top_srcdir)/ccan/container_of/container_of.h ripper.o: $(top_srcdir)/ccan/list/list.h diff --git a/ext/socket/depend b/ext/socket/depend index 675520c0b5..3db153bb1c 100644 --- a/ext/socket/depend +++ b/ext/socket/depend @@ -186,6 +186,7 @@ ancdata.o: $(hdrdir)/ruby/subst.h ancdata.o: $(hdrdir)/ruby/thread.h ancdata.o: $(hdrdir)/ruby/thread_native.h ancdata.o: $(hdrdir)/ruby/util.h +ancdata.o: $(hdrdir)/ruby/version.h ancdata.o: $(top_srcdir)/ccan/check_type/check_type.h ancdata.o: $(top_srcdir)/ccan/container_of/container_of.h ancdata.o: $(top_srcdir)/ccan/list/list.h @@ -394,6 +395,7 @@ basicsocket.o: $(hdrdir)/ruby/subst.h basicsocket.o: $(hdrdir)/ruby/thread.h basicsocket.o: $(hdrdir)/ruby/thread_native.h basicsocket.o: $(hdrdir)/ruby/util.h +basicsocket.o: $(hdrdir)/ruby/version.h basicsocket.o: $(top_srcdir)/ccan/check_type/check_type.h basicsocket.o: $(top_srcdir)/ccan/container_of/container_of.h basicsocket.o: $(top_srcdir)/ccan/list/list.h @@ -602,6 +604,7 @@ constants.o: $(hdrdir)/ruby/subst.h constants.o: $(hdrdir)/ruby/thread.h constants.o: $(hdrdir)/ruby/thread_native.h constants.o: $(hdrdir)/ruby/util.h +constants.o: $(hdrdir)/ruby/version.h constants.o: $(top_srcdir)/ccan/check_type/check_type.h constants.o: $(top_srcdir)/ccan/container_of/container_of.h constants.o: $(top_srcdir)/ccan/list/list.h @@ -811,6 +814,7 @@ ifaddr.o: $(hdrdir)/ruby/subst.h ifaddr.o: $(hdrdir)/ruby/thread.h ifaddr.o: $(hdrdir)/ruby/thread_native.h ifaddr.o: $(hdrdir)/ruby/util.h +ifaddr.o: $(hdrdir)/ruby/version.h ifaddr.o: $(top_srcdir)/ccan/check_type/check_type.h ifaddr.o: $(top_srcdir)/ccan/container_of/container_of.h ifaddr.o: $(top_srcdir)/ccan/list/list.h @@ -1019,6 +1023,7 @@ init.o: $(hdrdir)/ruby/subst.h init.o: $(hdrdir)/ruby/thread.h init.o: $(hdrdir)/ruby/thread_native.h init.o: $(hdrdir)/ruby/util.h +init.o: $(hdrdir)/ruby/version.h init.o: $(top_srcdir)/ccan/check_type/check_type.h init.o: $(top_srcdir)/ccan/container_of/container_of.h init.o: $(top_srcdir)/ccan/list/list.h @@ -1227,6 +1232,7 @@ ipsocket.o: $(hdrdir)/ruby/subst.h ipsocket.o: $(hdrdir)/ruby/thread.h ipsocket.o: $(hdrdir)/ruby/thread_native.h ipsocket.o: $(hdrdir)/ruby/util.h +ipsocket.o: $(hdrdir)/ruby/version.h ipsocket.o: $(top_srcdir)/ccan/check_type/check_type.h ipsocket.o: $(top_srcdir)/ccan/container_of/container_of.h ipsocket.o: $(top_srcdir)/ccan/list/list.h @@ -1435,6 +1441,7 @@ option.o: $(hdrdir)/ruby/subst.h option.o: $(hdrdir)/ruby/thread.h option.o: $(hdrdir)/ruby/thread_native.h option.o: $(hdrdir)/ruby/util.h +option.o: $(hdrdir)/ruby/version.h option.o: $(top_srcdir)/ccan/check_type/check_type.h option.o: $(top_srcdir)/ccan/container_of/container_of.h option.o: $(top_srcdir)/ccan/list/list.h @@ -1643,6 +1650,7 @@ raddrinfo.o: $(hdrdir)/ruby/subst.h raddrinfo.o: $(hdrdir)/ruby/thread.h raddrinfo.o: $(hdrdir)/ruby/thread_native.h raddrinfo.o: $(hdrdir)/ruby/util.h +raddrinfo.o: $(hdrdir)/ruby/version.h raddrinfo.o: $(top_srcdir)/ccan/check_type/check_type.h raddrinfo.o: $(top_srcdir)/ccan/container_of/container_of.h raddrinfo.o: $(top_srcdir)/ccan/list/list.h @@ -1851,6 +1859,7 @@ socket.o: $(hdrdir)/ruby/subst.h socket.o: $(hdrdir)/ruby/thread.h socket.o: $(hdrdir)/ruby/thread_native.h socket.o: $(hdrdir)/ruby/util.h +socket.o: $(hdrdir)/ruby/version.h socket.o: $(top_srcdir)/ccan/check_type/check_type.h socket.o: $(top_srcdir)/ccan/container_of/container_of.h socket.o: $(top_srcdir)/ccan/list/list.h @@ -2059,6 +2068,7 @@ sockssocket.o: $(hdrdir)/ruby/subst.h sockssocket.o: $(hdrdir)/ruby/thread.h sockssocket.o: $(hdrdir)/ruby/thread_native.h sockssocket.o: $(hdrdir)/ruby/util.h +sockssocket.o: $(hdrdir)/ruby/version.h sockssocket.o: $(top_srcdir)/ccan/check_type/check_type.h sockssocket.o: $(top_srcdir)/ccan/container_of/container_of.h sockssocket.o: $(top_srcdir)/ccan/list/list.h @@ -2267,6 +2277,7 @@ tcpserver.o: $(hdrdir)/ruby/subst.h tcpserver.o: $(hdrdir)/ruby/thread.h tcpserver.o: $(hdrdir)/ruby/thread_native.h tcpserver.o: $(hdrdir)/ruby/util.h +tcpserver.o: $(hdrdir)/ruby/version.h tcpserver.o: $(top_srcdir)/ccan/check_type/check_type.h tcpserver.o: $(top_srcdir)/ccan/container_of/container_of.h tcpserver.o: $(top_srcdir)/ccan/list/list.h @@ -2475,6 +2486,7 @@ tcpsocket.o: $(hdrdir)/ruby/subst.h tcpsocket.o: $(hdrdir)/ruby/thread.h tcpsocket.o: $(hdrdir)/ruby/thread_native.h tcpsocket.o: $(hdrdir)/ruby/util.h +tcpsocket.o: $(hdrdir)/ruby/version.h tcpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h tcpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h tcpsocket.o: $(top_srcdir)/ccan/list/list.h @@ -2683,6 +2695,7 @@ udpsocket.o: $(hdrdir)/ruby/subst.h udpsocket.o: $(hdrdir)/ruby/thread.h udpsocket.o: $(hdrdir)/ruby/thread_native.h udpsocket.o: $(hdrdir)/ruby/util.h +udpsocket.o: $(hdrdir)/ruby/version.h udpsocket.o: $(top_srcdir)/ccan/check_type/check_type.h udpsocket.o: $(top_srcdir)/ccan/container_of/container_of.h udpsocket.o: $(top_srcdir)/ccan/list/list.h @@ -2891,6 +2904,7 @@ unixserver.o: $(hdrdir)/ruby/subst.h unixserver.o: $(hdrdir)/ruby/thread.h unixserver.o: $(hdrdir)/ruby/thread_native.h unixserver.o: $(hdrdir)/ruby/util.h +unixserver.o: $(hdrdir)/ruby/version.h unixserver.o: $(top_srcdir)/ccan/check_type/check_type.h unixserver.o: $(top_srcdir)/ccan/container_of/container_of.h unixserver.o: $(top_srcdir)/ccan/list/list.h @@ -3099,6 +3113,7 @@ unixsocket.o: $(hdrdir)/ruby/subst.h unixsocket.o: $(hdrdir)/ruby/thread.h unixsocket.o: $(hdrdir)/ruby/thread_native.h unixsocket.o: $(hdrdir)/ruby/util.h +unixsocket.o: $(hdrdir)/ruby/version.h unixsocket.o: $(top_srcdir)/ccan/check_type/check_type.h unixsocket.o: $(top_srcdir)/ccan/container_of/container_of.h unixsocket.o: $(top_srcdir)/ccan/list/list.h diff --git a/gc.c b/gc.c index ce5ae34dc8..6050fd522a 100644 --- a/gc.c +++ b/gc.c @@ -952,6 +952,7 @@ typedef struct rb_objspace { #endif rb_darray(VALUE *) weak_references; + rb_postponed_job_handle_t finalize_deferred_pjob; } rb_objspace_t; @@ -1425,6 +1426,8 @@ PRINTF_ARGS(static void gc_report_body(int level, rb_objspace_t *objspace, const static const char *obj_info(VALUE obj); static const char *obj_type_name(VALUE obj); +static void gc_finalize_deferred(void *dmy); + /* * 1 - TSC (H/W Time Stamp Counter) * 2 - getrusage @@ -1906,6 +1909,10 @@ rb_objspace_alloc(void) rb_objspace_t *objspace = calloc1(sizeof(rb_objspace_t)); objspace->flags.measure_gc = 1; malloc_limit = gc_params.malloc_limit_min; + objspace->finalize_deferred_pjob = rb_postponed_job_preregister(gc_finalize_deferred, objspace); + if (objspace->finalize_deferred_pjob == POSTPONED_JOB_HANDLE_INVALID) { + rb_bug("Could not preregister postponed job for GC"); + } for (int i = 0; i < SIZE_POOL_COUNT; i++) { rb_size_pool_t *size_pool = &size_pools[i]; @@ -4527,9 +4534,8 @@ gc_finalize_deferred(void *dmy) static void gc_finalize_deferred_register(rb_objspace_t *objspace) { - if (rb_postponed_job_register_one(0, gc_finalize_deferred, objspace) == 0) { - rb_bug("gc_finalize_deferred_register: can't register finalizer."); - } + /* will enqueue a call to gc_finalize_deferred */ + rb_postponed_job_trigger(objspace->finalize_deferred_pjob); } static int pop_mark_stack(mark_stack_t *stack, VALUE *data); diff --git a/include/ruby/debug.h b/include/ruby/debug.h index e173f16e25..88bd721230 100644 --- a/include/ruby/debug.h +++ b/include/ruby/debug.h @@ -10,6 +10,7 @@ * modify this file, provided that the conditions mentioned in the * file COPYING are met. Consult the file for details. */ +#include "ruby/internal/attr/deprecated.h" #include "ruby/internal/attr/nonnull.h" #include "ruby/internal/attr/returns_nonnull.h" #include "ruby/internal/dllexport.h" @@ -615,48 +616,151 @@ VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg); /* * Postponed Job API - * rb_postponed_job_register and rb_postponed_job_register_one are - * async-signal-safe and used via SIGPROF by the "stackprof" RubyGem + * + * This API is designed to be called from contexts where it is not safe to run Ruby + * code (e.g. because they do not hold the GVL or because GC is in progress), and + * defer a callback to run in a context where it _is_ safe. The primary intended + * users of this API is for sampling profilers like the "stackprof" gem; these work + * by scheduling the periodic delivery of a SIGPROF signal, and inside the C-level + * signal handler, deferring a job to collect a Ruby backtrace when it is next safe + * to do so. + * + * Historically, this API provided two functions `rb_postponed_job_register` and + * `rb_postponed_job_register_one`, which claimed to be fully async-signal-safe and + * would call back the provided `func` and `data` at an appropriate time. However, + * these functions were subject to race conditions which could cause crashes when + * racing with Ruby's internal use of them. + * + * Therefore, this API has now been changed, and now requires that jobs scheduled + * from a signal handler context are pre-registered in advance into a fixed-size + * table. This table is quite small (it only has 32 entries on most systems) + * and so gems should generally only preregister one or two funcs. This process is + * managed by the `rb_postponed_job_preregister` and `rb_postponed_job_trigger` + * functions. + * + * We also provide the old `rb_postponed_job_register` and + * `rb_postponed_job_register_one` functions for backwards compatability, but with + * changed semantics; `rb_postponed_job_register` now behaves the same as + * `rb_postponed_job_register_once`. These changes should remain compatible with all + * of the observed in-the-wild usages of the postponed job APIs, which almost all + * use the _one API and pass `0` for data anyway. */ + /** * Type of postponed jobs. * - * @param[in,out] arg What was passed to rb_postponed_job_register(). + * @param[in,out] arg What was passed to `rb_postponed_job_preregister` */ typedef void (*rb_postponed_job_func_t)(void *arg); /** - * Registers a postponed job. + * The type of a handle returned from `rb_postponed_job_preregister` and + * passed to `rb_postponed_job_trigger` + */ +typedef unsigned int rb_postponed_job_handle_t; +#define POSTPONED_JOB_HANDLE_INVALID ((rb_postponed_job_handle_t)UINT_MAX) + +/** + * Pre-registers a func in Ruby's postponed job preregistration table, + * returning an opaque handle which can be used to trigger the job later. Generally, + * this function will be called during the initialization routine of an extension. * - * There are situations when running a ruby program is not possible. For - * instance when a program is in a signal handler; for another instance when - * the GC is busy. On such situations however, there might be needs to do - * something. We cannot but defer such operations until we are 100% sure it is - * safe to execute them. This mechanism is called postponed jobs. This - * function registers a new one. The registered job would eventually gets - * executed. + * The returned handle can be used later to call `rb_postponed_job_trigger`. This will + * cause Ruby to call back into the registered `func` with `data` at a later time, in + * a context where the GVL is held and it is safe to perform Ruby allocations. * - * @param[in] flags (Unused) reserved for future extensions. + * If the given func was already pre-registered, this method will overwrite the + * stored data with the newly passed data, and return the same handle instance as + * was previously returned. + * + * If this function is called concurrently with the same `func`, then the stored data + * could be the value from either call (but will definitely be one of them). + * + * If this function is called to update the data concurrently with a call to + * `rb_postponed_job_trigger` on the same handle, it's undefined whether `func` will + * be called with the old data or the new data. + * + * Although the current implementation of this method is in fact async-signal-safe and + * has defined semantics when called concurrently on the same `func`, a future Ruby + * version might require that this method be called under the GVL; thus, programs which + * aim to be forward-compatible should call this method whilst holding the GVL. + * + * @param[in] func The function to be pre-registered + * @param[in] data The data to be pre-registered + * @retval POSTPONED_JOB_HANDLE_INVALID The job table is full; this registration + * did not succeed and no further registration will do so for + * the lifetime of the program. + * @retval otherwise A handle which can be passed to `rb_postponed_job_trigger` + */ +rb_postponed_job_handle_t rb_postponed_job_preregister(rb_postponed_job_func_t func, void *data); + +/** + * Triggers a pre-registered job registered with rb_postponed_job_preregister, + * scheduling it for execution the next time the Ruby VM checks for interrupts. + * The context in which the job is called in holds the GVL and is safe to perform + * Ruby allocations within (i.e. it is not during GC). + * + * This method is async-signal-safe and can be called from any thread, at any + * time, including in signal handlers. + * + * If this method is called multiple times, Ruby will coalesce this into only + * one call to the job the next time it checks for interrupts. + * + * @params[in] h A handle returned from rb_postponed_job_preregister + */ +void rb_postponed_job_trigger(rb_postponed_job_handle_t h); + +/** + * Schedules the given `func` to be called with `data` when Ruby next checks for + * interupts. If this function is called multiple times in between Ruby checking + * for interrupts, then `func` will be called only once with the `data` vlaue from + * the first call to this function. + * + * Like `rb_postponed_job_trigger`, the context in which the job is called + * holds the GVL and can allocate Ruby objects. + * + * This method essentially has the same semantics as: + * + * ``` + * rb_postponed_job_trigger(rb_postponed_job_preregister(func, data)); + * ``` + * + * @note Prevoius versions of Ruby promised that the (`func`, `data`) pairs would + * be executed as many times as they were registered with this function; in + * reality this was always subject to race conditions and this function no + * longer provides this guarantee. Instead, we only promise that `func` will + * be called once. + * + * @deprecated This interface implies that arbitrarily many `func`'s can be enqueued + * over the lifetime of the program, whilst in reality the registration + * slots for postponed jobs are a finite resource. This is made clearer + * by the `rb_postponed_job_preregister` and `rb_postponed_job_trigger` + * functions, and a future version of Ruby might delete this function. + * + * @param[in] flags Unused and ignored. * @param[in] func Job body. * @param[in,out] data Passed as-is to `func`. - * @retval 0 Postponed job buffer is full. Failed. - * @retval otherwise Opaque return value. - * @post The passed job is postponed. + * @retval 0 Postponed job registration table is full. Failed. + * @retval 1 Registration succeeded. + * @post The passed job will run on the next interrupt check. */ + RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger")) int rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data); /** - * Identical to rb_postponed_job_register(), except it additionally checks for - * duplicated registration. In case the passed job is already in the postponed - * job buffer this function does nothing. + * Identical to `rb_postponed_job_register` * - * @param[in] flags (Unused) reserved for future extensions. + * @deprecated This is deprecated for the same reason as `rb_postponed_job_register` + * + * @param[in] flags Unused and ignored. * @param[in] func Job body. * @param[in,out] data Passed as-is to `func`. - * @retval 0 Postponed job buffer is full. Failed. - * @retval otherwise Opaque return value. + * @retval 0 Postponed job registration table is full. Failed. + * @retval 1 Registration succeeded. + * @post The passed job will run on the next interrupt check. */ + RBIMPL_ATTR_DEPRECATED(("use rb_postponed_job_preregister and rb_postponed_job_trigger")) int rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data); /** @} */ diff --git a/inits.c b/inits.c index 46530bb6d0..a114b780aa 100644 --- a/inits.c +++ b/inits.c @@ -22,7 +22,6 @@ rb_call_inits(void) { CALL(default_shapes); CALL(Thread_Mutex); - CALL(vm_postponed_job); CALL(RandomSeedCore); CALL(encodings); CALL(sym); diff --git a/rjit.c b/rjit.c index 4ce6af4d4b..1ce02649de 100644 --- a/rjit.c +++ b/rjit.c @@ -101,6 +101,9 @@ VALUE rb_rjit_raw_samples = 0; // Line numbers for --rjit-trace-exits VALUE rb_rjit_line_samples = 0; +// Postponed job handle for triggering rjit_iseq_update_references +static rb_postponed_job_handle_t rjit_iseq_update_references_pjob; + // A default threshold used to add iseq to JIT. #define DEFAULT_CALL_THRESHOLD 10 // Size of executable memory block in MiB. @@ -301,7 +304,7 @@ rb_rjit_iseq_update_references(struct rb_iseq_constant_body *const body) // Asynchronously hook the Ruby code to avoid allocation during GC.compact. // Using _one because it's too slow to invalidate all for each ISEQ. Thus // not giving an ISEQ pointer. - rb_postponed_job_register_one(0, rjit_iseq_update_references, NULL); + rb_postponed_job_trigger(rjit_iseq_update_references_pjob); } void @@ -429,6 +432,10 @@ rb_rjit_init(const struct rb_rjit_options *opts) rb_rjit_enabled = false; return; } + rjit_iseq_update_references_pjob = rb_postponed_job_preregister(rjit_iseq_update_references, NULL); + if (rjit_iseq_update_references_pjob == POSTPONED_JOB_HANDLE_INVALID) { + rb_bug("Could not preregister postponed job for RJIT"); + } rb_mRJITC = rb_const_get(rb_mRJIT, rb_intern("C")); VALUE rb_cRJITCompiler = rb_const_get(rb_mRJIT, rb_intern("Compiler")); rb_RJITCompiler = rb_funcall(rb_cRJITCompiler, rb_intern("new"), 0); diff --git a/test/-ext-/postponed_job/test_postponed_job.rb b/test/-ext-/postponed_job/test_postponed_job.rb index fee0172d11..6831ef7b37 100644 --- a/test/-ext-/postponed_job/test_postponed_job.rb +++ b/test/-ext-/postponed_job/test_postponed_job.rb @@ -2,34 +2,62 @@ require 'test/unit' require '-test-/postponed_job' -module Bug - def self.postponed_job_call_direct_wrapper(*args) - postponed_job_call_direct(*args) - end - - def self.postponed_job_register_wrapper(*args) - postponed_job_register(*args) - end -end - class TestPostponed_job < Test::Unit::TestCase - def test_register - direct, registered = [], [] + def test_preregister_and_trigger + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + require '-test-/postponed_job' + Bug.postponed_job_preregister_and_call_without_sleep(counters = []) + # i.e. rb_postponed_job_trigger performs coalescing + assert_equal([3], counters) - Bug.postponed_job_call_direct_wrapper(direct) - Bug.postponed_job_register_wrapper(registered) + # i.e. rb_postponed_job_trigger resets after interrupts are checked + Bug.postponed_job_preregister_and_call_with_sleep(counters = []) + assert_equal([1, 2, 3], counters) + RUBY + end - assert_equal([0], direct) - assert_equal([3], registered) + def test_multiple_preregistration + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + require '-test-/postponed_job' + handles = Bug.postponed_job_preregister_multiple_times + # i.e. rb_postponed_job_preregister returns the same handle if preregistered multiple times + assert_equal [handles[0]], handles.uniq + RUBY + end - Bug.postponed_job_register_one(ary = []) - assert_equal [1], ary + + def test_legacy_register + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + require '-test-/postponed_job' + direct, registered = [], [] + + Bug.postponed_job_call_direct(direct) + Bug.postponed_job_register(registered) + + assert_equal([0], direct) + assert_equal([3], registered) + + Bug.postponed_job_register_one(ary = []) + assert_equal [1], ary + RUBY + end + + def test_legacy_register_one_same + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + require '-test-/postponed_job' + # Registering the same job three times should result in three of the same handle + handles = Bug.postponed_job_register_one_same + assert_equal [handles[0]], handles.uniq + RUBY end if Bug.respond_to?(:postponed_job_register_in_c_thread) - def test_register_in_c_thread - assert Bug.postponed_job_register_in_c_thread(ary = []) - assert_equal [1], ary + def test_legacy_register_in_c_thread + assert_separately([], __FILE__, __LINE__, <<-'RUBY') + require '-test-/postponed_job' + assert Bug.postponed_job_register_in_c_thread(ary = []) + assert_equal [1], ary + RUBY end end end diff --git a/thread.c b/thread.c index 3e9493ae3c..1539153dac 100644 --- a/thread.c +++ b/thread.c @@ -4626,6 +4626,7 @@ rb_thread_atfork_internal(rb_thread_t *th, void (*atfork)(rb_thread_t *, const r rb_vm_living_threads_init(vm); rb_ractor_atfork(vm, th); + rb_vm_postponed_job_atfork(); /* may be held by RJIT threads in parent */ rb_native_mutex_initialize(&vm->workqueue_lock); diff --git a/vm.c b/vm.c index d7ae74a968..f247838764 100644 --- a/vm.c +++ b/vm.c @@ -3029,7 +3029,7 @@ ruby_vm_destruct(rb_vm_t *vm) st_free_table(vm->static_ext_inits); st_free_table(vm->ensure_rollback_table); - ruby_xfree(vm->postponed_job_buffer); + rb_vm_postponed_job_free(); st_free_table(vm->defined_module_hash); rb_id_table_free(vm->constant_cache); @@ -3077,7 +3077,6 @@ ruby_vm_destruct(rb_vm_t *vm) } size_t rb_vm_memsize_waiting_fds(struct ccan_list_head *waiting_fds); // thread.c -size_t rb_vm_memsize_postponed_job_buffer(void); // vm_trace.c size_t rb_vm_memsize_workqueue(struct ccan_list_head *workqueue); // vm_trace.c // Used for VM memsize reporting. Returns the size of the at_exit list by @@ -3136,7 +3135,7 @@ vm_memsize(const void *ptr) rb_st_memsize(vm->loaded_features_index) + rb_st_memsize(vm->loading_table) + rb_st_memsize(vm->ensure_rollback_table) + - rb_vm_memsize_postponed_job_buffer() + + rb_vm_memsize_postponed_job_queue() + rb_vm_memsize_workqueue(&vm->workqueue) + rb_st_memsize(vm->defined_module_hash) + vm_memsize_at_exit_list(vm->at_exit) + @@ -4180,8 +4179,9 @@ Init_BareVM(void) MEMZERO(th, rb_thread_t, 1); vm_init2(vm); - vm->objspace = rb_objspace_alloc(); + rb_vm_postponed_job_queue_init(vm); ruby_current_vm_ptr = vm; + vm->objspace = rb_objspace_alloc(); vm->negative_cme_table = rb_id_table_create(16); vm->overloaded_cme_table = st_init_numtable(); vm->constant_cache = rb_id_table_create(0); diff --git a/vm_core.h b/vm_core.h index cd720fd8cf..ef770ab441 100644 --- a/vm_core.h +++ b/vm_core.h @@ -726,9 +726,8 @@ typedef struct rb_vm_struct { /* relation table of ensure - rollback for callcc */ struct st_table *ensure_rollback_table; - /* postponed_job (async-signal-safe, NOT thread-safe) */ - struct rb_postponed_job_struct *postponed_job_buffer; - rb_atomic_t postponed_job_index; + /* postponed_job (async-signal-safe, and thread-safe) */ + struct rb_postponed_job_queue *postponed_job_queue; int src_encoding_index; @@ -2171,6 +2170,10 @@ rb_exec_event_hook_script_compiled(rb_execution_context_t *ec, const rb_iseq_t * } void rb_vm_trap_exit(rb_vm_t *vm); +void rb_vm_postponed_job_atfork(void); /* vm_trace.c */ +void rb_vm_postponed_job_free(void); /* vm_trace.c */ +size_t rb_vm_memsize_postponed_job_queue(void); /* vm_trace.c */ +void rb_vm_postponed_job_queue_init(rb_vm_t *vm); /* vm_trace.c */ RUBY_SYMBOL_EXPORT_BEGIN diff --git a/vm_trace.c b/vm_trace.c index 6114ef9d4e..dc4c4738b8 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -23,11 +23,15 @@ #include "eval_intern.h" #include "internal.h" +#include "internal/bits.h" #include "internal/class.h" +#include "internal/gc.h" #include "internal/hash.h" #include "internal/symbol.h" +#include "internal/thread.h" #include "iseq.h" #include "rjit.h" +#include "ruby/atomic.h" #include "ruby/debug.h" #include "vm_core.h" #include "ruby/ractor.h" @@ -1617,17 +1621,22 @@ Init_vm_trace(void) rb_undef_alloc_func(rb_cTracePoint); } -typedef struct rb_postponed_job_struct { - rb_postponed_job_func_t func; - void *data; -} rb_postponed_job_t; - -#define MAX_POSTPONED_JOB 1000 -#define MAX_POSTPONED_JOB_SPECIAL_ADDITION 24 +/* + * Ruby actually has two separate mechanisms for enqueueing work from contexts + * where it is not safe to run Ruby code, to run later on when it is safe. One + * is async-signal-safe but more limited, and accessed through the + * `rb_postponed_job_preregister` and `rb_postponed_job_trigger` functions. The + * other is more flexible but cannot be used in signal handlers, and is accessed + * through the `rb_workqueue_register` function. + * + * The postponed job functions form part of Ruby's extension API, but the + * workqueue functions are for internal use only. + */ struct rb_workqueue_job { struct ccan_list_node jnode; /* <=> vm->workqueue */ - rb_postponed_job_t job; + rb_postponed_job_func_t func; + void *data; }; // Used for VM memsize reporting. Returns the size of a list of rb_workqueue_job @@ -1645,110 +1654,6 @@ rb_vm_memsize_workqueue(struct ccan_list_head *workqueue) return size; } -// Used for VM memsize reporting. Returns the total size of the postponed job -// buffer that was allocated at initialization. -size_t -rb_vm_memsize_postponed_job_buffer(void) -{ - return sizeof(rb_postponed_job_t) * MAX_POSTPONED_JOB; -} - -void -Init_vm_postponed_job(void) -{ - rb_vm_t *vm = GET_VM(); - vm->postponed_job_buffer = ALLOC_N(rb_postponed_job_t, MAX_POSTPONED_JOB); - vm->postponed_job_index = 0; - /* workqueue is initialized when VM locks are initialized */ -} - -enum postponed_job_register_result { - PJRR_SUCCESS = 0, - PJRR_FULL = 1, - PJRR_INTERRUPTED = 2 -}; - -/* Async-signal-safe */ -static enum postponed_job_register_result -postponed_job_register(rb_execution_context_t *ec, rb_vm_t *vm, - unsigned int flags, rb_postponed_job_func_t func, void *data, rb_atomic_t max, rb_atomic_t expected_index) -{ - rb_postponed_job_t *pjob; - - if (expected_index >= max) return PJRR_FULL; /* failed */ - - if (ATOMIC_CAS(vm->postponed_job_index, expected_index, expected_index+1) == expected_index) { - pjob = &vm->postponed_job_buffer[expected_index]; - } - else { - return PJRR_INTERRUPTED; - } - - /* unused: pjob->flags = flags; */ - pjob->func = func; - pjob->data = data; - - RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec); - - return PJRR_SUCCESS; -} - -static rb_execution_context_t * -get_valid_ec(rb_vm_t *vm) -{ - rb_execution_context_t *ec = rb_current_execution_context(false); - if (ec == NULL) ec = rb_vm_main_ractor_ec(vm); - return ec; -} - -/* - * return 0 if job buffer is full - * Async-signal-safe - */ -int -rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data) -{ - rb_vm_t *vm = GET_VM(); - rb_execution_context_t *ec = get_valid_ec(vm); - - begin: - switch (postponed_job_register(ec, vm, flags, func, data, MAX_POSTPONED_JOB, vm->postponed_job_index)) { - case PJRR_SUCCESS : return 1; - case PJRR_FULL : return 0; - case PJRR_INTERRUPTED: goto begin; - default: rb_bug("unreachable"); - } -} - -/* - * return 0 if job buffer is full - * Async-signal-safe - */ -int -rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data) -{ - rb_vm_t *vm = GET_VM(); - rb_execution_context_t *ec = get_valid_ec(vm); - rb_postponed_job_t *pjob; - rb_atomic_t i, index; - - begin: - index = vm->postponed_job_index; - for (i=0; ipostponed_job_buffer[i]; - if (pjob->func == func) { - RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(ec); - return 2; - } - } - switch (postponed_job_register(ec, vm, flags, func, data, MAX_POSTPONED_JOB + MAX_POSTPONED_JOB_SPECIAL_ADDITION, index)) { - case PJRR_SUCCESS : return 1; - case PJRR_FULL : return 0; - case PJRR_INTERRUPTED: goto begin; - default: rb_bug("unreachable"); - } -} - /* * thread-safe and called from non-Ruby thread * returns FALSE on failure (ENOMEM), TRUE otherwise @@ -1760,8 +1665,8 @@ rb_workqueue_register(unsigned flags, rb_postponed_job_func_t func, void *data) rb_vm_t *vm = GET_VM(); if (!wq_job) return FALSE; - wq_job->job.func = func; - wq_job->job.data = data; + wq_job->func = func; + wq_job->data = data; rb_nativethread_lock_lock(&vm->workqueue_lock); ccan_list_add_tail(&vm->workqueue, &wq_job->jnode); @@ -1773,11 +1678,144 @@ rb_workqueue_register(unsigned flags, rb_postponed_job_func_t func, void *data) return TRUE; } +#define PJOB_PREREG_TABLE_SIZE (sizeof(rb_atomic_t) * CHAR_BIT) +/* pre-registered jobs table, for async-safe jobs */ +typedef struct rb_postponed_job_queue { + struct { + rb_postponed_job_func_t func; + void *data; + } prereg_table[PJOB_PREREG_TABLE_SIZE]; + /* Bits in this are set when the corresponding entry in prereg_table has non-zero + * triggered_count; i.e. somebody called rb_postponed_job_trigger */ + rb_atomic_t prereg_triggered_bitset; +} rb_postponed_job_queues_t; + +void +rb_vm_postponed_job_queue_init(rb_vm_t *vm) +{ + /* use mimmalloc; postponed job registration is a dependency of objspace, so this gets + * called _VERY_ early inside Init_BareVM */ + rb_postponed_job_queues_t *pjq = ruby_mimmalloc(sizeof(rb_postponed_job_queues_t)); + pjq->prereg_triggered_bitset = 0; + memset(pjq->prereg_table, 0, sizeof(pjq->prereg_table)); + vm->postponed_job_queue = pjq; +} + +static rb_execution_context_t * +get_valid_ec(rb_vm_t *vm) +{ + rb_execution_context_t *ec = rb_current_execution_context(false); + if (ec == NULL) ec = rb_vm_main_ractor_ec(vm); + return ec; +} + +void +rb_vm_postponed_job_atfork(void) +{ + rb_vm_t *vm = GET_VM(); + rb_postponed_job_queues_t *pjq = vm->postponed_job_queue; + /* make sure we set the interrupt flag on _this_ thread if we carried any pjobs over + * from the other side of the fork */ + if (pjq->prereg_triggered_bitset) { + RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm)); + } + +} + +/* Frees the memory managed by the postponed job infrastructure at shutdown */ +void +rb_vm_postponed_job_free(void) +{ + rb_vm_t *vm = GET_VM(); + ruby_xfree(vm->postponed_job_queue); + vm->postponed_job_queue = NULL; +} + +// Used for VM memsize reporting. Returns the total size of the postponed job +// queue infrastructure. +size_t +rb_vm_memsize_postponed_job_queue(void) +{ + return sizeof(rb_postponed_job_queues_t); +} + + +rb_postponed_job_handle_t +rb_postponed_job_preregister(rb_postponed_job_func_t func, void *data) +{ + /* The doc comments say that this function should be called under the GVL, because + * that is actually required to get the guarantee that "if a given (func, data) pair + * was already pre-registered, this method will return the same handle instance". + * + * However, the actual implementation here is called without the GVL, from inside + * rb_postponed_job_register, to support that legacy interface. In the presence + * of concurrent calls to both _preregister and _register functions on the same + * func, however, the data may get mixed up between them. */ + + rb_postponed_job_queues_t *pjq = GET_VM()->postponed_job_queue; + for (unsigned int i = 0; i < PJOB_PREREG_TABLE_SIZE; i++) { + /* Try and set this slot to equal `func` */ + rb_postponed_job_func_t existing_func = (rb_postponed_job_func_t)RUBY_ATOMIC_PTR_CAS(pjq->prereg_table[i], NULL, (void *)func); + if (existing_func == NULL || existing_func == func) { + /* Either this slot was NULL, and we set it to func, or, this slot was already equal to func. + * In either case, clobber the data with our data. Note that concurrent calls to + * rb_postponed_job_register with the same func & different data will result in either of the + * datas being written */ + RUBY_ATOMIC_PTR_EXCHANGE(pjq->prereg_table[i].data, data); + return (rb_postponed_job_handle_t)i; + } else { + /* Try the next slot if this one already has a func in it */ + continue; + } + } + + /* full */ + return POSTPONED_JOB_HANDLE_INVALID; +} + +void +rb_postponed_job_trigger(rb_postponed_job_handle_t h) +{ + rb_vm_t *vm = GET_VM(); + rb_postponed_job_queues_t *pjq = vm->postponed_job_queue; + + RUBY_ATOMIC_OR(pjq->prereg_triggered_bitset, (((rb_atomic_t)1UL) << h)); + RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(get_valid_ec(vm)); +} + + +static int +pjob_register_legacy_impl(unsigned int flags, rb_postponed_job_func_t func, void *data) +{ + /* We _know_ calling preregister from a signal handler like this is racy; what is + * and is not promised is very exhaustively documented in debug.h */ + rb_postponed_job_handle_t h = rb_postponed_job_preregister(func, data); + if (h == POSTPONED_JOB_HANDLE_INVALID) { + return 0; + } + rb_postponed_job_trigger(h); + return 1; +} + +int +rb_postponed_job_register(unsigned int flags, rb_postponed_job_func_t func, void *data) +{ + return pjob_register_legacy_impl(flags, func, data); +} + +int +rb_postponed_job_register_one(unsigned int flags, rb_postponed_job_func_t func, void *data) +{ + return pjob_register_legacy_impl(flags, func, data); +} + + void rb_postponed_job_flush(rb_vm_t *vm) { + rb_postponed_job_queues_t *pjq = GET_VM()->postponed_job_queue; rb_execution_context_t *ec = GET_EC(); - const rb_atomic_t block_mask = POSTPONED_JOB_INTERRUPT_MASK|TRAP_INTERRUPT_MASK; + const rb_atomic_t block_mask = POSTPONED_JOB_INTERRUPT_MASK | TRAP_INTERRUPT_MASK; volatile rb_atomic_t saved_mask = ec->interrupt_mask & block_mask; VALUE volatile saved_errno = ec->errinfo; struct ccan_list_head tmp; @@ -1788,26 +1826,31 @@ rb_postponed_job_flush(rb_vm_t *vm) ccan_list_append_list(&tmp, &vm->workqueue); rb_nativethread_lock_unlock(&vm->workqueue_lock); + rb_atomic_t prereg_triggered_bits = RUBY_ATOMIC_EXCHANGE(pjq->prereg_triggered_bitset, 0); + ec->errinfo = Qnil; /* mask POSTPONED_JOB dispatch */ ec->interrupt_mask |= block_mask; { EC_PUSH_TAG(ec); if (EC_EXEC_TAG() == TAG_NONE) { - rb_atomic_t index; - struct rb_workqueue_job *wq_job; - - while ((index = vm->postponed_job_index) > 0) { - if (ATOMIC_CAS(vm->postponed_job_index, index, index-1) == index) { - rb_postponed_job_t *pjob = &vm->postponed_job_buffer[index-1]; - (*pjob->func)(pjob->data); - } + /* execute postponed jobs */ + while (prereg_triggered_bits) { + unsigned int i = bit_length(prereg_triggered_bits) - 1; + prereg_triggered_bits ^= ((1UL) << i); /* toggle ith bit off */ + rb_postponed_job_func_t func = pjq->prereg_table[i].func; + void *data = pjq->prereg_table[i].data; + (func)(data); } + + /* execute workqueue jobs */ + struct rb_workqueue_job *wq_job; while ((wq_job = ccan_list_pop(&tmp, struct rb_workqueue_job, jnode))) { - rb_postponed_job_t pjob = wq_job->job; + rb_postponed_job_func_t func = wq_job->func; + void *data = wq_job->data; free(wq_job); - (pjob.func)(pjob.data); + (func)(data); } } EC_POP_TAG(); @@ -1816,7 +1859,8 @@ rb_postponed_job_flush(rb_vm_t *vm) ec->interrupt_mask &= ~(saved_mask ^ block_mask); ec->errinfo = saved_errno; - /* don't leak memory if a job threw an exception */ + /* If we threw an exception, there might be leftover workqueue items; carry them over + * to a subsequent execution of flush */ if (!ccan_list_empty(&tmp)) { rb_nativethread_lock_lock(&vm->workqueue_lock); ccan_list_prepend_list(&vm->workqueue, &tmp); @@ -1824,4 +1868,10 @@ rb_postponed_job_flush(rb_vm_t *vm) RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC()); } + /* likewise with any remaining-to-be-executed bits of the preregistered postponed + * job table */ + if (prereg_triggered_bits) { + RUBY_ATOMIC_OR(pjq->prereg_triggered_bitset, prereg_triggered_bits); + RUBY_VM_SET_POSTPONED_JOB_INTERRUPT(GET_EC()); + } }