summaryrefslogtreecommitdiff
path: root/zjit/src/payload.rs
diff options
context:
space:
mode:
Diffstat (limited to 'zjit/src/payload.rs')
-rw-r--r--zjit/src/payload.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/zjit/src/payload.rs b/zjit/src/payload.rs
new file mode 100644
index 0000000000..807c33b8b7
--- /dev/null
+++ b/zjit/src/payload.rs
@@ -0,0 +1,135 @@
+use std::ffi::c_void;
+use std::ptr::NonNull;
+use crate::codegen::IseqCallRef;
+use crate::stats::CompileError;
+use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr};
+
+pub use crate::jit_frame::JITFrame;
+
+/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC.
+#[derive(Debug)]
+pub struct IseqPayload {
+ /// Type information of YARV instruction operands
+ pub profile: IseqProfile,
+ /// JIT code versions. Different versions should have different assumptions.
+ pub versions: Vec<IseqVersionRef>,
+ /// Whether a previous compilation of this ISEQ was invalidated due to
+ /// singleton class creation (violation of [`crate::hir::Invariant::NoSingletonClass`]).
+ pub was_invalidated_for_singleton_class_creation: bool,
+ /// Whether `self` is guaranteed to be a heap (non-immediate) object for this
+ /// ISEQ. Set at compile triggers (entry point / function stub hit) where the
+ /// owning class is known via the method entry, and consumed in `iseq_to_hir`
+ /// to type the `self`-producing instructions (`LoadSelf` / `SelfParam`
+ /// `LoadArg`) as `HeapBasicObject`. Defaults to `false` (the conservative
+ /// `BasicObject`) when the owner is unknown.
+ /// See [`crate::cruby::iseq_self_is_heap_object`].
+ pub self_is_heap_object: bool,
+}
+
+impl IseqPayload {
+ fn new() -> Self {
+ Self {
+ profile: IseqProfile::new(),
+ versions: vec![],
+ was_invalidated_for_singleton_class_creation: false,
+ self_is_heap_object: false,
+ }
+ }
+}
+
+/// JIT code version. When the same ISEQ is compiled with a different assumption, a new version is created.
+#[derive(Debug)]
+pub struct IseqVersion {
+ /// ISEQ pointer. Stored here to minimize the size of PatchPoint.
+ pub iseq: IseqPtr,
+
+ /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled.
+ pub status: IseqStatus,
+
+ /// GC offsets of the JIT code. These are the addresses of objects that need to be marked.
+ pub gc_offsets: Vec<CodePtr>,
+
+ /// JIT-to-JIT calls from the ISEQ. The IseqPayload's ISEQ is the caller of it.
+ pub outgoing: Vec<IseqCallRef>,
+
+ /// JIT-to-JIT calls to the ISEQ. The IseqPayload's ISEQ is the callee of it.
+ pub incoming: Vec<IseqCallRef>,
+}
+
+/// We use a raw pointer instead of Rc to save space for refcount
+pub type IseqVersionRef = NonNull<IseqVersion>;
+
+impl IseqVersion {
+ /// Check if this version was invalidated
+ pub fn is_invalidated(&self) -> bool {
+ self.status == IseqStatus::Invalidated
+ }
+
+ /// Allocate a new IseqVersion to be compiled
+ pub fn new(iseq: IseqPtr) -> IseqVersionRef {
+ let version = Self {
+ iseq,
+ status: IseqStatus::NotCompiled,
+ gc_offsets: vec![],
+ outgoing: vec![],
+ incoming: vec![],
+ };
+ let version_ptr = Box::into_raw(Box::new(version));
+ NonNull::new(version_ptr).expect("no null from Box")
+ }
+}
+
+/// Set of CodePtrs for an ISEQ
+#[derive(Clone, Debug, PartialEq)]
+pub struct IseqCodePtrs {
+ /// Entry for the interpreter
+ pub start_ptr: CodePtr,
+ /// Entries for JIT-to-JIT calls
+ pub jit_entry_ptrs: Vec<CodePtr>,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum IseqStatus {
+ Compiled(IseqCodePtrs),
+ CantCompile(CompileError),
+ NotCompiled,
+ Invalidated,
+}
+
+/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists.
+pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload {
+ type VoidPtr = *mut c_void;
+
+ unsafe {
+ let payload = rb_iseq_get_zjit_payload(iseq);
+ if payload.is_null() {
+ // Allocate a new payload with Box and transfer ownership to the GC.
+ // We drop the payload with Box::from_raw when the GC frees the ISEQ and calls us.
+ // NOTE(alan): Sometimes we read from an ISEQ without ever writing to it.
+ // We allocate in those cases anyways.
+ let new_payload = IseqPayload::new();
+ let new_payload = Box::into_raw(Box::new(new_payload));
+ rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr);
+
+ new_payload
+ } else {
+ payload as *mut IseqPayload
+ }
+ }
+}
+
+/// Get the payload object associated with an ISEQ. Create one if none exists.
+pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload {
+ let payload_non_null = get_or_create_iseq_payload_ptr(iseq);
+ payload_ptr_as_mut(payload_non_null)
+}
+
+/// Convert an IseqPayload pointer to a mutable reference. Only one reference
+/// should be kept at a time.
+pub fn payload_ptr_as_mut(payload_ptr: *mut IseqPayload) -> &'static mut IseqPayload {
+ // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have
+ // exclusive mutable access.
+ // Hmm, nothing seems to stop calling this on the same
+ // iseq twice, though, which violates aliasing rules.
+ unsafe { payload_ptr.as_mut() }.unwrap()
+}