rusty_racer 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/ext/rusty_racer/Cargo.toml +1 -1
- data/ext/rusty_racer/src/stack.rs +90 -22
- data/lib/rusty_racer/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65667fe42e623b8ca26d70810029cc49de133a7c127e2a829c897359c510ef79
|
|
4
|
+
data.tar.gz: 4b82947a2404a0cea7ee14dc9a80f88a3abbd28c79c788d24b6f8b5f7524af65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '084d1145e469c8d3422ec2402293e15d1e0fde2dd9d7436f16d5ec9655709207c809ca3e4d89f310ab5c91ae8b1449a5f4d80d2168d33f9c63217d2f14f09571'
|
|
7
|
+
data.tar.gz: ccfa7cd5c2d2e4cb847e15079a44bac3b1757410e2bf3f450ecf44524b8c0445b14c94d45f32d74688b4daec1cebe4decc74fb8c39455fc04bf02f4791a22c60
|
data/ext/rusty_racer/Cargo.toml
CHANGED
|
@@ -157,8 +157,9 @@ fn native_stack_bounds() -> (usize, usize) {
|
|
|
157
157
|
// a deep fiber recursion overflows the real stack and SEGVs the unmapped guard.
|
|
158
158
|
// (0, 0) if unknown.
|
|
159
159
|
//
|
|
160
|
-
// Cached per native thread, several regions at a time, because a miss
|
|
161
|
-
//
|
|
160
|
+
// Cached per native thread, several regions at a time, because a miss asks the
|
|
161
|
+
// OS for the whole memory map and costs far more than the op it serves: with ONE
|
|
162
|
+
// slot
|
|
162
163
|
// a workload that alternates between fibers missed on every single op, and an
|
|
163
164
|
// eval went from 1.2us to 31us as soon as two fibers took turns (measured on a
|
|
164
165
|
// 212-line maps; a Rails process has many times that). Ruby pools fiber stacks,
|
|
@@ -275,16 +276,16 @@ fn current_region_bounds_cached(addr: usize) -> (usize, usize) {
|
|
|
275
276
|
})
|
|
276
277
|
}
|
|
277
278
|
|
|
278
|
-
// The [start, end) of the
|
|
279
|
-
//
|
|
280
|
-
// only called on a cache miss (a new fiber).
|
|
279
|
+
// The [start, end) of the mapping containing `addr`, or (0, 0) when it can't be
|
|
280
|
+
// established. Only called on a cache miss (a new fiber), because it is slow.
|
|
281
281
|
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
282
|
+
// (0, 0) is the fail-safe answer, not an error: every caller falls back to a
|
|
283
|
+
// fixed budget below the SP, which is what this gem did on every platform before
|
|
284
|
+
// any of these lookups existed. So a platform without an implementation, or a
|
|
285
|
+
// lookup that returns something we can't vouch for, degrades to a smaller JS
|
|
286
|
+
// stack rather than to a wrong one.
|
|
287
|
+
//
|
|
288
|
+
// Linux reads /proc/self/maps fresh each time.
|
|
288
289
|
#[cfg(target_os = "linux")]
|
|
289
290
|
fn query_region_bounds(addr: usize) -> (usize, usize) {
|
|
290
291
|
use std::io::Read;
|
|
@@ -314,7 +315,73 @@ fn query_region_bounds(addr: usize) -> (usize, usize) {
|
|
|
314
315
|
(0, 0)
|
|
315
316
|
}
|
|
316
317
|
|
|
317
|
-
|
|
318
|
+
// macOS has no /proc, so ask the Mach VM directly. mach_vm_region reports the
|
|
319
|
+
// region containing `addr` — or, when `addr` sits in a hole, the next one ABOVE
|
|
320
|
+
// it — so the answer is checked for containment rather than trusted. It is also
|
|
321
|
+
// checked for being readable and writable: a stack always is, and a protection
|
|
322
|
+
// that isn't tells us we either walked into something that is not a stack or
|
|
323
|
+
// misread the reply, in which case the fixed-budget fallback is the safer answer.
|
|
324
|
+
#[cfg(target_os = "macos")]
|
|
325
|
+
fn query_region_bounds(addr: usize) -> (usize, usize) {
|
|
326
|
+
// Not in libc, which points at the `mach2` crate instead; these are the only
|
|
327
|
+
// entry points we need, so declare them here rather than take the dependency.
|
|
328
|
+
// (libc does carry mach_task_self_, but deprecated for the same reason.)
|
|
329
|
+
unsafe extern "C" {
|
|
330
|
+
static mach_task_self_: libc::mach_port_t;
|
|
331
|
+
fn mach_vm_region(
|
|
332
|
+
target_task: libc::vm_map_t,
|
|
333
|
+
address: *mut libc::mach_vm_address_t,
|
|
334
|
+
size: *mut libc::mach_vm_size_t,
|
|
335
|
+
flavor: libc::c_int,
|
|
336
|
+
info: *mut u32,
|
|
337
|
+
info_count: *mut libc::mach_msg_type_number_t,
|
|
338
|
+
object_name: *mut libc::mach_port_t,
|
|
339
|
+
) -> libc::kern_return_t;
|
|
340
|
+
fn mach_port_deallocate(
|
|
341
|
+
task: libc::mach_port_t,
|
|
342
|
+
name: libc::mach_port_t,
|
|
343
|
+
) -> libc::kern_return_t;
|
|
344
|
+
}
|
|
345
|
+
// VM_REGION_BASIC_INFO_64, and the width of vm_region_basic_info_data_64_t
|
|
346
|
+
// in u32s. Only `protection` (its first field) is read.
|
|
347
|
+
const VM_REGION_BASIC_INFO_64: libc::c_int = 9;
|
|
348
|
+
const VM_REGION_BASIC_INFO_COUNT_64: libc::mach_msg_type_number_t = 9;
|
|
349
|
+
const KERN_SUCCESS: libc::kern_return_t = 0;
|
|
350
|
+
const VM_PROT_READ_WRITE: u32 = 0x1 | 0x2;
|
|
351
|
+
|
|
352
|
+
unsafe {
|
|
353
|
+
let mut start = addr as libc::mach_vm_address_t;
|
|
354
|
+
let mut size: libc::mach_vm_size_t = 0;
|
|
355
|
+
let mut info = [0u32; VM_REGION_BASIC_INFO_COUNT_64 as usize];
|
|
356
|
+
let mut count = VM_REGION_BASIC_INFO_COUNT_64;
|
|
357
|
+
let mut object_name: libc::mach_port_t = 0;
|
|
358
|
+
let kr = mach_vm_region(
|
|
359
|
+
mach_task_self_,
|
|
360
|
+
&mut start,
|
|
361
|
+
&mut size,
|
|
362
|
+
VM_REGION_BASIC_INFO_64,
|
|
363
|
+
info.as_mut_ptr(),
|
|
364
|
+
&mut count,
|
|
365
|
+
&mut object_name,
|
|
366
|
+
);
|
|
367
|
+
// Documented to come back MACH_PORT_NULL for this flavor, but a leaked
|
|
368
|
+
// port name in a per-fiber path would accumulate, so hand it back.
|
|
369
|
+
if object_name != 0 {
|
|
370
|
+
mach_port_deallocate(mach_task_self_, object_name);
|
|
371
|
+
}
|
|
372
|
+
if kr != KERN_SUCCESS || size == 0 || info[0] & VM_PROT_READ_WRITE != VM_PROT_READ_WRITE {
|
|
373
|
+
return (0, 0);
|
|
374
|
+
}
|
|
375
|
+
let (lo, hi) = (start as usize, start.saturating_add(size) as usize);
|
|
376
|
+
if addr >= lo && addr < hi {
|
|
377
|
+
(lo, hi)
|
|
378
|
+
} else {
|
|
379
|
+
(0, 0) // `addr` was in a hole; this is the region after it
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
|
|
318
385
|
fn query_region_bounds(_addr: usize) -> (usize, usize) {
|
|
319
386
|
(0, 0)
|
|
320
387
|
}
|
|
@@ -337,8 +404,8 @@ pub(crate) static STACK_DEBUG: AtomicBool = AtomicBool::new(false);
|
|
|
337
404
|
// * Too low (below the real bottom) and a deep recursion grows past the
|
|
338
405
|
// mapped stack and SEGVs the unmapped guard page below it.
|
|
339
406
|
// So detect the stack by comparing the SP to the cached native bounds: on the
|
|
340
|
-
// native stack, anchor to its pthread bottom; on a fiber,
|
|
341
|
-
//
|
|
407
|
+
// native stack, anchor to its pthread bottom; on a fiber, look up the bottom of
|
|
408
|
+
// the mapping holding the SP (the fiber's real bottom — anchoring to
|
|
342
409
|
// SP minus a fixed guard punched through the bottom of Avo's small/deep Capybara
|
|
343
410
|
// fibers and SEGV'd).
|
|
344
411
|
//
|
|
@@ -433,8 +500,8 @@ impl<'a> StackScope<'a> {
|
|
|
433
500
|
let limit = if on_native {
|
|
434
501
|
nbottom + NATIVE_GUARD
|
|
435
502
|
} else {
|
|
436
|
-
// Anchor to the FIBER's real bottom (the
|
|
437
|
-
//
|
|
503
|
+
// Anchor to the FIBER's real bottom (the mapping holding the SP),
|
|
504
|
+
// not the SP: SP - fixed_guard can punch through the
|
|
438
505
|
// bottom of a small/deep fiber stack and SEGV (Avo's deep Capybara
|
|
439
506
|
// filter chain). Reserve FIBER_RESERVE above the bottom for the
|
|
440
507
|
// throw, but keep the limit below the SP so we don't false-overflow;
|
|
@@ -448,15 +515,16 @@ impl<'a> StackScope<'a> {
|
|
|
448
515
|
if region.0 != 0 {
|
|
449
516
|
(region.0 + FIBER_RESERVE).min(sp.saturating_sub(8 * 1024))
|
|
450
517
|
} else {
|
|
451
|
-
// Region unknown
|
|
452
|
-
//
|
|
453
|
-
//
|
|
518
|
+
// Region unknown. Both shipped platforms implement the lookup,
|
|
519
|
+
// so reaching this on one of them means the lookup FAILED (see
|
|
520
|
+
// query_region_bounds for what it refuses to vouch for) — worth
|
|
521
|
+
// chasing rather than assuming, since the budget here is only a
|
|
522
|
+
// guess: a fixed reserve below the SP, hoping the fiber is that
|
|
454
523
|
// deep. It usually is for an OUTERMOST fiber op, whose SP sits
|
|
455
524
|
// near the fiber's top, and much less reliably for a NESTED one,
|
|
456
525
|
// whose SP is already far down the fiber — there this can land
|
|
457
526
|
// below the fiber's mapped bottom, and V8 then grows through the
|
|
458
|
-
// guard page (SEGV) instead of throwing.
|
|
459
|
-
// means a mach_vm_region lookup for darwin.
|
|
527
|
+
// guard page (SEGV) instead of throwing.
|
|
460
528
|
sp.saturating_sub(64 * 1024)
|
|
461
529
|
}
|
|
462
530
|
};
|
|
@@ -519,7 +587,7 @@ impl<'a> StackScope<'a> {
|
|
|
519
587
|
// * a fiber we ENTERED (the enclosing op is elsewhere) — this op's
|
|
520
588
|
// `stack_top`, which keeps the range between two real stack pointers
|
|
521
589
|
// (marker..stack_top) and so guaranteed mapped. We can't use the
|
|
522
|
-
//
|
|
590
|
+
// looked-up region's top: that mapping isn't reliably contiguous, so
|
|
523
591
|
// the scan could still hit a hole below it.
|
|
524
592
|
// * a fiber we were ALREADY on (a nested op under a host callback that
|
|
525
593
|
// didn't switch stacks) — the enclosing op's start, which is above
|
data/lib/rusty_racer/version.rb
CHANGED