Skip to content

Commit 511e667

Browse files
committed
Temporary fix for the Julia GC
This addresses a problem where the Julia GC during a partial sweep frees some, but not all objects of an unreachable data structure. If an address to a remaining object of such a data structure is still randomly hit during conservative stack scanning, it may erroneously try to mark the deallocated objects, too. This temporary fix works by validating each pointer before it is marked. Because this is potentially expensive, the eventual solution should fix the root cause, which is conservative stack scanning resurrecting pointers to dead objects that have been freed during a partial collection.
1 parent 65993f6 commit 511e667

1 file changed

Lines changed: 54 additions & 17 deletions

File tree

src/julia_gc.c

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,13 @@
3737
** Various options controlling special features of the Julia GC code follow
3838
*/
3939

40-
// if DISABLE_BIGVAL_TRACKING is defined, we don't track the location of
41-
// large bags; this speeds up some things, but may expose bugs in GAP code
42-
// which incorrectly holds pointers into bags over a GC.
43-
// #define DISABLE_BIGVAL_TRACKING
40+
// if SCAN_STACK_FOR_MPTRS_ONLY is defined, stack scanning will only
41+
// look for references to master pointers, but not bags themselves.
42+
// This should be safe, as Gasman uses the same mechanism, but it is
43+
// at least theoretically possible that an optimizing compiler will
44+
// remove the last master pointer reference before the last reference
45+
// to the bag itself.
46+
#define SCAN_STACK_FOR_MPTRS_ONLY
4447

4548
// if REQUIRE_PRECISE_MARKING is defined, we assume that all marking
4649
// functions are precise, i.e., they only invoke MarkBag on valid bags,
@@ -54,6 +57,12 @@
5457
// if MARKING_STRESS_TEST is defined, we stress test the TryMark code
5558
// #define MARKING_STRESS_TEST
5659

60+
// if VALIDATE_MARKING is defined, the program is aborted if we ever
61+
// encounter a reference during marking that does not meet additional
62+
// validation criteria. These tests are compararively expensive and
63+
// should not be enabled by default.
64+
// #define VALIDATE_MARKING
65+
5766

