summaryrefslogtreecommitdiff
path: root/rjit.c
diff options
context:
space:
mode:
authorKJ Tsanaktsidis <kj@kjtsanaktsidis.id.au>2023-11-19 22:54:57 +1100
committerKoichi Sasada <ko1@atdot.net>2023-12-10 15:00:37 +0900
commitf8effa209adb3ce050c100ffaffe6f3cc1508185 (patch)
treec0a672d5917a9917910679504d0f8b2d450088f7 /rjit.c
parentaecbd66742f43ccfcac04ca4143fcc68ad834320 (diff)
Change the semantics of rb_postponed_job_register
Our current implementation of rb_postponed_job_register suffers from some safety issues that can lead to interpreter crashes (see bug #1991). Essentially, the issue is that jobs can be called with the wrong arguments. We made two attempts to fix this whilst keeping the promised semantics, but: * The first one involved masking/unmasking when flushing jobs, which was believed to be too expensive * The second one involved a lock-free, multi-producer, single-consumer ringbuffer, which was too complex The critical insight behind this third solution is that essentially the only user of these APIs are a) internal, or b) profiling gems. For a), none of the usages actually require variable data; they will work just fine with the preregistration interface. For b), generally profiling gems only call a single callback with a single piece of data (which is actually usually just zero) for the life of the program. The ringbuffer is complex because it needs to support multi-word inserts of job & data (which can't be atomic); but nobody actually even needs that functionality, really. So, this comit: * Introduces a pre-registration API for jobs, with a GVL-requiring rb_postponed_job_prereigster, which returns a handle which can be used with an async-signal-safe rb_postponed_job_trigger. * Deprecates rb_postponed_job_register (and re-implements it on top of the preregister function for compatability) * Moves all the internal usages of postponed job register pre-registration
Diffstat (limited to 'rjit.c')
-rw-r--r--rjit.c9
1 files changed, 8 insertions, 1 deletions
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);