forked from mmtk/mmtk-ruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathabi.rs
405 lines (354 loc) · 13.1 KB
/
abi.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
use crate::api::RubyMutator;
use crate::{upcalls, Ruby};
use mmtk::scheduler::GCWorker;
use mmtk::util::{Address, ObjectReference, VMMutatorThread, VMWorkerThread};
// For the C binding
pub const OBJREF_OFFSET: usize = 8;
pub const MIN_OBJ_ALIGN: usize = 8; // Even on 32-bit machine. A Ruby object is at least 40 bytes large.
pub const GC_THREAD_KIND_WORKER: libc::c_int = 1;
const HAS_MOVED_GIVTBL: usize = 1 << 63;
const HIDDEN_SIZE_MASK: usize = 0x0000FFFFFFFFFFFF;
// Should keep in sync with C code.
const RUBY_FL_EXIVAR: usize = 1 << 10;
// An opaque type for the C counterpart.
#[allow(non_camel_case_types)]
pub struct st_table;
/// Provide convenient methods for accessing Ruby objects.
/// TODO: Wrap C functions in `RubyUpcalls` as Rust-friendly methods.
pub struct RubyObjectAccess {
objref: ObjectReference,
}
impl RubyObjectAccess {
pub fn from_objref(objref: ObjectReference) -> Self {
Self { objref }
}
pub fn obj_start(&self) -> Address {
self.objref.to_raw_address().sub(Self::prefix_size())
}
pub fn payload_addr(&self) -> Address {
self.objref.to_raw_address()
}
pub fn suffix_addr(&self) -> Address {
self.objref.to_raw_address().add(self.payload_size())
}
pub fn obj_end(&self) -> Address {
self.suffix_addr() + Self::suffix_size()
}
fn hidden_field(&self) -> Address {
self.obj_start()
}
fn load_hidden_field(&self) -> usize {
unsafe { self.hidden_field().load::<usize>() }
}
fn update_hidden_field<F>(&self, f: F)
where
F: FnOnce(usize) -> usize,
{
let old_value = self.load_hidden_field();
let new_value = f(old_value);
unsafe {
self.hidden_field().store(new_value);
}
}
pub fn payload_size(&self) -> usize {
self.load_hidden_field() & HIDDEN_SIZE_MASK
}
pub fn set_payload_size(&self, size: usize) {
debug_assert!((size & HIDDEN_SIZE_MASK) == size);
self.update_hidden_field(|old| old & !HIDDEN_SIZE_MASK | size & HIDDEN_SIZE_MASK);
}
fn flags_field(&self) -> Address {
self.objref.to_raw_address()
}
pub fn load_flags(&self) -> usize {
unsafe { self.flags_field().load::<usize>() }
}
pub fn has_exivar_flag(&self) -> bool {
(self.load_flags() & RUBY_FL_EXIVAR) != 0
}
pub fn has_moved_givtbl(&self) -> bool {
(self.load_hidden_field() & HAS_MOVED_GIVTBL) != 0
}
pub fn set_has_moved_givtbl(&self) {
self.update_hidden_field(|old| old | HAS_MOVED_GIVTBL)
}
pub fn clear_has_moved_givtbl(&self) {
self.update_hidden_field(|old| old & !HAS_MOVED_GIVTBL)
}
pub fn prefix_size() -> usize {
// Currently, a hidden size field of word size is placed before each object.
OBJREF_OFFSET
}
pub fn suffix_size() -> usize {
// In RACTOR_CHECK_MODE, Ruby hides a field after each object to hold the Ractor ID.
unsafe { crate::BINDING_FAST_MUT.suffix_size }
}
pub fn object_size(&self) -> usize {
Self::prefix_size() + self.payload_size() + Self::suffix_size()
}
pub fn get_givtbl(&self) -> *mut libc::c_void {
if self.has_moved_givtbl() {
let moved_givtbl = crate::binding().moved_givtbl.lock().unwrap();
moved_givtbl
.get(&self.objref)
.unwrap_or_else(|| {
panic!(
"Object {} has HAS_MOVED_GIVTBL flag but not an entry in `moved_givtbl`",
self.objref
)
})
.gen_ivtbl
} else {
self.get_original_givtbl().unwrap_or_else(|| {
panic!(
"Object {} does not have HAS_MOVED_GIVTBL flag or original givtbl",
self.objref
)
})
}
}
pub fn get_original_givtbl(&self) -> Option<*mut libc::c_void> {
let addr = (upcalls().get_original_givtbl)(self.objref);
if addr.is_null() {
None
} else {
Some(addr)
}
}
}
type ObjectClosureFunction =
extern "C" fn(*mut libc::c_void, *mut libc::c_void, ObjectReference, bool) -> ObjectReference;
#[repr(C)]
pub struct ObjectClosure {
/// The function to be called from C.
pub c_function: ObjectClosureFunction,
/// The pointer to the Rust-level closure object.
pub rust_closure: *mut libc::c_void,
}
impl Default for ObjectClosure {
fn default() -> Self {
Self {
c_function: THE_UNREGISTERED_CLOSURE_FUNC,
rust_closure: std::ptr::null_mut(),
}
}
}
/// Rust doesn't require function items to have a unique address.
/// We therefore force using this particular constant.
///
/// See: https://rust-lang.github.io/rust-clippy/master/index.html#fn_address_comparisons
const THE_UNREGISTERED_CLOSURE_FUNC: ObjectClosureFunction = ObjectClosure::c_function_unregistered;
impl ObjectClosure {
/// Set this ObjectClosure temporarily to `visit_object`, and execute `f`. During the execution of
/// `f`, the Ruby VM may call this ObjectClosure. When the Ruby VM calls this ObjectClosure,
/// it effectively calls `visit_object`.
///
/// This method is intended to run Ruby VM code in `f` with temporarily modified behavior of
/// `rb_gc_mark`, `rb_gc_mark_movable` and `rb_gc_location`
///
/// Both `f` and `visit_object` may access and modify local variables in the environment where
/// `set_temporarily_and_run_code` called.
///
/// Note that this function is not reentrant. Don't call this function in either `callback` or
/// `f`.
pub fn set_temporarily_and_run_code<'env, T, F1, F2>(
&mut self,
mut visit_object: F1,
f: F2,
) -> T
where
F1: 'env + FnMut(&'static mut GCWorker<Ruby>, ObjectReference, bool) -> ObjectReference,
F2: 'env + FnOnce() -> T,
{
debug_assert!(
self.c_function == THE_UNREGISTERED_CLOSURE_FUNC,
"set_temporarily_and_run_code is recursively called."
);
self.c_function = Self::c_function_registered::<F1>;
self.rust_closure = &mut visit_object as *mut F1 as *mut libc::c_void;
let result = f();
*self = Default::default();
result
}
extern "C" fn c_function_registered<F>(
rust_closure: *mut libc::c_void,
worker: *mut libc::c_void,
object: ObjectReference,
pin: bool,
) -> ObjectReference
where
F: FnMut(&'static mut GCWorker<Ruby>, ObjectReference, bool) -> ObjectReference,
{
let rust_closure = unsafe { &mut *(rust_closure as *mut F) };
let worker = unsafe { &mut *(worker as *mut GCWorker<Ruby>) };
rust_closure(worker, object, pin)
}
extern "C" fn c_function_unregistered(
_rust_closure: *mut libc::c_void,
worker: *mut libc::c_void,
object: ObjectReference,
pin: bool,
) -> ObjectReference {
let worker = unsafe { &mut *(worker as *mut GCWorker<Ruby>) };
panic!(
"object_closure is not set. worker ordinal: {}, object: {}, pin: {}",
worker.ordinal, object, pin
);
}
}
#[repr(C)]
pub struct GCThreadTLS {
pub kind: libc::c_int,
pub gc_context: *mut libc::c_void,
pub object_closure: ObjectClosure,
}
impl GCThreadTLS {
fn new(kind: libc::c_int, gc_context: *mut libc::c_void) -> Self {
Self {
kind,
gc_context,
object_closure: Default::default(),
}
}
pub fn for_worker(gc_context: *mut GCWorker<Ruby>) -> Self {
Self::new(GC_THREAD_KIND_WORKER, gc_context as *mut libc::c_void)
}
pub fn from_vwt(vwt: VMWorkerThread) -> *mut GCThreadTLS {
unsafe { std::mem::transmute(vwt) }
}
/// Cast a pointer to `GCThreadTLS` to a ref, with assertion for null pointer.
///
/// # Safety
///
/// Has undefined behavior if `ptr` is invalid.
pub unsafe fn check_cast(ptr: *mut GCThreadTLS) -> &'static mut GCThreadTLS {
assert!(!ptr.is_null());
let result = &mut *ptr;
debug_assert!({
let kind = result.kind;
kind == GC_THREAD_KIND_WORKER
});
result
}
/// Cast a pointer to `VMWorkerThread` to a ref, with assertion for null pointer.
///
/// # Safety
///
/// Has undefined behavior if `ptr` is invalid.
pub unsafe fn from_vwt_check(vwt: VMWorkerThread) -> &'static mut GCThreadTLS {
let ptr = Self::from_vwt(vwt);
Self::check_cast(ptr)
}
#[allow(clippy::not_unsafe_ptr_arg_deref)] // `transmute` does not dereference pointer
pub fn to_vwt(ptr: *mut Self) -> VMWorkerThread {
unsafe { std::mem::transmute(ptr) }
}
/// Get a ref to `GCThreadTLS` from C-level thread-local storage, with assertion for null
/// pointer.
///
/// # Safety
///
/// Has undefined behavior if the pointer held in C-level TLS is invalid.
pub unsafe fn from_upcall_check() -> &'static mut GCThreadTLS {
let ptr = (upcalls().get_gc_thread_tls)();
Self::check_cast(ptr)
}
pub fn worker<'w>(&mut self) -> &'w mut GCWorker<Ruby> {
// NOTE: The returned ref points to the worker which does not have the same lifetime as self.
assert!(self.kind == GC_THREAD_KIND_WORKER);
unsafe { &mut *(self.gc_context as *mut GCWorker<Ruby>) }
}
}
#[repr(C)]
#[derive(Clone)]
pub struct RawVecOfObjRef {
pub ptr: *mut ObjectReference,
pub len: usize,
pub capa: usize,
}
impl RawVecOfObjRef {
pub fn from_vec(vec: Vec<ObjectReference>) -> RawVecOfObjRef {
// Note: Vec::into_raw_parts is unstable. We implement it manually.
let mut vec = std::mem::ManuallyDrop::new(vec);
let (ptr, len, capa) = (vec.as_mut_ptr(), vec.len(), vec.capacity());
RawVecOfObjRef { ptr, len, capa }
}
/// # Safety
///
/// This function turns raw pointer into a Vec without check.
pub unsafe fn into_vec(self) -> Vec<ObjectReference> {
Vec::from_raw_parts(self.ptr, self.len, self.capa)
}
}
impl From<Vec<ObjectReference>> for RawVecOfObjRef {
fn from(v: Vec<ObjectReference>) -> Self {
Self::from_vec(v)
}
}
#[repr(C)]
#[derive(Clone)]
pub struct RubyBindingOptions {
pub ractor_check_mode: bool,
pub suffix_size: usize,
}
#[repr(C)]
#[derive(Clone)]
pub struct RubyUpcalls {
pub init_gc_worker_thread: extern "C" fn(gc_worker_tls: *mut GCThreadTLS),
pub get_gc_thread_tls: extern "C" fn() -> *mut GCThreadTLS,
pub is_mutator: extern "C" fn() -> bool,
pub stop_the_world: extern "C" fn(tls: VMWorkerThread),
pub resume_mutators: extern "C" fn(tls: VMWorkerThread),
pub block_for_gc: extern "C" fn(tls: VMMutatorThread),
pub number_of_mutators: extern "C" fn() -> usize,
pub get_mutators: extern "C" fn(
visit_mutator: extern "C" fn(*mut RubyMutator, *mut libc::c_void),
data: *mut libc::c_void,
),
pub scan_vm_roots: extern "C" fn(),
pub scan_finalizer_tbl_roots: extern "C" fn(),
pub scan_end_proc_roots: extern "C" fn(),
pub scan_global_tbl_roots: extern "C" fn(),
pub scan_obj_to_id_tbl_roots: extern "C" fn(),
pub scan_misc_roots: extern "C" fn(),
pub scan_final_jobs_roots: extern "C" fn(),
pub scan_roots_in_mutator_thread:
extern "C" fn(mutator_tls: VMMutatorThread, worker_tls: VMWorkerThread),
pub scan_object_ruby_style: extern "C" fn(object: ObjectReference),
pub call_gc_mark_children: extern "C" fn(object: ObjectReference),
pub call_obj_free: extern "C" fn(object: ObjectReference),
pub cleanup_generic_iv_tbl: extern "C" fn(),
pub get_original_givtbl: extern "C" fn(object: ObjectReference) -> *mut libc::c_void,
pub move_givtbl: extern "C" fn(old_objref: ObjectReference, new_objref: ObjectReference),
pub vm_live_bytes: extern "C" fn() -> usize,
pub is_no_longer_ppp: extern "C" fn(object: ObjectReference) -> bool,
pub update_frozen_strings_table: extern "C" fn(),
pub update_finalizer_table: extern "C" fn(),
pub update_obj_id_tables: extern "C" fn(),
pub update_global_symbols_table: extern "C" fn(),
pub update_overloaded_cme_table: extern "C" fn(),
pub update_ci_table: extern "C" fn(),
pub get_frozen_strings_table: extern "C" fn() -> *mut st_table,
pub get_finalizer_table: extern "C" fn() -> *mut st_table,
pub get_obj_id_tables: extern "C" fn() -> *mut st_table,
pub get_global_symbols_table: extern "C" fn() -> *mut st_table,
pub get_overloaded_cme_table: extern "C" fn() -> *mut st_table,
pub get_ci_table: extern "C" fn() -> *mut st_table,
pub st_get_size_info: extern "C" fn(
table: *const st_table,
entries_start: *mut libc::size_t,
entries_bound: *mut libc::size_t,
bins_num: *mut libc::size_t,
),
pub st_update_entries_range: extern "C" fn(
table: *mut st_table,
begin: libc::size_t,
end: libc::size_t,
weak_keys: bool,
weak_records: bool,
forward: bool,
),
pub st_update_bins_range:
extern "C" fn(table: *mut st_table, begin: libc::size_t, end: libc::size_t),
}
unsafe impl Sync for RubyUpcalls {}