diff --git a/code/amcss.c b/code/amcss.c index 6dd8737ae6..f0b477ecb5 100644 --- a/code/amcss.c +++ b/code/amcss.c @@ -128,6 +128,24 @@ static void test_stepper(mps_addr_t object, mps_fmt_t fmt, mps_pool_t pool, } +/* area_scan -- area scanning function for mps_pool_walk */ + +static mps_res_t area_scan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + unsigned long *count = closure; + mps_res_t res; + while (base < limit) { + mps_addr_t prev = base; + ++ *count; + res = dylan_scan1(ss, &base); + if (res != MPS_RES_OK) return res; + Insist(prev < base); + } + Insist(base == limit); + return MPS_RES_OK; +} + + /* test -- the body of the test */ static void test(mps_pool_class_t pool_class, size_t roots_count) @@ -225,11 +243,14 @@ static void test(mps_pool_class_t pool_class, size_t roots_count) "NULL in arena"); if (collections == collectionsCOUNT / 2) { - unsigned long object_count = 0; + unsigned long count1 = 0, count2 = 0; mps_arena_park(arena); - mps_arena_formatted_objects_walk(arena, test_stepper, &object_count, 0); + mps_arena_formatted_objects_walk(arena, test_stepper, &count1, 0); + die(mps_pool_walk(pool, area_scan, &count2), "mps_pool_walk"); mps_arena_release(arena); - printf("stepped on %lu objects.\n", object_count); + printf("stepped on %lu objects.\n", count1); + printf("walked %lu objects.\n", count2); + Insist(count1 == count2); } if (collections == rampSwitch) { int begin_ramp = !ramping diff --git a/code/amcssth.c b/code/amcssth.c index 949ad23f84..35bd6a14ed 100644 --- a/code/amcssth.c +++ b/code/amcssth.c @@ -5,9 +5,9 @@ * Portions copyright (c) 2002 Global Graphics Software. * * The main thread parks the arena half way through the test case and - * runs mps_arena_formatted_objects_walk(). This checks that walking - * works while the other threads continue to allocate in the - * background. + * runs mps_pool_walk() and mps_arena_formatted_objects_walk(). This + * checks that walking works while the other threads continue to + * allocate in the background. */ #include "fmtdy.h" @@ -86,6 +86,24 @@ static void test_stepper(mps_addr_t object, mps_fmt_t fmt, mps_pool_t pool, } +/* area_scan -- area scanning function for mps_pool_walk */ + +static mps_res_t area_scan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + unsigned long *count = closure; + mps_res_t res; + while (base < limit) { + mps_addr_t prev = base; + ++ *count; + res = dylan_scan1(ss, &base); + if (res != MPS_RES_OK) return res; + Insist(prev < base); + } + Insist(base == limit); + return MPS_RES_OK; +} + + /* churn -- create an object and install into roots */ static void churn(mps_ap_t ap, size_t roots_count) @@ -209,11 +227,13 @@ static void test_pool(const char *name, mps_pool_t pool, size_t roots_count) if (collections >= collectionsCOUNT / 2 && !walked) { - unsigned long count = 0; + unsigned long count1 = 0, count2 = 0; mps_arena_park(arena); - mps_arena_formatted_objects_walk(arena, test_stepper, &count, 0); + mps_arena_formatted_objects_walk(arena, test_stepper, &count1, 0); + die(mps_pool_walk(pool, area_scan, &count2), "mps_pool_walk"); mps_arena_release(arena); - printf("stepped on %lu objects.\n", count); + printf("stepped on %lu objects.\n", count1); + printf("walked %lu objects.\n", count2); walked = TRUE; } if (collections >= rampSwitch && !ramped) { diff --git a/code/format.c b/code/format.c index 34a6e38f03..8d513578e7 100644 --- a/code/format.c +++ b/code/format.c @@ -40,8 +40,7 @@ Bool FormatCheck(Format format) /* FormatNo methods -- default values for format keyword arguments */ -static mps_res_t FormatNoScan(mps_ss_t mps_ss, mps_addr_t base, - mps_addr_t limit) +mps_res_t FormatNoScan(mps_ss_t mps_ss, mps_addr_t base, mps_addr_t limit) { UNUSED(mps_ss); UNUSED(base); @@ -191,36 +190,6 @@ Arena FormatArena(Format format) } -/* FormatScan -- scan formatted objects for references - * - * This is a wrapper for formatted objects scanning functions, which - * should not otherwise be called directly from within the MPS. This - * function checks arguments and takes care of accounting for the - * scanned memory. - * - * c.f. TraceScanArea() - */ - -Res FormatScan(Format format, ScanState ss, Addr base, Addr limit) -{ - /* TODO: How critical are these? */ - AVERT_CRITICAL(Format, format); - AVERT_CRITICAL(ScanState, ss); - AVER_CRITICAL(base != NULL); - AVER_CRITICAL(limit != NULL); - AVER_CRITICAL(base < limit); - - /* TODO: EVENT here? */ - - /* scannedSize is accumulated whether or not format->scan succeeds, - so it's safe to accumulate now so that we can tail-call - format->scan. */ - ss->scannedSize += AddrOffset(base, limit); - - return format->scan(&ss->ss_s, base, limit); -} - - /* FormatDescribe -- describe a format */ Res FormatDescribe(Format format, mps_lib_FILE *stream, Count depth) diff --git a/code/locv.c b/code/locv.c index eabb7c43fd..9a0627c1b2 100644 --- a/code/locv.c +++ b/code/locv.c @@ -42,6 +42,23 @@ static mps_fmt_A_s locv_fmt = static mps_addr_t roots[4]; +/* area_scan -- area scanning function for mps_pool_walk */ + +static mps_res_t area_scan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + unsigned long *count = closure; + testlib_unused(ss); + while (base < limit) { + mps_addr_t prev = base; + ++ *count; + base = skip(base); + Insist(prev < base); + } + Insist(base == limit); + return MPS_RES_OK; +} + + int main(int argc, char *argv[]) { mps_arena_t arena; @@ -85,9 +102,15 @@ int main(int argc, char *argv[]) *(mps_word_t *)p = sizeof(void *); cdie(mps_commit(ap, p, sizeof(void *)), "commit last"); + mps_arena_park(arena); { size_t count = 0; mps_arena_formatted_objects_walk(arena, stepper, &count, 0); + cdie(count == 4, "stepped 4 objects"); + } + { + size_t count = 0; + die(mps_pool_walk(pool, area_scan, &count), "mps_pool_walk"); cdie(count == 4, "walk 4 objects"); } diff --git a/code/mpm.h b/code/mpm.h index 72901cee73..9bdfc9dd95 100644 --- a/code/mpm.h +++ b/code/mpm.h @@ -354,10 +354,13 @@ extern const char *MessageNoGCStartWhy(Message message); extern void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, Rank rank, ZoneSet white); +extern void ScanStateInitSeg(ScanState ss, TraceSet ts, Arena arena, + Rank rank, ZoneSet white, Seg seg); extern void ScanStateFinish(ScanState ss); extern Bool ScanStateCheck(ScanState ss); extern void ScanStateSetSummary(ScanState ss, RefSet summary); extern RefSet ScanStateSummary(ScanState ss); +extern void ScanStateUpdateSummary(ScanState ss, Seg seg, Bool wasTotal); /* See impl.h.mpmst.ss */ #define ScanStateZoneShift(ss) ((Shift)(ss)->ss_s._zs) @@ -449,6 +452,7 @@ extern void TraceIdMessagesDestroy(Arena arena, TraceId ti); } \ END +extern Res TraceScanFormat(ScanState ss, Addr base, Addr limit); extern Res TraceScanArea(ScanState ss, Word *base, Word *limit, mps_area_scan_t scan_area, void *closure); @@ -808,7 +812,7 @@ extern Res FormatCreate(Format *formatReturn, Arena arena, ArgList args); extern void FormatDestroy(Format format); extern Arena FormatArena(Format format); extern Res FormatDescribe(Format format, mps_lib_FILE *stream, Count depth); -extern Res FormatScan(Format format, ScanState ss, Addr base, Addr limit); +extern mps_res_t FormatNoScan(mps_ss_t mps_ss, mps_addr_t base, mps_addr_t limit); /* Reference Interface -- see */ diff --git a/code/mpmst.h b/code/mpmst.h index 181fb2448a..257f11a931 100644 --- a/code/mpmst.h +++ b/code/mpmst.h @@ -402,6 +402,9 @@ typedef struct ScanStateStruct { Sig sig; /* */ struct mps_ss_s ss_s; /* .ss */ Arena arena; /* owning arena */ + mps_fmt_scan_t formatScan; /* callback for scanning formatted objects */ + mps_area_scan_t areaScan; /* ditto via the area scanning interface */ + void *areaScanClosure; /* closure argument for areaScan */ SegFixMethod fix; /* third stage fix function */ void *fixClosure; /* see .ss.fix-closure */ TraceSet traces; /* traces to scan for */ diff --git a/code/mps.h b/code/mps.h index 0d1fcad979..4c56265c2e 100644 --- a/code/mps.h +++ b/code/mps.h @@ -498,6 +498,7 @@ extern mps_res_t mps_pool_create_k(mps_pool_t *, mps_arena_t, extern void mps_pool_destroy(mps_pool_t); extern size_t mps_pool_total_size(mps_pool_t); extern size_t mps_pool_free_size(mps_pool_t); +extern mps_res_t mps_pool_walk(mps_pool_t, mps_area_scan_t, void *); /* Chains */ diff --git a/code/poolamc.c b/code/poolamc.c index 0130b3551a..9e817684f3 100644 --- a/code/poolamc.c +++ b/code/poolamc.c @@ -1277,7 +1277,7 @@ static Res amcSegScanNailedRange(Bool *totalReturn, Bool *moreReturn, Addr q; q = (*format->skip)(p); if ((*amc->pinned)(amc, board, p, q)) { - Res res = FormatScan(format, ss, p, q); + Res res = TraceScanFormat(ss, p, q); if(res != ResOK) { *totalReturn = FALSE; *moreReturn = TRUE; @@ -1425,7 +1425,7 @@ static Res amcSegScan(Bool *totalReturn, Seg seg, ScanState ss) *totalReturn = TRUE; return ResOK; } - res = FormatScan(format, ss, base, limit); + res = TraceScanFormat(ss, base, limit); if(res != ResOK) { *totalReturn = FALSE; return res; @@ -1438,7 +1438,7 @@ static Res amcSegScan(Bool *totalReturn, Seg seg, ScanState ss) AVER(SegBase(seg) <= base); AVER(base <= AddrAdd(SegLimit(seg), format->headerSize)); if(base < limit) { - res = FormatScan(format, ss, base, limit); + res = TraceScanFormat(ss, base, limit); if(res != ResOK) { *totalReturn = FALSE; return res; diff --git a/code/poolams.c b/code/poolams.c index 43c3b1dca5..75cc5c991c 100644 --- a/code/poolams.c +++ b/code/poolams.c @@ -1305,10 +1305,9 @@ static Res amsScanObject(Seg seg, Index i, Addr p, Addr next, void *clos) /* @@@@ This isn't quite right for multiple traces. */ if (closure->scanAllObjects || AMS_IS_GREY(seg, i)) { - res = FormatScan(format, - closure->ss, - AddrAdd(p, format->headerSize), - AddrAdd(next, format->headerSize)); + res = TraceScanFormat(closure->ss, + AddrAdd(p, format->headerSize), + AddrAdd(next, format->headerSize)); if (res != ResOK) return res; if (!closure->scanAllObjects) { @@ -1393,7 +1392,7 @@ static Res amsSegScan(Bool *totalReturn, Seg seg, ScanState ss) next = AddrAdd(p, alignment); } j = PoolIndexOfAddr(SegBase(seg), pool, next); - res = FormatScan(format, ss, clientP, clientNext); + res = TraceScanFormat(ss, clientP, clientNext); if (res != ResOK) { /* */ amsseg->marksChanged = TRUE; diff --git a/code/poolawl.c b/code/poolawl.c index e183295dd1..3808c608a8 100644 --- a/code/poolawl.c +++ b/code/poolawl.c @@ -852,7 +852,7 @@ static void awlSegBlacken(Seg seg, TraceSet traceSet) /* base and limit are both offset by the header size */ static Res awlScanObject(Arena arena, AWL awl, ScanState ss, - Format format, Addr base, Addr limit) + Addr base, Addr limit) { Res res; Bool dependent; /* is there a dependent object? */ @@ -862,7 +862,6 @@ static Res awlScanObject(Arena arena, AWL awl, ScanState ss, AVERT(Arena, arena); AVERT(AWL, awl); AVERT(ScanState, ss); - AVERT(Format, format); AVER(base != 0); AVER(base < limit); @@ -875,7 +874,7 @@ static Res awlScanObject(Arena arena, AWL awl, ScanState ss, SegSetSummary(dependentSeg, RefSetUNIV); } - res = FormatScan(format, ss, base, limit); + res = TraceScanFormat(ss, base, limit); if (dependent) ShieldCover(arena, dependentSeg); @@ -931,7 +930,7 @@ static Res awlSegScanSinglePass(Bool *anyScannedReturn, ScanState ss, /* */ if (scanAllObjects || (BTGet(awlseg->mark, i) && !BTGet(awlseg->scanned, i))) { - Res res = awlScanObject(arena, awl, ss, pool->format, + Res res = awlScanObject(arena, awl, ss, hp, objectLimit); if (res != ResOK) return res; diff --git a/code/poollo.c b/code/poollo.c index d74cb38976..25295e604c 100644 --- a/code/poollo.c +++ b/code/poollo.c @@ -71,6 +71,7 @@ static Bool loSegBufferFill(Addr *baseReturn, Addr *limitReturn, Seg seg, Size size, RankSet rankSet); static void loSegBufferEmpty(Seg seg, Buffer buffer); static Res loSegWhiten(Seg seg, Trace trace); +static Res loSegScan(Bool *totalReturn, Seg seg, ScanState ss); static Res loSegFix(Seg seg, ScanState ss, Ref *refIO); static void loSegReclaim(Seg seg, Trace trace); static void loSegWalk(Seg seg, Format format, FormattedObjectsVisitor f, @@ -89,6 +90,7 @@ DEFINE_CLASS(Seg, LOSeg, klass) klass->bufferFill = loSegBufferFill; klass->bufferEmpty = loSegBufferEmpty; klass->whiten = loSegWhiten; + klass->scan = loSegScan; klass->fix = loSegFix; klass->fixEmergency = loSegFix; klass->reclaim = loSegReclaim; @@ -646,6 +648,62 @@ static Res loSegWhiten(Seg seg, Trace trace) } +static Res loSegScan(Bool *totalReturn, Seg seg, ScanState ss) +{ + LOSeg loseg = MustBeA(LOSeg, seg); + Pool pool = SegPool(seg); + Addr p, base, limit; + Buffer buffer; + Bool hasBuffer = SegBuffer(&buffer, seg); + Format format = NULL; /* suppress "may be used uninitialized" warning */ + Bool b; + + AVER(totalReturn != NULL); + AVERT(Seg, seg); + AVERT(ScanState, ss); + + base = SegBase(seg); + limit = SegLimit(seg); + + b = PoolFormat(&format, pool); + AVER(b); + + p = base; + while (p < limit) { + Addr q; + Index i; + + if (hasBuffer) { + if (p == BufferScanLimit(buffer) + && BufferScanLimit(buffer) != BufferLimit(buffer)) { + /* skip over buffered area */ + p = BufferLimit(buffer); + continue; + } + /* since we skip over the buffered area we are always */ + /* either before the buffer, or after it, never in it */ + AVER(p < BufferGetInit(buffer) || BufferLimit(buffer) <= p); + } + i = PoolIndexOfAddr(base, pool, p); + if (!BTGet(loseg->alloc, i)) { + p = AddrAdd(p, PoolAlignment(pool)); + continue; + } + q = (*format->skip)(AddrAdd(p, format->headerSize)); + q = AddrSub(q, format->headerSize); + if (BTGet(loseg->mark, i)) { + Res res = TraceScanFormat(ss, p, q); + if (res != ResOK) + return res; + } + p = q; + } + AVER(p == limit); + + return ResOK; +} + + static Res loSegFix(Seg seg, ScanState ss, Ref *refIO) { LOSeg loseg = MustBeA_CRITICAL(LOSeg, seg); diff --git a/code/poolsnc.c b/code/poolsnc.c index a7af0cad7a..b8564aaa96 100644 --- a/code/poolsnc.c +++ b/code/poolsnc.c @@ -508,19 +508,17 @@ static void sncSegBufferEmpty(Seg seg, Buffer buffer) static Res sncSegScan(Bool *totalReturn, Seg seg, ScanState ss) { Addr base, limit; - Format format; Res res; AVER(totalReturn != NULL); AVERT(ScanState, ss); AVERT(Seg, seg); - format = SegPool(seg)->format; base = SegBase(seg); limit = SegBufferScanLimit(seg); if (base < limit) { - res = FormatScan(format, ss, base, limit); + res = TraceScanFormat(ss, base, limit); if (res != ResOK) { *totalReturn = FALSE; return res; diff --git a/code/seg.c b/code/seg.c index c415b25810..9782e43b3c 100644 --- a/code/seg.c +++ b/code/seg.c @@ -771,9 +771,6 @@ Res SegScan(Bool *totalReturn, Seg seg, ScanState ss) * See */ AVER(ss->rank == RankEXACT || RankSetIsMember(SegRankSet(seg), ss->rank)); - /* Should only scan segments which contain grey objects. */ - AVER(TraceSetInter(SegGrey(seg), ss->traces) != TraceSetEMPTY); - EVENT5(SegScan, seg, SegPool(seg), ss->arena, ss->traces, ss->rank); return Method(Seg, seg, scan)(totalReturn, seg, ss); } diff --git a/code/sncss.c b/code/sncss.c index 0dc324bcc6..91e1cbbd8a 100644 --- a/code/sncss.c +++ b/code/sncss.c @@ -84,6 +84,28 @@ static void fmtVisitor(mps_addr_t object, mps_fmt_t format, env->obj += obj->size; } + +/* area_scan -- area scanning function for mps_pool_walk */ + +static mps_res_t area_scan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + env_t env = closure; + testlib_unused(ss); + while (base < limit) { + mps_addr_t prev = base; + obj_t obj = base; + if (obj->pad) + env->pad += obj->size; + else + env->obj += obj->size; + base = fmtSkip(base); + Insist(prev < base); + } + Insist(base == limit); + return MPS_RES_OK; +} + + #define AP_MAX 3 /* Number of allocation points */ #define DEPTH_MAX 20 /* Maximum depth of frame push */ @@ -152,11 +174,10 @@ static void test(mps_pool_class_t pool_class) } } + mps_arena_park(arena); { - env_s env = {0, 0}; + env_s env1 = {0, 0}, env2 = {0, 0}; size_t alloc = 0; - size_t free = mps_pool_free_size(pool); - size_t total = mps_pool_total_size(pool); for (i = 0; i < NELEMS(aps); ++i) { ap_t a = &aps[i]; @@ -165,15 +186,12 @@ static void test(mps_pool_class_t pool_class) } } - mps_arena_formatted_objects_walk(arena, fmtVisitor, &env, 0); + mps_arena_formatted_objects_walk(arena, fmtVisitor, &env1, 0); + Insist(alloc == env1.obj); - printf("alloc=%lu obj=%lu pad=%lu free=%lu total=%lu\n", - (unsigned long)alloc, - (unsigned long)env.obj, - (unsigned long)env.pad, - (unsigned long)free, - (unsigned long)total); - Insist(alloc == env.obj); + die(mps_pool_walk(pool, area_scan, &env2), "mps_pool_walk"); + Insist(alloc == env2.obj); + Insist(env1.pad == env2.pad); } for (i = 0; i < NELEMS(aps); ++i) { diff --git a/code/trace.c b/code/trace.c index f58c73b5e7..9507eb8f72 100644 --- a/code/trace.c +++ b/code/trace.c @@ -37,6 +37,9 @@ Bool ScanStateCheck(ScanState ss) ZoneSet white; CHECKS(ScanState, ss); + CHECKL(FUNCHECK(ss->formatScan)); + CHECKL(FUNCHECK(ss->areaScan)); + /* Can't check ss->areaScanClosure. */ CHECKL(FUNCHECK(ss->fix)); /* Can't check ss->fixClosure. */ CHECKL(ScanStateZoneShift(ss) == ss->arena->zoneShift); @@ -56,6 +59,15 @@ Bool ScanStateCheck(ScanState ss) } +/* traceNoAreaScan -- area scan function that must not be called */ + +static mps_res_t traceNoAreaScan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + UNUSED(closure); + return FormatNoScan(ss, base, limit); +} + + /* ScanStateInit -- Initialize a ScanState object */ void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, @@ -91,6 +103,8 @@ void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, if (ss->fix == SegFix && ArenaEmergency(arena)) ss->fix = SegFixEmergency; + ss->formatScan = FormatNoScan; + ss->areaScan = traceNoAreaScan; ss->rank = rank; ss->traces = ts; ScanStateSetZoneShift(ss, arena->zoneShift); @@ -114,6 +128,21 @@ void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, } +/* ScanStateInitSeg -- Initialize a ScanState object for scanning a segment */ + +void ScanStateInitSeg(ScanState ss, TraceSet ts, Arena arena, + Rank rank, ZoneSet white, Seg seg) +{ + Format format; + AVERT(Seg, seg); + + ScanStateInit(ss, ts, arena, rank, white); + if (PoolFormat(&format, SegPool(seg))) { + ss->formatScan = format->scan; + } +} + + /* ScanStateFinish -- Finish a ScanState object */ void ScanStateFinish(ScanState ss) @@ -1114,6 +1143,34 @@ RefSet ScanStateSummary(ScanState ss) } +/* ScanStateUpdateSummary -- update segment summary after scan + * + * wasTotal is TRUE if we know that all references were scanned, FALSE + * if some references might not have been scanned. + */ +void ScanStateUpdateSummary(ScanState ss, Seg seg, Bool wasTotal) +{ + RefSet summary; + + AVERT(ScanState, ss); + AVERT(Seg, seg); + AVERT(Bool, wasTotal); + + /* Only apply the write barrier if it is not deferred. */ + if (seg->defer == 0) { + /* If we scanned every reference in the segment then we have a + complete summary we can set. Otherwise, we just have + information about more zones that the segment refers to. */ + if (wasTotal) + summary = ScanStateSummary(ss); + else + summary = RefSetUnion(SegSummary(seg), ScanStateSummary(ss)); + } else { + summary = RefSetUNIV; + } + SegSetSummary(seg, summary); +} + /* traceScanSegRes -- scan a segment to remove greyness * * @@@@ During scanning, the segment should be write-shielded to prevent @@ -1126,7 +1183,6 @@ static Res traceScanSegRes(TraceSet ts, Rank rank, Arena arena, Seg seg) Bool wasTotal; ZoneSet white; Res res; - RefSet summary; /* The reason for scanning a segment is that it's grey. */ AVER(TraceSetInter(ts, SegGrey(seg)) != TraceSetEMPTY); @@ -1141,7 +1197,7 @@ static Res traceScanSegRes(TraceSet ts, Rank rank, Arena arena, Seg seg) } else { /* scan it */ ScanStateStruct ssStruct; ScanState ss = &ssStruct; - ScanStateInit(ss, ts, arena, rank, white); + ScanStateInitSeg(ss, ts, arena, rank, white, seg); /* Expose the segment to make sure we can scan it. */ ShieldExpose(arena, seg); @@ -1183,20 +1239,7 @@ static Res traceScanSegRes(TraceSet ts, Rank rank, Arena arena, Seg seg) seg->defer = WB_DEFER_DELAY; } - /* Only apply the write barrier if it is not deferred. */ - if (seg->defer == 0) { - /* If we scanned every reference in the segment then we have a - complete summary we can set. Otherwise, we just have - information about more zones that the segment refers to. */ - if (res == ResOK && wasTotal) - summary = ScanStateSummary(ss); - else - summary = RefSetUnion(SegSummary(seg), ScanStateSummary(ss)); - } else { - summary = RefSetUNIV; - } - SegSetSummary(seg, summary); - + ScanStateUpdateSummary(ss, seg, res == ResOK && wasTotal); ScanStateFinish(ss); } @@ -1473,16 +1516,36 @@ void TraceScanSingleRef(TraceSet ts, Rank rank, Arena arena, } +/* TraceScanFormat -- scan a formatted area of memory for references + * + * This is a wrapper for format scanning functions, which should not + * otherwise be called directly from within the MPS. This function + * checks arguments and takes care of accounting for the scanned + * memory. + */ +Res TraceScanFormat(ScanState ss, Addr base, Addr limit) +{ + AVERT_CRITICAL(ScanState, ss); + AVER_CRITICAL(base != NULL); + AVER_CRITICAL(limit != NULL); + AVER_CRITICAL(base < limit); + + /* scannedSize is accumulated whether or not ss->formatScan + * succeeds, so it's safe to accumulate now so that we can tail-call + * ss->formatScan. */ + ss->scannedSize += AddrOffset(base, limit); + + return ss->formatScan(&ss->ss_s, base, limit); +} + + /* TraceScanArea -- scan an area of memory for references * * This is a wrapper for area scanning functions, which should not * otherwise be called directly from within the MPS. This function * checks arguments and takes care of accounting for the scanned * memory. - * - * c.f. FormatScan() */ - Res TraceScanArea(ScanState ss, Word *base, Word *limit, mps_area_scan_t scan_area, void *closure) diff --git a/code/walk.c b/code/walk.c index c9d63fc2c0..0b3d96baf9 100644 --- a/code/walk.c +++ b/code/walk.c @@ -381,6 +381,123 @@ void mps_arena_roots_walk(mps_arena_t mps_arena, mps_roots_stepper_t f, } +/* walkNoFix -- third-stage fix function for poolWalk. + * + * The second-stage fix is not called via poolWalk; so this is not + * called either. The NOTREACHED checks that this is the case. + */ +static Res walkNoFix(Seg seg, ScanState ss, Addr *refIO) +{ + AVERT(Seg, seg); + AVERT(ScanState, ss); + AVER(refIO != NULL); + + NOTREACHED; + + return ResUNIMPL; +} + + +/* poolWalkScan -- format scanner for poolWalk */ + +static mps_res_t poolWalkScan(mps_ss_t mps_ss, void *base, void *limit) +{ + ScanState ss = PARENT(ScanStateStruct, ss_s, mps_ss); + + AVERT(ScanState, ss); + AVER(base != NULL); + AVER(limit != NULL); + AVER(base < limit); + + return ss->areaScan(mps_ss, base, limit, ss->areaScanClosure); +} + + +/* poolWalk -- walk formatted areas in a pool + * + * See . + */ + +static Res poolWalk(Arena arena, Pool pool, mps_area_scan_t area_scan, void *closure) +{ + Trace trace; + TraceSet ts; + ScanStateStruct ss; + Ring node, nextNode; + Res res = ResOK; + + AVERT(Arena, arena); + AVERT(Pool, pool); + AVER(FUNCHECK(area_scan)); + /* closure is arbitrary and can't be checked */ + + AVER(ArenaGlobals(arena)->clamped); /* .assume.parked */ + AVER(arena->busyTraces == TraceSetEMPTY); /* .assume.parked */ + + /* Synthesize a flipped trace with an empty white set. The empty + * white set means that the MPS_FIX1 test will always fail and + * _mps_fix2 will never be called. */ + res = TraceCreate(&trace, arena, TraceStartWhyWALK); + /* Fail if no trace available. Unlikely due to .assume.parked. */ + if (res != ResOK) + return res; + trace->white = ZoneSetEMPTY; + trace->state = TraceFLIPPED; + arena->flippedTraces = TraceSetAdd(arena->flippedTraces, trace); + ts = TraceSetSingle(trace); + + ScanStateInit(&ss, ts, arena, RankEXACT, trace->white); + ss.formatScan = poolWalkScan; + ss.areaScan = area_scan; + ss.areaScanClosure = closure; + ss.fix = walkNoFix; + + RING_FOR(node, &pool->segRing, nextNode) { + Bool wasTotal; + Seg seg = SegOfPoolRing(node); + Bool needSummary = SegRankSet(seg) != RankSetEMPTY; + + if (needSummary) + ScanStateSetSummary(&ss, RefSetEMPTY); + + /* Expose the segment to make sure we can scan it. */ + ShieldExpose(arena, seg); + res = SegScan(&wasTotal, seg, &ss); + ShieldCover(arena, seg); + + if (needSummary) + ScanStateUpdateSummary(&ss, seg, res == ResOK && wasTotal); + + if (res != ResOK) + break; + } + + ScanStateFinish(&ss); + trace->state = TraceFINISHED; + TraceDestroyFinished(trace); + AVER(!ArenaEmergency(arena)); /* There was no allocation. */ + + return res; +} + + +mps_res_t mps_pool_walk(mps_pool_t pool, mps_area_scan_t area_scan, void *closure) +{ + Arena arena; + Res res; + + AVER(TESTT(Pool, pool)); + arena = PoolArena(pool); + ArenaEnter(arena); + AVER(FUNCHECK(area_scan)); + /* closure is arbitrary and can't be checked */ + + res = poolWalk(arena, pool, area_scan, closure); + ArenaLeave(arena); + return res; +} + + /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2001-2020 Ravenbrook Limited . diff --git a/code/walkt0.c b/code/walkt0.c index 503abd3d78..dd2b5d8fba 100644 --- a/code/walkt0.c +++ b/code/walkt0.c @@ -138,6 +138,30 @@ static void object_stepper(mps_addr_t object, mps_fmt_t format, } +/* area_scan -- area scanning function for mps_pool_walk */ + +static mps_res_t area_scan(mps_ss_t ss, void *base, void *limit, void *closure) +{ + object_stepper_data_t sd = closure; + mps_res_t res; + while (base < limit) { + size_t size = AddrOffset(base, dylan_skip(base)); + mps_addr_t prev = base; + if (dylan_ispad(base)) { + sd->padSize += size; + } else { + ++ sd->count; + sd->objSize += size; + } + res = dylan_scan1(ss, &base); + if (res != MPS_RES_OK) return res; + Insist(prev < base); + } + Insist(base == limit); + return MPS_RES_OK; +} + + /* A roots stepper function. Passed to mps_arena_roots_walk. */ typedef struct roots_stepper_data { @@ -169,6 +193,7 @@ static void test(mps_arena_t arena, mps_pool_class_t pool_class) unsigned long objs; object_stepper_data_s objectStepperData, *sd; roots_stepper_data_s rootsStepperData, *rsd; + int walk; die(dylan_fmt(&format, arena), "fmt_create"); die(mps_chain_create(&chain, arena, genCOUNT, testChain), "chain_create"); @@ -217,29 +242,39 @@ static void test(mps_arena_t arena, mps_pool_class_t pool_class) printf("%lu %lu\n", (unsigned long)rsd->count, (unsigned long)exactRootsCOUNT); Insist(rsd->count == exactRootsCOUNT); - sd = &objectStepperData; - sd->arena = arena; - sd->expect_pool = pool; - sd->expect_fmt = format; - sd->count = 0; - sd->objSize = 0; - sd->padSize = 0; - mps_arena_formatted_objects_walk(arena, object_stepper, sd, sizeof *sd); - Insist(sd->count == objs); - - totalSize = mps_pool_total_size(pool); - freeSize = mps_pool_free_size(pool); - allocSize = totalSize - freeSize; - bufferSize = AddrOffset(ap->init, ap->limit); - printf("%s: obj=%lu pad=%lu total=%lu free=%lu alloc=%lu buffer=%lu\n", - ClassName(pool_class), - (unsigned long)sd->objSize, - (unsigned long)sd->padSize, - (unsigned long)totalSize, - (unsigned long)freeSize, - (unsigned long)allocSize, - (unsigned long)bufferSize); - Insist(sd->objSize + sd->padSize + bufferSize == allocSize); + for (walk = 0; walk < 2; ++walk) + { + sd = &objectStepperData; + sd->arena = arena; + sd->expect_pool = pool; + sd->expect_fmt = format; + sd->count = 0; + sd->objSize = 0; + sd->padSize = 0; + if (walk) { + mps_arena_formatted_objects_walk(arena, object_stepper, + sd, sizeof *sd); + } else { + die(mps_pool_walk(pool, area_scan, sd), "mps_pool_walk"); + } + Insist(sd->count == objs); + + totalSize = mps_pool_total_size(pool); + freeSize = mps_pool_free_size(pool); + allocSize = totalSize - freeSize; + bufferSize = AddrOffset(ap->init, ap->limit); + printf("%s: obj=%lu pad=%lu total=%lu free=%lu alloc=%lu buffer=%lu\n", + ClassName(pool_class), + (unsigned long)sd->objSize, + (unsigned long)sd->padSize, + (unsigned long)totalSize, + (unsigned long)freeSize, + (unsigned long)allocSize, + (unsigned long)bufferSize); + Insist(sd->objSize + sd->padSize + bufferSize == allocSize); + } + + mps_arena_collect(arena); mps_ap_destroy(ap); mps_root_destroy(exactRoot); diff --git a/design/index.txt b/design/index.txt index 6a368a7322..ab28baaa74 100644 --- a/design/index.txt +++ b/design/index.txt @@ -111,6 +111,7 @@ trace_ Tracer type_ General MPS types version-library_ Library version mechanism vm_ Virtual mapping +walk_ Walking formatted objects write-barrier_ Write Barrier writef_ The WriteF function ====================== ================================================ @@ -187,6 +188,7 @@ writef_ The WriteF function .. _type: type .. _version-library: version-library .. _vm: vm +.. _walk: walk .. _write-barrier: write-barrier .. _writef: writef @@ -223,6 +225,7 @@ Document History - 2014-01-17 GDR_ Add abq, nailboard, range. - 2016-03-22 RB_ Add write-barier. - 2016-03-27 RB_ Goodbye pool MV *sniff*. +- 2020-08-31 GDR_ Add walk. .. _RB: https://www.ravenbrook.com/consultants/rb .. _NB: https://www.ravenbrook.com/consultants/nb diff --git a/design/object-debug.txt b/design/object-debug.txt index b73cd19ed5..a5c5dbe479 100644 --- a/design/object-debug.txt +++ b/design/object-debug.txt @@ -343,11 +343,10 @@ _`.interface.tags.alloc`: Two functions to extend the existing ``mps_alloc()`` (request.???.??? proposes to remove the varargs) ``void (*mps_objects_step_t)(mps_addr_t addr, size_t size, mps_fmt_t format, mps_pool_t pool, void *tag_data, void *p)`` -``void mps_pool_walk(mps_arena_t arena, mps_pool_t pool, mps_objects_step_t step, void *p)`` ``void mps_arena_walk(mps_arena_t arena, mps_objects_step_t step, void *p)`` _`.interface.tags.walker`: Functions to walk all the allocated -objects in a pool or an arena (only client pools in this case), +objects in an arena (only client pools in this case), ``format`` and ``tag_data`` can be ``NULL`` (``tag_data`` really wants to be ``void *``, not ``mps_addr_t``, because it's stored together with the internal tag data in an MPS internal pool) diff --git a/design/seg.txt b/design/seg.txt index ebcb1576a0..809c76682a 100644 --- a/design/seg.txt +++ b/design/seg.txt @@ -312,15 +312,18 @@ called via the generic function ``SegBlacken()``. ``typedef Res (*SegScanMethod)(Bool *totalReturn, Seg seg, ScanState ss)`` _`.method.scan`: The ``scan`` method scans all the grey objects on the -segment ``seg``, passing the scan state ``ss`` to ``FormatScan``. The -segment may additionally accumulate a summary of *all* its objects. If -it succeeds in accumulating such a summary it must indicate that it -has done so by setting the ``*totalReturn`` parameter to ``TRUE``. -Otherwise it must set ``*totalReturn`` to ``FALSE``. Segment classes -are not required to provide this method, and not doing so indicates -that all instances of this class will have no fixable or traceable -references in them. This method is called via the generic function -``SegScan()``. +segment ``seg``, passing the scan state ``ss`` to +``TraceScanFormat()``. The segment may additionally accumulate a +summary of *all* its objects. If it succeeds in accumulating such a +summary it must indicate that it has done so by setting the +``*totalReturn`` parameter to ``TRUE``. Otherwise it must set +``*totalReturn`` to ``FALSE``. This method is called via the generic +function ``SegScan()``. + +_`.method.scan.required`: Automatically managed segment classes are +required to provide this method, even if all instances of this class +will have no fixable or traceable references in them, in order to +support ``mps_pool_walk()``. ``typedef Res (*SegFixMethod)(Seg seg, ScanState ss, Ref *refIO)`` @@ -365,13 +368,19 @@ via the generic function ``SegReclaim()``. _`.method.walk`: The ``walk`` method must call the visitor function ``f`` (along with its closure parameters ``v`` and ``s`` and the -format ``format``) once for each of the *black* objects in the -segment ``seg``. Padding objects may or may not be included in the -walk, at the segment's discretion: it is the responsibility of the -client program to handle them. Forwarding objects must not be included -in the walk. Segment classes need not provide this method. This -method is called by the genetic function ``SegWalk()``, which is -called by the heap walker ``mps_arena_formatted_objects_walk()``. +format ``format``) once for each of the *black* objects in the segment +``seg``. Padding objects may or may not be included in the walk, at +the segment's discretion: it is the responsibility of the client +program to handle them. Forwarding objects must not be included in the +walk. Segment classes need not provide this method. This method is +called by the generic function ``SegWalk()``, which is called by the +deprecated public functions ``mps_arena_formatted_objects_walk()`` and +``mps_amc_apply()``. + +_`.method.walk.deprecated`: The ``walk`` method is deprecated along +with the public functions ``mps_arena_formatted_objects_walk()`` and +``mps_amc_apply()`` and will be removed along with them in a future +release. ``typedef void (*SegFlipMethod)(Seg seg, Trace trace)`` diff --git a/design/sp.txt b/design/sp.txt index 526fee48c2..1038ec7876 100644 --- a/design/sp.txt +++ b/design/sp.txt @@ -95,7 +95,7 @@ Args Locals Function 4 9 ``traceScanSegRes()`` 4 0 ``SegScan()`` 4 5 ``amcSegScan()`` - 4 0 ``FormatScan()`` + 3 0 ``TraceScanFormat()`` 3 ≤64 ``format->scan()`` 3 0 ``SegFix()`` 4 15 ``amcSegFix()`` @@ -113,12 +113,12 @@ Args Locals Function 3 7 ``SplaySplay()`` 4 8 ``SplaySplitDown()`` 3 0 ``SplayZig()`` - 112 ≤258 **Total** + 111 ≤258 **Total** ==== ====== ======================== We expect that a compiler will not need to push all local variables onto the stack, but even in the case where it pushes all of them, this -call requires no more than 370 words of stack space. +call requires no more than 369 words of stack space. This isn't necessarily the deepest call into the MPS (the MPS's modular design and class system makes it hard to do a complete diff --git a/design/walk.txt b/design/walk.txt new file mode 100644 index 0000000000..e92e6b416a --- /dev/null +++ b/design/walk.txt @@ -0,0 +1,157 @@ +.. mode: -*- rst -*- + +Walking formatted objects +========================= + +:Tag: design.mps.walk +:Author: Gareth Rees +:Date: 2020-08-31 +:Status: complete design +:Revision: $Id$ +:Copyright: See `Copyright and License`_. +:Index terms: pair: walk; design + + +Introduction +------------ + +_`.intro`: This is the design of the formatted objects walk interface. +The intended audience is MPS developers. + +_`.source`: Based on [GDR_2020-08-30]_. + + +Use cases +--------- + +_`.case.reload`: A language runtime that offers hot reloading of code +will need to walk all objects belonging to a class (say) in order to +modify the references in the objects so they refer to the updated +class definition. [Strömbäck_2020-08-20]_ + +_`.case.serialize`: A language runtime that offers serialization and +deserialization of the heap will need to walk all formatted objects in +order to identify references to globals (during serialization) and +modify references to refer to the new locations of the globals (after +deserialization). [GDR_2018-08-30]_ + + +Requirements +------------ + +_`.req.walk.all`: It must be possible for the client program to visit +all automatically managed formatted objects using a callback. + +_`.req.walk.assume-format`: The callback should not need to switch on +the format, as this may be awkward in a program which has modules +using different pools with different formats. + +_`.req.walk.examine`: It must be possible for the callback to examine +other automatically managed memory while walking the objects. + +_`.req.walk.modify`: It must be possible for the callback to modify +the references in the objects. + +_`.req.walk.overhead`: The overhead of calling the callback should be +minimized. + +_`.req.walk.perf`: The performance of subsequent collections should +not be affected. + +_`.req.walk.closure`: The callback must have access to arbitrary data +from the caller. + +_`.req.walk.maint`: The interface should be easy to implement and +maintain. + + +Design +------ + +A new public function ``mps_pool_walk()`` visits the live formatted +objects in an automatically managed pool. + +_`.sol.walk.all`: The client program must know which pools it has +created so it can call ``mps_pool_walk()`` for each pool. + +_`.sol.walk.assume-format`: All objects in a pool share the same +format, so the callback does not need to switch on the format. + +_`.sol.walk.examine`: ``mps_pool_walk()`` must only be called when the +arena is parked, and so there is no read barrier on any object. + +_`.sol.walk.modify`: ``mps_pool_walk()`` arranges for write-protection +to be removed from each segment while it is being walked and restored +afterwards if necessary. + +_`.sol.walk.overhead`: The callback is called for contiguous regions +of formatted objects (not just for each object) where possible so that +the per-object function call overhead is minimized. + +_`.sol.walk.perf`: The callback uses the scanning protocol so that +every reference is fixed and the summary is maintained. + +_`.sol.walk.closure`: ``mps_pool_walk()`` takes a closure pointer +which is stored in the ``ScanState`` and passed to the callback. + +_`.sol.walk.maint`: We reuse the scanning protocol and provide a +generic implementation that iterates over the ring of segments in the +pool. We set up an empty white set in the ``ScanState`` so that the +``MPS_FIX1()`` test always fails and ``_mps_fix2()`` is never called. +This avoids any per-pool code to support the interface. + + +References +---------- + +.. [GDR_2018-08-30] + "Save/restore draft proposal"; + Gareth Rees; 2018-08-30; + . + +.. [GDR_2020-08-30] + "Re: Modifying objects during mps_formatted_objects_walk"; + Gareth Rees; 2020-08-30; + . + +.. [Strömbäck_2020-08-20] + "Modifying objects during mps_formatted_objects_walk"; + Filip Strömbäck; 2020-08-20; + . + + +Document History +---------------- + +- 2020-08-31 GDR_ Initial version based on [GDR_2020-08-30]_ + +.. _GDR: https://www.ravenbrook.com/consultants/gdr/ + + +Copyright and License +--------------------- + +Copyright © 2001–2020 `Ravenbrook Limited `_. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/manual/source/design/index.rst b/manual/source/design/index.rst index 97f3b6ddf3..7e568a89d7 100644 --- a/manual/source/design/index.rst +++ b/manual/source/design/index.rst @@ -48,5 +48,6 @@ Design type version-library vm + walk write-barrier writef diff --git a/manual/source/glossary/s.rst b/manual/source/glossary/s.rst index 234537c0d9..2b633edc97 100644 --- a/manual/source/glossary/s.rst +++ b/manual/source/glossary/s.rst @@ -703,10 +703,9 @@ Memory Management Glossary: S A function that will be called on each element in a collection. For example, a stepper function of type - :c:type:`mps_formatted_objects_stepper_t` can be passed to - :c:func:`mps_arena_formatted_objects_walk` and it will be - called on all :term:`formatted objects` - in an :term:`arena`. + :c:type:`mps_roots_stepper_t` can be passed to + :c:func:`mps_arena_roots_walk` and it will be called on + all :term:`roots` in an :term:`arena`. sticky reference count diff --git a/manual/source/pool/amc.rst b/manual/source/pool/amc.rst index f768a3a389..4760f928bd 100644 --- a/manual/source/pool/amc.rst +++ b/manual/source/pool/amc.rst @@ -139,74 +139,3 @@ AMC interface MPS_ARGS_ADD(args, MPS_KEY_FORMAT, fmt); res = mps_pool_create_k(&pool, arena, mps_class_amc(), args); } MPS_ARGS_END(args); - - -.. index:: - pair: AMC pool class; introspection - -.. _pool-amc-introspection: - -AMC introspection ------------------ - -:: - - #include "mpscamc.h" - -.. c:function:: void mps_amc_apply(mps_pool_t pool, mps_amc_apply_stepper_t f, void *p, size_t s) - - Visit all :term:`formatted objects` in an AMC pool. - - ``pool`` is the pool whose formatted objects you want to visit. - - ``f`` is a function that will be called for each formatted object in - the pool. - - ``p`` and ``s`` are arguments that will be passed to ``f`` each time it - is called. This is intended to make it easy to pass, for example, - an array and its size as parameters. - - It is an error to call this function when the :term:`arena` is not - in the :term:`parked state`. You need to call - :c:func:`mps_arena_collect` or :c:func:`mps_arena_park` before - calling :c:func:`mps_amc_apply`. - - The function ``f`` will be called on both :term:`client ` and :term:`padding objects`. It is the job of ``f`` to - distinguish, if necessary, between the two. It may also be called - on :term:`dead` objects that the collector has not recycled or has - been unable to recycle. - - .. note:: - - There is no equivalent function for other pool classes, but - there is a more general function - :c:func:`mps_arena_formatted_objects_walk` that visits all - formatted objects in the arena. - - .. note:: - - This function is intended for heap analysis, tuning, and - debugging, not for frequent use in production. - - -.. c:type:: void (*mps_amc_apply_stepper_t)(mps_addr_t addr, void *p, size_t s) - - The type of a :term:`stepper function` for :term:`formatted - objects` in an AMC pool. - - ``addr`` is the address of an object in the pool. - - ``p`` and ``s`` are the corresponding arguments that were passed - to :c:func:`mps_amc_apply`. - - The function may not call any function in the MPS. It may access: - - a. memory inside the object or block pointed to by ``addr``; - - b. memory managed by the MPS that is in pools that do not protect - their contents; - - c. memory not managed by the MPS; - - It must not access other memory managed by the MPS. diff --git a/manual/source/pool/amcz.rst b/manual/source/pool/amcz.rst index 755a12b827..3101eced2a 100644 --- a/manual/source/pool/amcz.rst +++ b/manual/source/pool/amcz.rst @@ -86,12 +86,3 @@ AMCZ interface MPS_ARGS_ADD(args, MPS_KEY_FORMAT, fmt); res = mps_pool_create_k(&pool, arena, mps_class_amcz(), args); } MPS_ARGS_END(args); - - -.. index:: - pair: AMCZ pool class; introspection - -AMCZ introspection ------------------- - -See :ref:`pool-amc-introspection`. diff --git a/manual/source/release.rst b/manual/source/release.rst index 1098cab027..6ff3db2958 100644 --- a/manual/source/release.rst +++ b/manual/source/release.rst @@ -39,6 +39,11 @@ New features experimental: the implementation is likely to change in future versions of the MPS. See :ref:`design-monitor`. +#. The new function :c:func:`mps_pool_walk` visits all areas of + :term:`formatted objects` in a pool using the + :ref:`topic-scanning-protocol`. This allows the client program to + safely update references in the visited objects. + Interface changes ................. @@ -89,6 +94,10 @@ Interface changes .. |unpack| replace:: :py:func:`struct.unpack` .. _unpack: https://docs.python.org/3/library/struct.html#struct.unpack +#. The functions :c:func:`mps_formatted_objects_walk` and + :c:func:`mps_amc_apply` are deprecated in favour of the new + function :c:func:`mps_pool_walk`. + Other changes ............. diff --git a/manual/source/topic/arena.rst b/manual/source/topic/arena.rst index 864e592306..0e6e086e9b 100644 --- a/manual/source/topic/arena.rst +++ b/manual/source/topic/arena.rst @@ -951,8 +951,8 @@ Arena introspection and debugging * :c:func:`mps_addr_fmt`: determine the :term:`object format` to which an address belongs; - * :c:func:`mps_arena_formatted_objects_walk`: visit all - :term:`formatted objects` in an arena; + * :c:func:`mps_pool_walk`: visit all areas of :term:`formatted + objects` in a :term:`pool`; * :c:func:`mps_arena_roots_walk`: visit all references in :term:`roots` registered with an arena; and * :c:func:`mps_addr_pool`: determine the :term:`pool` to which an diff --git a/manual/source/topic/deprecated.rst b/manual/source/topic/deprecated.rst index 4dd90a1e1d..b61e1a7165 100644 --- a/manual/source/topic/deprecated.rst +++ b/manual/source/topic/deprecated.rst @@ -63,6 +63,164 @@ Deprecated in version 1.118 limit is set to 1.0 exactly. +.. c:function:: void mps_arena_formatted_objects_walk(mps_arena_t arena, mps_formatted_objects_stepper_t f, void *p, size_t s) + + .. deprecated:: + + Use :c:func:`mps_pool_walk` instead. + + Visit all :term:`formatted objects` in an + :term:`arena`. + + ``arena`` is the arena whose formatted objects you want to visit. + + ``f`` is a formatted objects stepper function. It will be called for + each formatted object in the arena. See + :c:type:`mps_formatted_objects_stepper_t`. + + ``p`` and ``s`` are arguments that will be passed to ``f`` each time it + is called. This is intended to make it easy to pass, for example, + an array and its size as parameters. + + Each :term:`pool class` determines for which objects the stepper + function is called. Typically, all validly formatted objects are + visited. :term:`Padding objects` may be visited at the pool + class's discretion: the stepper function must handle this + case. + + .. warning:: + + The callback function must obey the restrictions documented + under :c:type:`mps_formatted_objects_stepper_t`. + + If a garbage collection is currently in progress (that is, if + the arena is in the :term:`clamped ` or + :term:`unclamped state`), then only objects that are known to + be currently valid are visited. + + If you need to be certain that all objects are visited, or if + the callback function needs to follow references from the + object to automatically managed memory, you must ensure that + the arena is in the :term:`parked state` by calling + :c:func:`mps_arena_park` before calling this function (and + release it by calling :c:func:`mps_arena_release` afterwards, + if desired). + + If your application has requirements for introspection that + can't be met under these restrictions, :ref:`contact us + `. + + +.. c:type:: void (*mps_formatted_objects_stepper_t)(mps_addr_t addr, mps_fmt_t fmt, mps_pool_t pool, void *p, size_t s) + + .. deprecated:: + + Use :c:func:`mps_pool_walk` instead. + + The type of a :term:`formatted objects` + :term:`stepper function`. + + A function of this type can be passed to + :c:func:`mps_arena_formatted_objects_walk`, in which case it will + be called for each formatted object in an :term:`arena`. It + receives five arguments: + + ``addr`` is the address of the object. + + ``fmt`` is the :term:`object format` for that object. + + ``pool`` is the :term:`pool` to which the object belongs. + + ``p`` and ``s`` are the corresponding values that were passed to + :c:func:`mps_arena_formatted_objects_walk`. + + The function may not call any function in the MPS. It may access: + + a. memory inside the object or block pointed to by ``addr``; + + b. memory managed by the MPS that is in pools that do not protect + their contents; + + c. memory not managed by the MPS. + + It must not: + + d. access other memory managed by the MPS; + + e. modify any of the references in the object. + + +.. c:function:: void mps_amc_apply(mps_pool_t pool, mps_amc_apply_stepper_t f, void *p, size_t s) + + .. deprecated:: + + Use :c:func:`mps_pool_walk` instead. + + Visit all :term:`formatted objects` in an AMC pool. + + ``pool`` is the pool whose formatted objects you want to visit. + + ``f`` is a function that will be called for each formatted object in + the pool. + + ``p`` and ``s`` are arguments that will be passed to ``f`` each time it + is called. This is intended to make it easy to pass, for example, + an array and its size as parameters. + + It is an error to call this function when the :term:`arena` is not + in the :term:`parked state`. You need to call + :c:func:`mps_arena_collect` or :c:func:`mps_arena_park` before + calling :c:func:`mps_amc_apply`. + + The function ``f`` will be called on both :term:`client ` and :term:`padding objects`. It is the job of ``f`` to + distinguish, if necessary, between the two. It may also be called + on :term:`dead` objects that the collector has not recycled or has + been unable to recycle. + + .. note:: + + There is no equivalent function for other pool classes, but + there is a more general function + :c:func:`mps_arena_formatted_objects_walk` that visits all + formatted objects in the arena. + + .. note:: + + This function is intended for heap analysis, tuning, and + debugging, not for frequent use in production. + + +.. c:type:: void (*mps_amc_apply_stepper_t)(mps_addr_t addr, void *p, size_t s) + + .. deprecated:: + + Use :c:func:`mps_pool_walk` instead. + + The type of a :term:`stepper function` for :term:`formatted + objects` in an AMC pool. + + ``addr`` is the address of an object in the pool. + + ``p`` and ``s`` are the corresponding arguments that were passed + to :c:func:`mps_amc_apply`. + + The function may not call any function in the MPS. It may access: + + a. memory inside the object or block pointed to by ``addr``; + + b. memory managed by the MPS that is in pools that do not protect + their contents; + + c. memory not managed by the MPS; + + It must not: + + d. access other memory managed by the MPS; + + e. modify any of the references in the object. + + .. index:: single: deprecated interfaces; in version 1.115 @@ -77,7 +235,7 @@ Deprecated in version 1.115 pools were the only objects in the MPS that belonged to classes. - + .. c:function:: size_t mps_mvff_free_size(mps_pool_t pool) .. deprecated:: diff --git a/manual/source/topic/error.rst b/manual/source/topic/error.rst index 7cd65935a7..7fbef9abb5 100644 --- a/manual/source/topic/error.rst +++ b/manual/source/topic/error.rst @@ -67,6 +67,7 @@ assume that any MPS function that returns a result code can return * :c:macro:`MPS_RES_PARAM`: an invalid parameter was passed. +.. _topic-result-codes: Result codes ------------ @@ -320,8 +321,7 @@ this documentation. The client program has made a re-entrant call into the MPS. Look at the backtrace to see what it was. Common culprits are signal - handlers, assertion handlers, :term:`format methods`, and - :term:`stepper functions`. + handlers, assertion handlers, and :term:`format methods`. ``locus.c: gen->activeTraces == TraceSetEMPTY`` diff --git a/manual/source/topic/format.rst b/manual/source/topic/format.rst index 91f1e0035e..7ec2a9916c 100644 --- a/manual/source/topic/format.rst +++ b/manual/source/topic/format.rst @@ -487,82 +487,3 @@ Object format introspection managed by a pool with an object format, but which is not inside a block allocated by that pool. It never returns a false negative. - - -.. c:function:: void mps_arena_formatted_objects_walk(mps_arena_t arena, mps_formatted_objects_stepper_t f, void *p, size_t s) - - Visit all :term:`formatted objects` in an - :term:`arena`. - - ``arena`` is the arena whose formatted objects you want to visit. - - ``f`` is a formatted objects stepper function. It will be called for - each formatted object in the arena. See - :c:type:`mps_formatted_objects_stepper_t`. - - ``p`` and ``s`` are arguments that will be passed to ``f`` each time it - is called. This is intended to make it easy to pass, for example, - an array and its size as parameters. - - Each :term:`pool class` determines for which objects the stepper - function is called. Typically, all validly formatted objects are - visited. :term:`Padding objects` may be visited at the pool - class's discretion: the stepper function must handle this - case. - - .. warning:: - - The callback function must obey the restrictions documented - under :c:type:`mps_formatted_objects_stepper_t`. - - If a garbage collection is currently in progress (that is, if - the arena is in the :term:`clamped ` or - :term:`unclamped state`), then only objects that are known to - be currently valid are visited. - - If you need to be certain that all objects are visited, or if - the callback function needs to follow references from the - object to automatically managed memory, you must ensure that - the arena is in the :term:`parked state` by calling - :c:func:`mps_arena_park` before calling this function (and - release it by calling :c:func:`mps_arena_release` afterwards, - if desired). - - If your application has requirements for introspection that - can't be met under these restrictions, :ref:`contact us - `. - - -.. c:type:: void (*mps_formatted_objects_stepper_t)(mps_addr_t addr, mps_fmt_t fmt, mps_pool_t pool, void *p, size_t s) - - The type of a :term:`formatted objects` - :term:`stepper function`. - - A function of this type can be passed to - :c:func:`mps_arena_formatted_objects_walk`, in which case it will - be called for each formatted object in an :term:`arena`. It - receives five arguments: - - ``addr`` is the address of the object. - - ``fmt`` is the :term:`object format` for that object. - - ``pool`` is the :term:`pool` to which the object belongs. - - ``p`` and ``s`` are the corresponding values that were passed to - :c:func:`mps_arena_formatted_objects_walk`. - - The function may not call any function in the MPS. It may access: - - a. memory inside the object or block pointed to by ``addr``; - - b. memory managed by the MPS that is in pools that do not protect - their contents; - - c. memory not managed by the MPS. - - It must not: - - d. access other memory managed by the MPS; - - e. modify any of the references in the object. diff --git a/manual/source/topic/pool.rst b/manual/source/topic/pool.rst index 360911494c..ad59054983 100644 --- a/manual/source/topic/pool.rst +++ b/manual/source/topic/pool.rst @@ -170,3 +170,39 @@ Pool introspection at the address, use :c:func:`mps_addr_fmt`. If you only care whether the address belongs to a particular :term:`arena`, use :c:func:`mps_arena_has_addr`. + + +.. c:function:: mps_res_t mps_pool_walk(mps_pool_t pool, mps_area_scan_t scan_area, void *closure) + + Visit all :term:`formatted objects` in a :term:`pool`. The pool + must be :term:`automatically managed `. The pool's :term:`arena` must be in the + :term:`parked state`. + + :c:data:`pool` is the pool whose formatted objects are visited. + + :c:data:`scan_area` is an area scanning function. See + :ref:`topic-scanning-area`. + + :c:data:`closure` is an arbitrary pointer that will be passed to + :c:data:`scan_area`. + + The scanning function is called multiple times with disjoint areas + of memory that cover all formatted objects in the pool. The areas + may also include :term:`padding objects` if the pool's format has + a :term:`padding method`, but never includes :term:`forwarding + objects` since the arena is in the parked state. + + The scanning function must follow the + :ref:`topic-scanning-protocol`. In particular, it must :term:`fix` + every :term:`reference` in the area. The scanning function may + return :c:macro:`MPS_RES_OK` to continue visiting areas of + formatted objects, or return other :ref:`topic-result-codes` to + stop visiting and return to the caller. + + .. note:: + + If the scanning function modifies a reference, it must scan + the modified reference. It is safe to scan the original + reference as well, but this may lead to unwanted + :term:`retention`. diff --git a/manual/source/topic/scanning.rst b/manual/source/topic/scanning.rst index b842240995..8405fe8d85 100644 --- a/manual/source/topic/scanning.rst +++ b/manual/source/topic/scanning.rst @@ -17,13 +17,21 @@ Scanning Memory Pool System, and the most critical of the memory management functions that have to be implemented by the :term:`client program`. -Scanning is performed for two tasks: during :term:`tracing `, -blocks are scanned in order to follow references, and so determine -which blocks are :term:`reachable` and which are not. After objects -have been moved in memory, blocks are scanned in order to identify -references that need to be updated to point to the new locations of -these objects. Both tasks use the same scanning protocol, described -here. +Scanning is used to carry out three tasks: + +#. During :term:`tracing `, blocks are scanned in order to + follow references, and so determine which blocks are + :term:`reachable` and which are not. + +#. After objects have been moved in memory, blocks are scanned in + order to identify references that need to be updated to point to + the new locations of these objects. + +#. When iterating over allocated blocks in a pool using + :c:func:`mps_pool_walk`, blocks are scanned in order to keep data + structures consistent when references are updated. + +All these tasks use the same protocol, described here. .. index:: @@ -546,6 +554,20 @@ the scanners, found in ``scan.c`` in the MPS source code. :c:func:`mps_root_create_thread_tagged` then it is the value of the ``closure`` argument originally passed to that function. + .. note:: + + The reason that :c:data:`base` and :c:data:`limit` have type + :c:type:`void *` and not :c:type:`mps_addr_t` is that the + latter is used only for :term:`addresses` managed by the MPS, + but :c:type:`mps_area_scan_t` may also be used to scan + :term:`roots` that are not managed by the MPS. + + .. warning:: + + Area scanning functions are subject to the same set of + restrictions as format scanning functions, described under + :ref:`topic-format-cautions`. + .. c:function:: mps_res_t mps_scan_area(mps_ss_t ss, void *base, void *limit, void *closure) Scan an area of memory :term:`fixing ` every word. diff --git a/test/function/104.c b/test/function/104.c index 821a3c5aa2..392dc6c31b 100644 --- a/test/function/104.c +++ b/test/function/104.c @@ -1,7 +1,7 @@ /* TEST_HEADER id = $Id$ - summary = test of mps_arena_formatted_objects_walk, inc AMCZ + summary = test of mps_pool_walk and mps_arena_formatted_objects_walk, inc AMCZ language = c link = testlib.o rankfmt.o parameters = VERBOSE=0 @@ -48,7 +48,7 @@ long int apppadcount; int oldstamp, newstamp; mps_arena_t arena; -mps_pool_t poolamc, poollo, poolawl; +mps_pool_t poolamc, poolamcz, poolawl; mps_thr_t thread; mps_root_t root, root1; @@ -109,6 +109,61 @@ static void stepper(mps_addr_t addr, mps_fmt_t fmt, mps_pool_t pool, } +static mps_res_t area_scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit, void *closure) +{ + int i; + asserts(closure == MAGICPOINT, "VII. Void * didn't get passed!"); + + MPS_SCAN_BEGIN(ss) + { + while (base < limit) + { + mycell *obj = base; + mps_res_t res; + mps_addr_t p, q; + + switch (obj->tag & 0x3) + { + case MCpad: + apppadcount += 1; + base = (mps_addr_t) (obj->pad.tag &~ (mps_word_t) 3); + break; + case MCdata: + appcount += 1; + asserts(obj->data.checkedflag != newstamp, + "III/IV. step on object again at %p", obj); + commentif(VERBOSE && obj->data.checkedflag != oldstamp, + "*. step on unreachable object at %p", obj); + obj->data.checkedflag = newstamp; + p = obj->data.assoc; + if (p != NULL) { + res = MPS_FIX12(ss, &p); + if (res != MPS_RES_OK) return res; + obj->data.assoc = p; + } + + for (i=0; i<(obj->data.numrefs); i++) + { + p = obj->data.ref[i].addr; + if (p != NULL) + { + res = MPS_FIX12(ss, (mps_addr_t *) &p); + if (res != MPS_RES_OK) return res; + obj->data.ref[i].addr = p; + } + } + base = (mps_addr_t) ((char *) obj + (obj->data.size)); + break; + default: + asserts(0, "area_scan: bizarre obj tag at %p.", obj); + } + } + } + MPS_SCAN_END(ss); + return MPS_RES_OK; +} + + static void test(void *stack_pointer) { mycell *a[4], /* a is a table of exact roots */ @@ -135,7 +190,7 @@ static void test(void *stack_pointer) die(mmqa_pool_create_chain(&poolamc, arena, mps_class_amc(), format, chain), "create pool(amc)"); - die(mmqa_pool_create_chain(&poollo, arena, mps_class_amcz(), format, chain), + die(mmqa_pool_create_chain(&poolamcz, arena, mps_class_amcz(), format, chain), "create pool(amcz)"); die(mps_pool_create(&poolawl, arena, mps_class_awl(), format, getassociated), @@ -146,7 +201,7 @@ static void test(void *stack_pointer) "create ap(amc)"); cdie( - mps_ap_create(&aplo, poollo, mps_rank_exact()), + mps_ap_create(&aplo, poolamcz, mps_rank_exact()), "create ap(amcz)"); cdie( @@ -192,8 +247,17 @@ static void test(void *stack_pointer) oldstamp = newstamp; newstamp += 1; + mps_arena_formatted_objects_walk(arena, stepper, - (void *)MAGICPOINT, MAGICSIZE); + MAGICPOINT, MAGICSIZE); + + oldstamp = newstamp; + newstamp += 1; + + mps_pool_walk(poolamc, area_scan, MAGICPOINT); + mps_pool_walk(poolamcz, area_scan, MAGICPOINT); + mps_pool_walk(poolawl, area_scan, MAGICPOINT); + mps_arena_release(arena); comment("tracing..."); @@ -220,7 +284,7 @@ static void test(void *stack_pointer) comment("Destroyed aps."); mps_pool_destroy(poolamc); - mps_pool_destroy(poollo); + mps_pool_destroy(poolamcz); mps_pool_destroy(poolawl); comment("Destroyed pools."); diff --git a/test/function/97.c b/test/function/97.c index 127d86de6f..c6f0ff24b8 100644 --- a/test/function/97.c +++ b/test/function/97.c @@ -1,7 +1,7 @@ /* TEST_HEADER id = $Id$ - summary = test of mps_arena_formatted_objects_walk + summary = test of mps_pool_walk and mps_arena_formatted_objects_walk language = c link = testlib.o rankfmt.o parameters = VERBOSE=0 @@ -106,6 +106,62 @@ static void stepper(mps_addr_t addr, mps_fmt_t fmt, mps_pool_t pool, } } + +static mps_res_t area_scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit, void *closure) +{ + int i; + asserts(closure == MAGICPOINT, "VII. Void * didn't get passed!"); + + MPS_SCAN_BEGIN(ss) + { + while (base < limit) + { + mycell *obj = base; + mps_res_t res; + mps_addr_t p, q; + + switch (obj->tag & 0x3) + { + case MCpad: + apppadcount += 1; + base = (mps_addr_t) (obj->pad.tag &~ (mps_word_t) 3); + break; + case MCdata: + appcount += 1; + asserts(obj->data.checkedflag != newstamp, + "III/IV. step on object again at %p", obj); + commentif(VERBOSE && obj->data.checkedflag != oldstamp, + "*. step on unreachable object at %p", obj); + obj->data.checkedflag = newstamp; + p = obj->data.assoc; + if (p != NULL) { + res = MPS_FIX12(ss, &p); + if (res != MPS_RES_OK) return res; + obj->data.assoc = p; + } + + for (i=0; i<(obj->data.numrefs); i++) + { + p = obj->data.ref[i].addr; + if (p != NULL) + { + res = MPS_FIX12(ss, (mps_addr_t *) &p); + if (res != MPS_RES_OK) return res; + obj->data.ref[i].addr = p; + } + } + base = (mps_addr_t) ((char *) obj + (obj->data.size)); + break; + default: + asserts(0, "area_scan: bizarre obj tag at %p.", obj); + } + } + } + MPS_SCAN_END(ss); + return MPS_RES_OK; +} + + static void test(void *stack_pointer) { /* a is a table of exact roots @@ -201,8 +257,17 @@ static void test(void *stack_pointer) oldstamp = newstamp; newstamp += 1; + mps_arena_formatted_objects_walk(arena, stepper, - (void *) MAGICPOINT, MAGICSIZE); + MAGICPOINT, MAGICSIZE); + + oldstamp = newstamp; + newstamp += 1; + + mps_pool_walk(poolamc, area_scan, MAGICPOINT); + mps_pool_walk(poollo, area_scan, MAGICPOINT); + mps_pool_walk(poolawl, area_scan, MAGICPOINT); + mps_arena_release(arena); comment("tracing...");