5867
/****************************************************************************
5968
**
@@ -124,7 +133,7 @@ static void JFinalizer(jl_value_t * obj)
124133
TabFreeFuncBags[tnum]((Bag)&contents);
125134
}
126135

127-
#if !defined(DISABLE_BIGVAL_TRACKING)
136+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
128137

129138
/****************************************************************************
130139
**
@@ -196,7 +205,7 @@ static inline void * align_ptr(void * p)
196205
return (void *)u;
197206
}
198207

199-
#if !defined(DISABLE_BIGVAL_TRACKING)
208+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
200209

201210
typedef struct treap_t {
202211
struct treap_t *left, *right;
@@ -403,7 +412,7 @@ static UInt StackAlignBags;
403412
static Bag * GapStackBottom;
404413
static jl_ptls_t JuliaTLS, SaveTLS;
405414
static size_t max_pool_obj_size;
406-
#if !defined(DISABLE_BIGVAL_TRACKING)
415+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
407416
static size_t bigval_startoffset;
408417
#endif
409418
static UInt YoungRef;
@@ -448,11 +457,29 @@ void InitMarkFuncBags(UInt type, TNumMarkFuncBags mark_func)
448457
TabMarkFuncBags[type] = mark_func;
449458
}
450459

460+
static inline int JMarkGapObjSafe(void * obj)
461+
{
462+
// only traverse objects internally used by GAP
463+
void *ty = jl_typeof(obj);
464+
if (ty != datatype_mptr && ty != datatype_bag
465+
&& ty != datatype_largebag && ty != jl_weakref_type)
466+
return 0;
467+
return jl_gc_mark_queue_obj(JuliaTLS, (jl_value_t *)obj);
468+
}
469+
451470
static inline int JMark(void * obj)
452471
{
472+
#ifdef VALIDATE_MARKING
473+
jl_value_t *ty = jl_typeof(obj);
474+
if (jl_gc_internal_obj_base_ptr(ty) != ty)
475+
abort();
476+
if (!jl_typeis(ty, jl_datatype_type))
477+
abort();
478+
#endif
453479
return jl_gc_mark_queue_obj(JuliaTLS, (jl_value_t *)obj);
454480
}
455481

482+
456483
// Overview of conservative stack scanning
457484
//
458485
// A key aspect of conservative marking is that (1) we need to determine
@@ -482,7 +509,7 @@ static void TryMark(void * p)
482509
{
483510
jl_value_t * p2 = jl_gc_internal_obj_base_ptr(p);
484511
if (!p2) {
485-
#if !defined(DISABLE_BIGVAL_TRACKING)
512+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
486513
// It is possible for p to point past the end of
487514
// the object, so we subtract one word from the
488515
// address. This is safe, as the object is preceded
@@ -513,7 +540,12 @@ static void TryMark(void * p)
513540
#endif
514541
}
515542
if (p2) {
516-
JMark(p2);
543+
#ifdef SCAN_STACK_FOR_MPTRS_ONLY
544+
if (jl_typeis(p2, datatype_mptr))
545+
JMark(p2);
546+
#else
547+
JMarkGapObjSafe(p2);
548+
#endif
517549
}
518550
}
519551

@@ -575,7 +607,7 @@ static void GapRootScanner(int full)
575607
{
576608
// mark our Julia module (this contains references to our custom data
577609
// types, which thus also will not be collected prematurely)
578-
JMark(Module);
610+
jl_gc_mark_queue_obj(JuliaTLS, (jl_value_t *) Module);
579611
jl_task_t * task = JuliaTLS->current_task;
580612
size_t size;
581613
int tid;
@@ -687,7 +719,11 @@ static uintptr_t JMarkMPtr(jl_ptls_t ptls, jl_value_t * obj)
687719
{
688720
if (!*(void **)obj)
689721
return 0;
690-
if (JMark(BAG_HEADER((Bag)obj)))
722+
void *header = BAG_HEADER((Bag) obj);
723+
void *ty = jl_typeof(header);
724+
if (ty != datatype_bag && ty != datatype_largebag)
725+
return 0;
726+
if (JMark(header))
691727
return 1;
692728
return 0;
693729
}
@@ -712,7 +748,7 @@ void InitBags(UInt initial_size, Bag * stack_bottom, UInt stack_align)
712748
TabMarkFuncBags[i] = MarkAllSubBags;
713749
// These callbacks need to be set before initialization so
714750
// that we can track objects allocated during `jl_init()`.
715-
#if !defined(DISABLE_BIGVAL_TRACKING)
751+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
716752
jl_gc_set_cb_notify_external_alloc(alloc_bigval, 1);
717753
jl_gc_set_cb_notify_external_free(free_bigval, 1);
718754
bigval_startoffset = jl_gc_external_obj_hdr_size();
@@ -825,7 +861,7 @@ Bag NewBag(UInt type, UInt size)
825861
if (size == 0)
826862
alloc_size++;
827863

828-
#if defined(DISABLE_BIGVAL_TRACKING)
864+
#if defined(SCAN_STACK_FOR_MPTRS_ONLY)
829865
bag = jl_gc_alloc_typed(JuliaTLS, sizeof(void *), datatype_mptr);
830866
SET_PTR_BAG(bag, 0);
831867
#endif
@@ -837,7 +873,7 @@ Bag NewBag(UInt type, UInt size)
837873
header->size = size;
838874

839875

840-
#if !defined(DISABLE_BIGVAL_TRACKING)
876+
#if !defined(SCAN_STACK_FOR_MPTRS_ONLY)
841877
// allocate the new masterpointer
842878
bag = jl_gc_alloc_typed(JuliaTLS, sizeof(void *), datatype_mptr);
843879
SET_PTR_BAG(bag, DATA(header));
@@ -943,18 +979,19 @@ inline void MarkBag(Bag bag)
943979
// relies on Julia internals. It is functionally equivalent
944980
// to:
945981
//
946-
// if (JMark(p)) YoungRef++;
982+
// if (JMarkGapObjSafe(p)) YoungRef++;
947983
//
948984
switch (jl_astaggedvalue(p)->bits.gc) {
949985
case 0:
950-
if (JMark(p))
986+
if (JMarkGapObjSafe(p))
951987
YoungRef++;
952988
break;
953989
case 1:
954990
YoungRef++;
955991
break;
956992
case 2:
957-
JMark(p);
993+
JMarkGapObjSafe(p);
994+
break;
958995
case 3:
959996
break;
960997
}

0 commit comments

Comments
 (0)