diff --git a/mps/code/arena.c b/mps/code/arena.c
index a3301521fae..b73e548c78d 100644
--- a/mps/code/arena.c
+++ b/mps/code/arena.c
@@ -82,6 +82,14 @@ static Res ArenaNoPagesMarkAllocated(Arena arena, Chunk chunk,
return ResUNIMPL;
}
+static Bool ArenaNoChunkPageMapped(Chunk chunk, Index index)
+{
+ UNUSED(chunk);
+ UNUSED(index);
+ NOTREACHED;
+ return FALSE;
+}
+
static Res ArenaNoCreate(Arena *arenaReturn, ArgList args)
{
UNUSED(arenaReturn);
@@ -122,6 +130,7 @@ DEFINE_CLASS(Arena, AbstractArena, klass)
klass->chunkFinish = ArenaNoChunkFinish;
klass->compact = ArenaTrivCompact;
klass->pagesMarkAllocated = ArenaNoPagesMarkAllocated;
+ klass->chunkPageMapped = ArenaNoChunkPageMapped;
klass->sig = ArenaClassSig;
}
@@ -144,6 +153,7 @@ Bool ArenaClassCheck(ArenaClass klass)
CHECKL(FUNCHECK(klass->chunkFinish));
CHECKL(FUNCHECK(klass->compact));
CHECKL(FUNCHECK(klass->pagesMarkAllocated));
+ CHECKL(FUNCHECK(klass->chunkPageMapped));
CHECKS(ArenaClass, klass);
return TRUE;
}
diff --git a/mps/code/arenacl.c b/mps/code/arenacl.c
index 72e8c4af386..757b4c7326c 100644
--- a/mps/code/arenacl.c
+++ b/mps/code/arenacl.c
@@ -383,6 +383,20 @@ static Res ClientArenaPagesMarkAllocated(Arena arena, Chunk chunk,
}
+/* ClientChunkPageMapped -- determine if a page is mapped */
+
+static Bool ClientChunkPageMapped(Chunk chunk, Index index)
+{
+ UNUSED(chunk);
+ UNUSED(index);
+
+ AVERT(Chunk, chunk);
+ AVER(index < chunk->pages);
+
+ return TRUE;
+}
+
+
/* ClientArenaFree - free a region in the arena */
static void ClientArenaFree(Addr base, Size size, Pool pool)
@@ -443,6 +457,7 @@ DEFINE_CLASS(Arena, ClientArena, klass)
klass->free = ClientArenaFree;
klass->chunkInit = ClientChunkInit;
klass->chunkFinish = ClientChunkFinish;
+ klass->chunkPageMapped = ClientChunkPageMapped;
}
diff --git a/mps/code/arenavm.c b/mps/code/arenavm.c
index 0168b4b6ed8..78e2a67cbf4 100644
--- a/mps/code/arenavm.c
+++ b/mps/code/arenavm.c
@@ -932,6 +932,16 @@ static Res VMPagesMarkAllocated(Arena arena, Chunk chunk,
}
+static Bool VMChunkPageMapped(Chunk chunk, Index index)
+{
+ VMChunk vmChunk;
+ AVERT(Chunk, chunk);
+ AVER(index < chunk->pages);
+ vmChunk = Chunk2VMChunk(chunk);
+ return BTGet(vmChunk->pages.mapped, index);
+}
+
+
/* chunkUnmapAroundPage -- unmap spare pages in a chunk including this one
*
* Unmap the spare page passed, and possibly other pages in the chunk,
@@ -1208,6 +1218,7 @@ DEFINE_CLASS(Arena, VMArena, klass)
klass->chunkFinish = VMChunkFinish;
klass->compact = VMCompact;
klass->pagesMarkAllocated = VMPagesMarkAllocated;
+ klass->chunkPageMapped = VMChunkPageMapped;
}
diff --git a/mps/code/config.h b/mps/code/config.h
index 5b73c6d8ff3..021acf277c8 100644
--- a/mps/code/config.h
+++ b/mps/code/config.h
@@ -167,8 +167,9 @@
/* CONFIG_THREAD_SINGLE -- support single-threaded execution only
*
* This symbol causes the MPS to be built for single-threaded
- * execution only, where locks are not needed and so lock operations
- * can be defined as no-ops by lock.h.
+ * execution only, where locks are not needed and so the generic
+ * ("ANSI") lock module lockan.c can be used instead of the
+ * platform-specific lock module.
*/
#if !defined(CONFIG_THREAD_SINGLE)
diff --git a/mps/code/global.c b/mps/code/global.c
index 02b2f04928e..69768a6e5d5 100644
--- a/mps/code/global.c
+++ b/mps/code/global.c
@@ -409,7 +409,7 @@ void GlobalsPrepareToDestroy(Globals arenaGlobals)
arenaGlobals->defaultChain = NULL;
ChainDestroy(defaultChain);
- LockRelease(arenaGlobals->lock);
+ ArenaLeave(arena);
/* Theoretically, another thread could grab the lock here, but it's */
/* not worth worrying about, since an attempt after the lock has been */
/* destroyed would lead to a crash just the same. */
@@ -493,10 +493,9 @@ Ring GlobalsRememberedSummaryRing(Globals global)
/* ArenaEnter -- enter the state where you can look at the arena */
-void (ArenaEnter)(Arena arena)
+void ArenaEnter(Arena arena)
{
- AVERT(Arena, arena);
- ArenaEnter(arena);
+ ArenaEnterLock(arena, FALSE);
}
/* The recursive argument specifies whether to claim the lock
@@ -541,10 +540,10 @@ void ArenaEnterRecursive(Arena arena)
/* ArenaLeave -- leave the state where you can look at MPM data structures */
-void (ArenaLeave)(Arena arena)
+void ArenaLeave(Arena arena)
{
AVERT(Arena, arena);
- ArenaLeave(arena);
+ ArenaLeaveLock(arena, FALSE);
}
void ArenaLeaveLock(Arena arena, Bool recursive)
@@ -574,6 +573,12 @@ void ArenaLeaveRecursive(Arena arena)
ArenaLeaveLock(arena, TRUE);
}
+Bool ArenaBusy(Arena arena)
+{
+ return LockIsHeld(ArenaGlobals(arena)->lock);
+}
+
+
/* mps_exception_info -- pointer to exception info
*
* This is a hack to make exception info easier to find in a release
diff --git a/mps/code/lock.h b/mps/code/lock.h
index 35d0ed6db41..1a02d7997ed 100644
--- a/mps/code/lock.h
+++ b/mps/code/lock.h
@@ -78,6 +78,11 @@ extern void LockRelease(Lock lock);
extern Bool LockCheck(Lock lock);
+/* LockIsHeld -- test whether lock is held by any thread */
+
+extern Bool LockIsHeld(Lock lock);
+
+
/* == Global locks == */
@@ -123,26 +128,6 @@ extern void LockClaimGlobal(void);
extern void LockReleaseGlobal(void);
-#if defined(LOCK)
-/* Nothing to do: functions declared in all lock configurations. */
-#elif defined(LOCK_NONE)
-#define LockSize() MPS_PF_ALIGN
-#define LockInit(lock) UNUSED(lock)
-#define LockFinish(lock) UNUSED(lock)
-#define LockClaimRecursive(lock) UNUSED(lock)
-#define LockReleaseRecursive(lock) UNUSED(lock)
-#define LockClaim(lock) UNUSED(lock)
-#define LockRelease(lock) UNUSED(lock)
-#define LockCheck(lock) ((void)lock, TRUE)
-#define LockClaimGlobalRecursive()
-#define LockReleaseGlobalRecursive()
-#define LockClaimGlobal()
-#define LockReleaseGlobal()
-#else
-#error "No lock configuration."
-#endif /* LOCK */
-
-
#endif /* lock_h */
diff --git a/mps/code/lockan.c b/mps/code/lockan.c
index fe5082a6ebf..8b1b61a5243 100644
--- a/mps/code/lockan.c
+++ b/mps/code/lockan.c
@@ -79,6 +79,12 @@ void (LockReleaseRecursive)(Lock lock)
--lock->claims;
}
+Bool (LockIsHeld)(Lock lock)
+{
+ AVERT(Lock, lock);
+ return lock->claims > 0;
+}
+
/* Global locking is performed by normal locks.
* A separate lock structure is used for recursive and
diff --git a/mps/code/lockcov.c b/mps/code/lockcov.c
index 84866046d82..af225d07c40 100644
--- a/mps/code/lockcov.c
+++ b/mps/code/lockcov.c
@@ -39,20 +39,28 @@ int main(int argc, char *argv[])
Insist(b != NULL);
LockInit(a);
+ Insist(!LockIsHeld(a));
LockInit(b);
+ Insist(!LockIsHeld(b));
LockClaimGlobal();
LockClaim(a);
+ Insist(LockIsHeld(a));
LockClaimRecursive(b);
+ Insist(LockIsHeld(b));
LockClaimGlobalRecursive();
LockReleaseGlobal();
LockClaimGlobal();
LockRelease(a);
+ Insist(!LockIsHeld(a));
LockClaimGlobalRecursive();
LockReleaseGlobal();
LockClaimRecursive(b);
+ Insist(LockIsHeld(b));
LockFinish(a);
LockReleaseRecursive(b);
+ Insist(LockIsHeld(b));
LockReleaseRecursive(b);
+ Insist(!LockIsHeld(b));
LockFinish(b);
LockInit(a);
LockClaim(a);
diff --git a/mps/code/lockix.c b/mps/code/lockix.c
index 5b166fdc625..4c42d754ba2 100644
--- a/mps/code/lockix.c
+++ b/mps/code/lockix.c
@@ -45,6 +45,7 @@
SRCID(lockix, "$Id$");
+#if defined(LOCK)
/* LockStruct -- the MPS lock structure
*
@@ -185,6 +186,21 @@ void (LockReleaseRecursive)(Lock lock)
}
+/* LockIsHeld -- test whether lock is held */
+
+Bool (LockIsHeld)(Lock lock)
+{
+ AVERT(Lock, lock);
+ if (pthread_mutex_trylock(&lock->mut) == 0) {
+ Bool claimed = lock->claims > 0;
+ int res = pthread_mutex_unlock(&lock->mut);
+ AVER(res == 0);
+ return claimed;
+ }
+ return TRUE;
+}
+
+
/* Global locks
*
* .global: The two "global" locks are statically allocated normal locks.
@@ -245,6 +261,13 @@ void (LockReleaseGlobal)(void)
}
+#elif defined(LOCK_NONE)
+#include "lockan.c"
+#else
+#error "No lock configuration."
+#endif
+
+
/* C. COPYRIGHT AND LICENSE
*
* Copyright (C) 2001-2016 Ravenbrook Limited .
diff --git a/mps/code/lockw3.c b/mps/code/lockw3.c
index daf2473d4e3..a471687514e 100644
--- a/mps/code/lockw3.c
+++ b/mps/code/lockw3.c
@@ -31,6 +31,7 @@
SRCID(lockw3, "$Id$");
+#if defined(LOCK)
/* .lock.win32: Win32 lock structure; uses CRITICAL_SECTION */
typedef struct LockStruct {
@@ -103,6 +104,15 @@ void (LockReleaseRecursive)(Lock lock)
LeaveCriticalSection(&lock->cs);
}
+Bool (LockIsHeld)(Lock lock)
+{
+ if (TryEnterCriticalSection(&lock->cs)) {
+ Bool claimed = lock->claims > 0;
+ LeaveCriticalSection(&lock->cs);
+ return claimed;
+ }
+ return TRUE;
+}
/* Global locking is performed by normal locks.
@@ -156,6 +166,13 @@ void (LockReleaseGlobal)(void)
}
+#elif defined(LOCK_NONE)
+#include "lockan.c"
+#else
+#error "No lock configuration."
+#endif
+
+
/* C. COPYRIGHT AND LICENSE
*
* Copyright (C) 2001-2016 Ravenbrook Limited .
diff --git a/mps/code/mpm.h b/mps/code/mpm.h
index 9f5f3365fa4..e2b99e1fead 100644
--- a/mps/code/mpm.h
+++ b/mps/code/mpm.h
@@ -488,7 +488,6 @@ extern Bool ArenaAccess(Addr addr, AccessSet mode, MutatorFaultContext context);
extern Res ArenaFreeLandInsert(Arena arena, Addr base, Addr limit);
extern void ArenaFreeLandDelete(Arena arena, Addr base, Addr limit);
-
extern Bool GlobalsCheck(Globals arena);
extern Res GlobalsInit(Globals arena);
extern void GlobalsFinish(Globals arena);
@@ -524,16 +523,12 @@ extern Bool ArenaGrainSizeCheck(Size size);
extern void ArenaEnterLock(Arena arena, Bool recursive);
extern void ArenaLeaveLock(Arena arena, Bool recursive);
-extern void (ArenaEnter)(Arena arena);
-extern void (ArenaLeave)(Arena arena);
+extern void ArenaEnter(Arena arena);
+extern void ArenaLeave(Arena arena);
extern void (ArenaPoll)(Globals globals);
#if defined(SHIELD)
-#define ArenaEnter(arena) ArenaEnterLock(arena, FALSE)
-#define ArenaLeave(arena) ArenaLeaveLock(arena, FALSE)
#elif defined(SHIELD_NONE)
-#define ArenaEnter(arena) UNUSED(arena)
-#define ArenaLeave(arena) AVER(arena->busyTraces == TraceSetEMPTY)
#define ArenaPoll(globals) UNUSED(globals)
#else
#error "No shield configuration."
@@ -546,10 +541,12 @@ extern Bool (ArenaStep)(Globals globals, double interval, double multiplier);
extern void ArenaClamp(Globals globals);
extern void ArenaRelease(Globals globals);
extern void ArenaPark(Globals globals);
+extern void ArenaPostmortem(Globals globals);
extern void ArenaExposeRemember(Globals globals, Bool remember);
extern void ArenaRestoreProtection(Globals globals);
extern Res ArenaStartCollect(Globals globals, int why);
extern Res ArenaCollect(Globals globals, int why);
+extern Bool ArenaBusy(Arena arena);
extern Bool ArenaHasAddr(Arena arena, Addr addr);
extern Res ArenaAddrObject(Addr *pReturn, Arena arena, Addr addr);
extern void ArenaChunkInsert(Arena arena, Chunk chunk);
@@ -895,7 +892,7 @@ extern void (ShieldFlush)(Arena arena);
#define ShieldLower(arena, seg, mode) \
BEGIN UNUSED(arena); UNUSED(seg); UNUSED(mode); END
#define ShieldEnter(arena) BEGIN UNUSED(arena); END
-#define ShieldLeave(arena) BEGIN UNUSED(arena); END
+#define ShieldLeave(arena) AVER(arena->busyTraces == TraceSetEMPTY)
#define ShieldExpose(arena, seg) \
BEGIN UNUSED(arena); UNUSED(seg); END
#define ShieldCover(arena, seg) \
diff --git a/mps/code/mpmst.h b/mps/code/mpmst.h
index 04c19f63e92..dd0ecd077fc 100644
--- a/mps/code/mpmst.h
+++ b/mps/code/mpmst.h
@@ -504,6 +504,7 @@ typedef struct mps_arena_class_s {
ArenaChunkFinishMethod chunkFinish;
ArenaCompactMethod compact;
ArenaPagesMarkAllocatedMethod pagesMarkAllocated;
+ ArenaChunkPageMappedMethod chunkPageMapped;
Sig sig;
} ArenaClassStruct;
diff --git a/mps/code/mpmtypes.h b/mps/code/mpmtypes.h
index d1180ea9a8d..5d6ce41ff08 100644
--- a/mps/code/mpmtypes.h
+++ b/mps/code/mpmtypes.h
@@ -124,6 +124,7 @@ typedef void (*ArenaCompactMethod)(Arena arena, Trace trace);
typedef Res (*ArenaPagesMarkAllocatedMethod)(Arena arena, Chunk chunk,
Index baseIndex, Count pages,
Pool pool);
+typedef Bool (*ArenaChunkPageMappedMethod)(Chunk chunk, Index index);
/* These are not generally exposed and public, but are part of a commercial
diff --git a/mps/code/mps.h b/mps/code/mps.h
index 6aeffd2962d..c9ecab4cfae 100644
--- a/mps/code/mps.h
+++ b/mps/code/mps.h
@@ -435,6 +435,7 @@ typedef struct mps_fmt_fixed_s {
extern void mps_arena_clamp(mps_arena_t);
extern void mps_arena_release(mps_arena_t);
extern void mps_arena_park(mps_arena_t);
+extern void mps_arena_postmortem(mps_arena_t);
extern void mps_arena_expose(mps_arena_t);
extern void mps_arena_unsafe_expose_remember_protection(mps_arena_t);
extern void mps_arena_unsafe_restore_protection(mps_arena_t);
@@ -460,6 +461,7 @@ extern size_t mps_arena_spare_commit_limit(mps_arena_t);
extern double mps_arena_pause_time(mps_arena_t);
extern void mps_arena_pause_time_set(mps_arena_t, double);
+extern mps_bool_t mps_arena_busy(mps_arena_t);
extern mps_bool_t mps_arena_has_addr(mps_arena_t, mps_addr_t);
extern mps_bool_t mps_addr_pool(mps_pool_t *, mps_arena_t, mps_addr_t);
extern mps_bool_t mps_addr_fmt(mps_fmt_t *, mps_arena_t, mps_addr_t);
diff --git a/mps/code/mpsi.c b/mps/code/mpsi.c
index 9ae6fcbfa97..3449c1aaed6 100644
--- a/mps/code/mpsi.c
+++ b/mps/code/mpsi.c
@@ -260,6 +260,15 @@ void mps_arena_park(mps_arena_t arena)
}
+void mps_arena_postmortem(mps_arena_t arena)
+{
+ /* Don't call ArenaEnter -- one of the purposes of this function is
+ * to release the arena lock if it's held */
+ AVER(TESTT(Arena, arena));
+ ArenaPostmortem(ArenaGlobals(arena));
+}
+
+
void mps_arena_expose(mps_arena_t arena)
{
ArenaEnter(arena);
@@ -374,6 +383,17 @@ void mps_arena_destroy(mps_arena_t arena)
}
+/* mps_arena_busy -- is the arena part way through an operation? */
+
+mps_bool_t mps_arena_busy(mps_arena_t arena)
+{
+ /* Don't call ArenaEnter -- the purpose of this function is to
+ * determine if the arena lock is held */
+ AVER(TESTT(Arena, arena));
+ return ArenaBusy(arena);
+}
+
+
/* mps_arena_has_addr -- is this address managed by this arena? */
mps_bool_t mps_arena_has_addr(mps_arena_t arena, mps_addr_t p)
diff --git a/mps/code/mpsicv.c b/mps/code/mpsicv.c
index d1a9759a3b8..0df2c4bc0c7 100644
--- a/mps/code/mpsicv.c
+++ b/mps/code/mpsicv.c
@@ -450,11 +450,13 @@ static void *test(void *arg, size_t s)
mps_word_t c;
size_t r;
+ Insist(!mps_arena_busy(arena));
+
c = mps_collections(arena);
if(collections != c) {
collections = c;
- printf("\nCollection %"PRIuLONGEST", %lu objects.\n", (ulongest_t)c, i);
+ printf("Collection %"PRIuLONGEST", %lu objects.\n", (ulongest_t)c, i);
for(r = 0; r < exactRootsCOUNT; ++r) {
cdie(exactRoots[r] == objNULL || dylan_check(exactRoots[r]),
"all roots check");
@@ -565,7 +567,7 @@ int main(int argc, char *argv[])
MPS_ARGS_ADD(args, MPS_KEY_PAUSE_TIME, rnd_pause_time());
MPS_ARGS_ADD(args, MPS_KEY_ARENA_SIZE, TEST_ARENA_SIZE);
die(mps_arena_create_k(&arena, mps_arena_class_vm(), args),
- "arena_create\n");
+ "arena_create");
} MPS_ARGS_END(args);
die(mps_thread_reg(&thread, arena), "thread_reg");
@@ -591,9 +593,17 @@ int main(int argc, char *argv[])
}
mps_tramp(&r, test, arena, 0);
- mps_root_destroy(reg_root);
- mps_thread_dereg(thread);
- mps_arena_destroy(arena);
+ switch (rnd() % 2) {
+ default:
+ case 0:
+ mps_root_destroy(reg_root);
+ mps_thread_dereg(thread);
+ mps_arena_destroy(arena);
+ break;
+ case 1:
+ mps_arena_postmortem(arena);
+ break;
+ }
printf("%s: Conclusion: Failed to find any defects.\n", argv[0]);
return 0;
diff --git a/mps/code/qs.c b/mps/code/qs.c
index 2a62a5ef71a..9e3089b3a89 100644
--- a/mps/code/qs.c
+++ b/mps/code/qs.c
@@ -457,6 +457,7 @@ static mps_res_t scan1(mps_ss_t ss, mps_addr_t *objectIO)
static mps_res_t scan(mps_ss_t ss, mps_addr_t base, mps_addr_t limit)
{
+ Insist(mps_arena_busy(arena));
while(base < limit) {
mps_res_t res;
diff --git a/mps/code/tagtest.c b/mps/code/tagtest.c
index 62df8ee2b1d..9b3dea53c60 100644
--- a/mps/code/tagtest.c
+++ b/mps/code/tagtest.c
@@ -77,9 +77,12 @@ static void pad(mps_addr_t addr, size_t size)
}
}
+static mps_arena_t arena;
+
static mps_res_t scan(mps_ss_t ss, mps_addr_t base,
mps_addr_t limit)
{
+ Insist(mps_arena_busy(arena));
MPS_SCAN_BEGIN(ss) {
mps_word_t *p = base;
while (p < (mps_word_t *)limit) {
@@ -106,7 +109,7 @@ static mps_addr_t skip(mps_addr_t addr)
}
-static void collect(mps_arena_t arena, size_t expected)
+static void collect(size_t expected)
{
size_t finalized = 0;
mps_arena_collect(arena);
@@ -148,7 +151,6 @@ static const char *mode_name[] = {
static void test(int mode)
{
- mps_arena_t arena;
mps_thr_t thread;
mps_root_t root;
mps_fmt_t fmt;
@@ -214,7 +216,7 @@ static void test(int mode)
die(mps_finalize(arena, &addr), "finalize");
}
- collect(arena, expected);
+ collect(expected);
mps_arena_park(arena);
mps_ap_destroy(ap);
diff --git a/mps/code/traceanc.c b/mps/code/traceanc.c
index 52761e02092..5326c5f2e21 100644
--- a/mps/code/traceanc.c
+++ b/mps/code/traceanc.c
@@ -530,12 +530,11 @@ void TraceIdMessagesDestroy(Arena arena, TraceId ti)
-/* -------- ArenaRelease, ArenaClamp, ArenaPark -------- */
+/* ----- ArenaRelease, ArenaClamp, ArenaPark, ArenaPostmortem ----- */
-/* ArenaRelease, ArenaClamp, ArenaPark -- allow/prevent collection work.
- *
- * These functions allow or prevent collection work.
+/* ArenaRelease, ArenaClamp, ArenaPark, ArenaPostmortem --
+ * allow/prevent collection work.
*/
@@ -596,6 +595,62 @@ void ArenaPark(Globals globals)
AVER(!ArenaEmergency(arena));
}
+
+/* arenaExpose -- discard all protection from MPS-managed memory
+ *
+ * This is called by ArenaPostmortem, which we expect only to be used
+ * after a fatal error. So we use the lowest-level description of the
+ * MPS-managed memory (the chunk ring page tables) to avoid the risk
+ * of higher-level structures (like the segments) having been
+ * corrupted.
+ *
+ * After calling this function memory may not be in a consistent
+ * state, so it is not safe to continue running the MPS. If you need
+ * to expose memory but continue running the MPS, use
+ * ArenaExposeRemember instead.
+ */
+
+static void arenaExpose(Arena arena)
+{
+ Ring node, next;
+ RING_FOR(node, &arena->chunkRing, next) {
+ Chunk chunk = RING_ELT(Chunk, arenaRing, node);
+ Index i;
+ for (i = 0; i < chunk->pages; ++i) {
+ if (Method(Arena, arena, chunkPageMapped)(chunk, i)) {
+ ProtSet(PageIndexBase(chunk, i), PageIndexBase(chunk, i + 1),
+ AccessSetEMPTY);
+ }
+ }
+ }
+}
+
+
+/* ArenaPostmortem -- enter the postmortem state */
+
+void ArenaPostmortem(Globals globals)
+{
+ Arena arena = GlobalsArena(globals);
+
+ /* Ensure lock is released. */
+ while (LockIsHeld(globals->lock)) {
+ LockReleaseRecursive(globals->lock);
+ }
+
+ /* Remove the arena from the global arena ring so that it no longer
+ * handles protection faults. (Don't call arenaDenounce because that
+ * needs to claim the global ring lock, but that might already be
+ * held, for example if we are inside ArenaAccess.) */
+ RingRemove(&globals->globalRing);
+
+ /* Clamp the arena so that ArenaPoll does nothing. */
+ ArenaClamp(globals);
+
+ /* Remove all protection from mapped pages. */
+ arenaExpose(arena);
+}
+
+
/* ArenaStartCollect -- start a collection of everything in the
* arena; leave unclamped. */
diff --git a/mps/design/config.txt b/mps/design/config.txt
index 458ccdfe890..3bf1d96e29c 100644
--- a/mps/design/config.txt
+++ b/mps/design/config.txt
@@ -542,7 +542,8 @@ platform instead.
_`.opt.thread`: ``CONFIG_THREAD_SINGLE`` causes the MPS to be built
for single-threaded execution only, where locks are not needed and so
-lock operations can be defined as no-ops by ``lock.h``.
+the generic ("ANSI") lock module ``lockan.c`` can be used instead of
+the platform-specific lock module.
_`.opt.poll`: ``CONFIG_POLL_NONE`` causes the MPS to be built without
support for polling. This means that garbage collections will only
diff --git a/mps/design/lock.txt b/mps/design/lock.txt
index 6dc4e55de15..45737737bb4 100644
--- a/mps/design/lock.txt
+++ b/mps/design/lock.txt
@@ -59,6 +59,13 @@ be claimed again by the thread currently holding them, without
blocking or deadlocking. (This is needed to implement the global
recursive lock.)
+_`.req.held`: Provide a means to test if a lock is held. (This is
+needed for debugging a dynamic function table callback on Windows on
+x86-64. See ``mps_arena_busy()`` for a detailed description of this
+use case. Note that in this use case the program is running
+single-threaded and so there is no need for this feature to be
+thread-safe.)
+
_`.req.global`: Provide *global* locks: that is locks that need not be
allocated or initialized by the user.
@@ -124,6 +131,11 @@ thread and claims the lock (if not already held).
Restores the previous state of the lock remembered by the
corresponding ``LockClaimRecursive()`` call.
+``Bool LockIsHeld(Lock lock)``
+
+Return true if the lock is held by any thread, false otherwise. Note
+that this function need not be thread-safe (see `.req.held`_).
+
``void LockClaimGlobal(void)``
Claims ownership of the binary global lock which was previously not
diff --git a/mps/manual/source/glossary/c.rst b/mps/manual/source/glossary/c.rst
index 2165bac2394..478276e619b 100644
--- a/mps/manual/source/glossary/c.rst
+++ b/mps/manual/source/glossary/c.rst
@@ -193,14 +193,14 @@ Memory Management Glossary: C
.. mps:specific::
- One of the three states an :term:`arena` can be in (the
- others being the :term:`unclamped state` and the
- :term:`parked state`). In the clamped state, no object
- motion occurs and the staleness of :term:`location
- dependencies` does not change. However, a :term:`garbage
- collection` may be in progress. Call
- :c:func:`mps_arena_clamp` to put an arena into the clamped
- state.
+ One of the four states an :term:`arena` can be in (the
+ others being the :term:`unclamped state`, the
+ :term:`parked state`, and the :term:`postmortem state`).
+ In the clamped state, no object motion occurs and the
+ staleness of :term:`location dependencies` does not
+ change. However, a :term:`garbage collection` may be in
+ progress. Call :c:func:`mps_arena_clamp` to put an arena
+ into the clamped state.
client arena
diff --git a/mps/manual/source/glossary/index.rst b/mps/manual/source/glossary/index.rst
index ab67f3077ec..10b76d801a7 100644
--- a/mps/manual/source/glossary/index.rst
+++ b/mps/manual/source/glossary/index.rst
@@ -410,6 +410,7 @@ All
:term:`pointer`
:term:`pool`
:term:`pool class`
+:term:`postmortem state`
:term:`precise garbage collection `
:term:`precise reference `
:term:`precise root `
diff --git a/mps/manual/source/glossary/p.rst b/mps/manual/source/glossary/p.rst
index 4ca1dbcd275..f968a6471d0 100644
--- a/mps/manual/source/glossary/p.rst
+++ b/mps/manual/source/glossary/p.rst
@@ -179,14 +179,15 @@ Memory Management Glossary: P
.. mps:specific::
- One of the three states an :term:`arena` can be in (the
- others being the :term:`clamped state` and the
- :term:`unclamped state`). In the parked state, no
- :term:`garbage collection` is in progress, no object
- motion occurs and the staleness of :term:`location
- dependencies` does not change. Call
- :c:func:`mps_arena_park` or :c:func:`mps_arena_collect` to
- put an arena into the parked state.
+ One of the four states an :term:`arena` can be in (the
+ others being the :term:`clamped state`, the
+ :term:`postmortem state`, and the :term:`unclamped
+ state`). In the parked state, no :term:`garbage
+ collection` is in progress, no object motion occurs and
+ the staleness of :term:`location dependencies` does not
+ change. Call :c:func:`mps_arena_park` or
+ :c:func:`mps_arena_collect` to put an arena into the
+ parked state.
perfect fit
@@ -402,6 +403,21 @@ Memory Management Glossary: P
class of :term:`pools` that manage memory according to
particular policy. See :ref:`pool`.
+ postmortem state
+
+ .. mps:specific::
+
+ One of the four states an :term:`arena` can be in (the
+ others being the :term:`unclamped state`, the
+ :term:`clamped state`, and the :term:`parked state`). In
+ the postmortem state, objects do not move in memory, the
+ staleness of :term:`location dependencies` does not
+ change, memory occupied by :term:`unreachable` objects is
+ not recycled, all memory protection is removed, and memory
+ may be in an inconsistent state. Call
+ :c:func:`mps_arena_postmortem` to put an arena into the
+ postmortem state.
+
precise garbage collection
.. see:: :term:`exact garbage collection`.
diff --git a/mps/manual/source/glossary/u.rst b/mps/manual/source/glossary/u.rst
index 71f8fe78ac3..756ed88bc33 100644
--- a/mps/manual/source/glossary/u.rst
+++ b/mps/manual/source/glossary/u.rst
@@ -49,12 +49,12 @@ Memory Management Glossary: U
.. mps:specific::
- One of the three states an :term:`arena` can be in (the
- others being the :term:`clamped state` and the
- :term:`parked state`). In the unclamped state, object
- motion and other background activity may occur. Call
- :c:func:`mps_arena_release` to put an arena into the
- unclamped state.
+ One of the four states an :term:`arena` can be in (the
+ others being the :term:`clamped state`, the :term:`parked
+ state` and the :term:`postmortem state`). In the unclamped
+ state, object motion and other background activity may
+ occur. Call :c:func:`mps_arena_release` to put an arena
+ into the unclamped state.
undead
diff --git a/mps/manual/source/guide/debug.rst b/mps/manual/source/guide/debug.rst
index 3c1678e660b..a922ad35af7 100644
--- a/mps/manual/source/guide/debug.rst
+++ b/mps/manual/source/guide/debug.rst
@@ -94,6 +94,31 @@ General debugging advice
On OS X, barrier hits do not use signals and so do not enter the
debugger.
+#. .. index::
+ single: postmortem debugging
+ single: postmortem state
+
+ If the :term:`client program` is stopped in the debugger with the
+ MPS part of the way through execution of an operation in an
+ :term:`arena` (for example, a crash inside a :term:`scan method`),
+ it will not be possible to call introspection functions, such as
+ :c:func:`mps_arena_has_addr` or :c:func:`mps_addr_pool` (because
+ the MPS is not re-entrant), and it may not be possible to examine
+ some regions of memory (because they are :term:`protected` by the
+ MPS).
+
+ If you are in this situation and would like to be able to call MPS
+ functions or examine regions of memory from the debugger, then you
+ can put the arena into the :term:`postmortem state` by calling
+ :c:func:`mps_arena_postmortem` from the debugger. This unlocks the
+ arena and turns off protection.
+
+ .. warning::
+
+ After calling :c:func:`mps_arena_postmortem`, MPS-managed
+ memory is not in a consistent state, and so it is not safe to
+ continue running the client program.
+
.. index::
single: ASLR
diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst
index 5f73f0ad5b7..831f123cc36 100644
--- a/mps/manual/source/release.rst
+++ b/mps/manual/source/release.rst
@@ -25,6 +25,12 @@ New features
.. _LinuxThreads: http://pauillac.inria.fr/~xleroy/linuxthreads/
+#. New function :c:func:`mps_arena_postmortem` assists with postmortem
+ debugging.
+
+#. New function :c:func:`mps_arena_busy` assists debugging of re-entry
+ errors in dynamic function table callbacks on Windows on x86-64.
+
Interface changes
.................
diff --git a/mps/manual/source/topic/arena.rst b/mps/manual/source/topic/arena.rst
index f1386781572..a64c468325a 100644
--- a/mps/manual/source/topic/arena.rst
+++ b/mps/manual/source/topic/arena.rst
@@ -430,12 +430,10 @@ Arena properties
operating system.
The function :c:func:`mps_arena_committed` may be called whatever
- state the the arena is in (:term:`unclamped `,
- :term:`clamped `, or :term:`parked `). If it is called when the arena is in the unclamped state
- then the value may change after this function returns. A possible
- use might be to call it just after :c:func:`mps_arena_collect` to
- estimate the size of the heap.
+ state the the arena is in. If it is called when the arena is in
+ the :term:`unclamped state` then the value may change after this
+ function returns. A possible use might be to call it just after
+ :c:func:`mps_arena_collect` to estimate the size of the heap.
If you want to know how much memory the MPS is using then you're
probably interested in the value :c:func:`mps_arena_committed` −
@@ -649,21 +647,41 @@ An arena is always in one of three states.
The *parked state* is the same as the clamped state, with the
additional constraint that no garbage collections are in progress.
+#. .. index::
+ single: arena; postmortem state
+ single: postmortem state
+
+ In the *postmortem state*, incremental collection does not take
+ place, objects do not move in memory, references do not change, the
+ staleness of :term:`location dependencies` does not change, and
+ memory occupied by :term:`unreachable` objects is not recycled.
+ Additionally, all memory protection is removed, and memory may be
+ in an inconsistent state.
+
+ .. warning::
+
+ In this state, memory managed by the arena is not in a
+ consistent state, and so it is not safe to continue running the
+ client program. This state is intended for postmortem debugging
+ only.
+
+
Here's a summary:
-============================================ ================================== ============================= ===========================
-State unclamped clamped parked
-============================================ ================================== ============================= ===========================
-Collections may be running? yes yes no
-New collections may start? yes no no
-Objects may move? yes no no
-Location dependencies may become stale? yes no no
-Memory may be returned to the OS? yes no no
-Functions that leave the arena in this state :c:func:`mps_arena_create_k`, :c:func:`mps_arena_clamp`, :c:func:`mps_arena_park`,
+============================================ ================================== ============================= =========================== ==============================
+State unclamped clamped parked postmortem
+============================================ ================================== ============================= =========================== ==============================
+Collections may be running? yes yes no yes
+New collections may start? yes no no no
+Objects may move? yes no no no
+Location dependencies may become stale? yes no no no
+Memory may be returned to the OS? yes no no no
+Safe to continue running? yes yes yes no
+Functions that leave the arena in this state :c:func:`mps_arena_create_k`, :c:func:`mps_arena_clamp`, :c:func:`mps_arena_park`, :c:func:`mps_arena_postmortem`
:c:func:`mps_arena_release`, :c:func:`mps_arena_step` :c:func:`mps_arena_collect`
- :c:func:`mps_arena_start_collect`,
- :c:func:`mps_arena_step`
-============================================ ================================== ============================= ===========================
+ :c:func:`mps_arena_start_collect`,
+ :c:func:`mps_arena_step`
+============================================ ================================== ============================= =========================== ==============================
The clamped and parked states are important when introspecting and
debugging. If you are examining the contents of the heap, you don't
@@ -688,7 +706,7 @@ can only be called in this state.
Put an :term:`arena` into the :term:`clamped state`.
- ``arena`` is the arena to clamp.
+ ``arena`` is the arena.
In the clamped state, no object motion will occur and the
staleness of :term:`location dependencies` will not change. All
@@ -706,7 +724,7 @@ can only be called in this state.
Put an :term:`arena` into the :term:`parked state`.
- ``arena`` is the arena to park.
+ ``arena`` is the arena.
While an arena is parked, no object motion will occur and the
staleness of :term:`location dependencies` will not change. All
@@ -720,14 +738,42 @@ can only be called in this state.
.. c:function:: void mps_arena_release(mps_arena_t arena)
- Puts an arena into the :term:`unclamped state`.
+ Put an arena into the :term:`unclamped state`.
- ``arena`` is the arena to unclamp.
+ ``arena`` is the arena.
While an arena is unclamped, :term:`garbage collection`, object
motion, and other background activity can take place.
+.. c:function:: void mps_arena_postmortem(mps_arena_t arena)
+
+ Put an arena into the :term:`postmortem state`.
+
+ ``arena`` is the arena.
+
+ In the postmortem state, incremental collection does not take
+ place, objects do not move in memory, references do not change,
+ the staleness of :term:`location dependencies` does not change,
+ and memory occupied by :term:`unreachable` objects is not
+ recycled. Additionally, all memory protection is removed, and
+ memory may be in an inconsistent state.
+
+ .. warning::
+
+ 1. After calling this function, memory managed by the arena is
+ not in a consistent state, and so it is no longer safe to
+ continue running the client program. This functions is
+ intended for postmortem debugging only.
+
+ 2. This function must be called from the thread that holds the
+ arena lock (if any thread holds it). This is the case if the
+ program is single-threaded, or if it is called from an MPS
+ assertion handler. When calling this function from the
+ debugger, check the stack to see which thread has the MPS
+ arena lock.
+
+
.. index::
single: garbage collection; running
single: collection; running
@@ -885,9 +931,10 @@ application.
.. index::
pair: arena; introspection
+ pair: arena; debugging
-Arena introspection
--------------------
+Arena introspection and debugging
+---------------------------------
.. note::
@@ -903,6 +950,51 @@ Arena introspection
address belongs.
+.. c:function:: mps_bool_t mps_arena_busy(mps_arena_t arena)
+
+ Return true if an :term:`arena` is part of the way through
+ execution of an operation, false otherwise.
+
+ ``arena`` is the arena.
+
+ .. note::
+
+ This function is intended to assist with debugging fatal
+ errors in the :term:`client program`. It is not expected to be
+ needed in normal use. If you find yourself wanting to use this
+ function other than in the use case described below, there may
+ be a better way to meet your requirements: please
+ :ref:`contact us `.
+
+ A debugger running on Windows on x86-64 needs to decode the
+ call stack, which it does by calling a callback that was
+ previously installed in the dynamic function table using
+ |RtlInstallFunctionTableCallback|_. If the debugger is entered
+ while the arena is busy, and if the callback needs to read
+ from MPS-managed memory, then it may attempt to re-enter the
+ MPS, which will fail as the MPS is not re-entrant.
+
+ .. |RtlInstallFunctionTableCallback| replace:: ``RtlInstallFunctionTableCallback()``
+ .. _RtlInstallFunctionTableCallback: https://msdn.microsoft.com/en-us/library/windows/desktop/ms680595(v=vs.85).aspx
+
+ If this happens, in order to allow the debugger to finish
+ decoding the call stack, the only remedy is to put the arena
+ into the :term:`postmortem state`, so that memory is
+ :term:`unprotected` and objects do not move. So in your
+ dynamic function table callback, you might write::
+
+ if (mps_arena_busy(arena)) {
+ mps_arena_postmortem(arena);
+ }
+
+ .. warning::
+
+ This function only gives a reliable result in single-threaded
+ programs, and in multi-threaded programs where all threads but
+ one are known to be stopped (as they are when the debugger is
+ decoding the call stack in the use case described above).
+
+
.. c:function:: mps_bool_t mps_arena_has_addr(mps_arena_t arena, mps_addr_t addr)
Test whether an :term:`address` is managed by an :term:`arena`.