mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-01-01 01:41:01 -08:00
264 lines
8.7 KiB
C
264 lines
8.7 KiB
C
/*
|
|
TEST_HEADER
|
|
id = $Id$
|
|
summary = regression test for GitHub issue #61
|
|
language = c
|
|
link = testlib.o newfmt.o
|
|
parameters =
|
|
END_HEADER
|
|
*/
|
|
|
|
#include "testlib.h"
|
|
|
|
#if !defined(MPS_OS_W3)
|
|
|
|
static void test(void *stack_pointer)
|
|
{
|
|
/* nothing to do on non-Windows systems */
|
|
}
|
|
|
|
#else
|
|
|
|
#include "mpsavm.h"
|
|
#include "mpscamc.h"
|
|
#include "newfmt.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <Windows.h>
|
|
|
|
/* On Windows, the MPS uses a vectored exception handler to intercept
|
|
* and handle protection errors. These handlers need to save and
|
|
* restore the value inside GetLastError(), otherwise the mutator may
|
|
* observe that GetLastError() has ben cleared or set to something
|
|
* else (especially if the MPS executed a system call that failed).
|
|
*
|
|
* This test first sets up an AMC pool with the new format in
|
|
* newfmt.h. Then it creates a single root node with room for 100
|
|
* elements, and populates them with nodes as well. Each of these
|
|
* created nodes are populated with pointers from the root node to
|
|
* give the MPS a reason to protect them.
|
|
*
|
|
* We then start looking for nodes residing in pages where the barrier
|
|
* have been raised (both for reads and writes). We continuously
|
|
* allocate new nodes for even indices in the root node. This will
|
|
* trigger the MPS to do garbage collections, and only updating even
|
|
* nodes will ensure that we will end up with pointers to both objects
|
|
* in the nursery generation and in the older generation, increasing
|
|
* the likelihood of us finding a page with the barrier active.
|
|
*
|
|
* When we find a page with the read and write barrier active, we
|
|
* store that pointer and set up the test we want to perform. We want
|
|
* to set up a situation where we can trigger a barrier hit, and that
|
|
* the MPS will call a system call that fails in the exception handler
|
|
* when responding to the hit.
|
|
*
|
|
* The tool we use here is a separate thread. We create and register
|
|
* an additional thread with the MPS. Then we keep that thread alive
|
|
* until we know that the next thing the MPS will do is the barrier
|
|
* hit (if it fails to halt the thread once, it assumes the thread is
|
|
* no longer alive and will no longer try to halt it again). As such,
|
|
* we terminate the thread when we have found a page that will trigger
|
|
* the hit. Then, we can call SetLastError() with som error code, read
|
|
* from the memory with a barrier. This triggers the exception
|
|
* handler, which will realize that it needs to scan the particular
|
|
* page, and thus need to stop threads. In this case, we know that the
|
|
* SuspendThread() call will fail and thus clobbers the value in
|
|
* GetLastError(). Then we can verify that the main thread does not
|
|
* observe this change. This serves as a good (and hopefully,
|
|
* reliable) illustration of what could happen, even if it is
|
|
* tecnically a bad thing to have a terminated thread registered with
|
|
* the MPS.
|
|
*/
|
|
|
|
/* Error code we check for. We pick an error unlikely to happen in the
|
|
* MPS. Typically we see error code 5 (ERROR_ACCESS_DENIED) from
|
|
* SuspendThread() in this case, so anything but that should work.
|
|
*/
|
|
#define CHECK_ERROR_CODE ERROR_NETWORK_BUSY
|
|
|
|
/* Number of cells to allocate. */
|
|
#define NUM_CELLS 100
|
|
|
|
/* Maximum attempts before giving up. It seems like 10 or so is enough
|
|
* in most cases.
|
|
*/
|
|
#define MAX_ATTEMPTS 100
|
|
|
|
/* Get memory protection of the page containing the specified address. */
|
|
static DWORD memory_protection(void *ptr)
|
|
{
|
|
MEMORY_BASIC_INFORMATION info;
|
|
if (VirtualQuery(ptr, &info, sizeof(info)) == 0) {
|
|
printf("VirtualQuery failed\n");
|
|
fail();
|
|
}
|
|
return info.Protect;
|
|
}
|
|
|
|
/* Find a cell linked to from 'root' that is in a read- and write-protected page. */
|
|
static mycell *any_protected(mycell *root)
|
|
{
|
|
int i;
|
|
for (i = 0; i < NUM_CELLS; i++) {
|
|
mycell *cell = getref(root, i);
|
|
DWORD protection = memory_protection(cell);
|
|
|
|
switch (protection) {
|
|
case PAGE_EXECUTE:
|
|
case PAGE_NOACCESS:
|
|
return cell;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Allocate an object and fill it with some pointers to encourage the
|
|
* MPS to raise the read- and write-barrier for the object at some point.
|
|
*/
|
|
static mycell *alloc_cell(mps_ap_t ap, mycell *root)
|
|
{
|
|
int i;
|
|
mycell *cell = allocone(ap, NUM_CELLS);
|
|
for (i = 0; i < NUM_CELLS; i++)
|
|
setref(cell, i, getref(root, i));
|
|
return cell;
|
|
}
|
|
|
|
/* Variables shared by the spawned thread and the main thread. */
|
|
static mps_arena_t arena;
|
|
static mps_thr_t extra_thread;
|
|
static HANDLE terminate_thread;
|
|
|
|
/* Function for the extra thread. The thread registers itself with the
|
|
* MPS, waits until the main thread asks it to terminate, and then
|
|
* exits.
|
|
*/
|
|
static DWORD WINAPI thread_fn(LPVOID parameter) {
|
|
mps_thread_reg(&extra_thread, arena);
|
|
WaitForSingleObject(terminate_thread, INFINITE);
|
|
return 0;
|
|
}
|
|
|
|
static void test(void *stack_pointer)
|
|
{
|
|
mps_fmt_t format;
|
|
mps_pool_t pool;
|
|
mps_thr_t thread;
|
|
mps_root_t root;
|
|
mps_chain_t chain;
|
|
mps_ap_t ap;
|
|
int i, j;
|
|
|
|
mycell *root_cell;
|
|
mycell *protected;
|
|
|
|
HANDLE thread_handle;
|
|
DWORD after_trip;
|
|
|
|
mps_gen_param_s gen_params[2] = {
|
|
/* Make the first generation fairly small to trigger the case we want. */
|
|
{ 512, 0.9 },
|
|
{ 10 * 1024, 0.5 }
|
|
};
|
|
|
|
die(mps_arena_create_k(&arena, mps_arena_class_vm(), mps_args_none), "mps_arena_create_k");
|
|
die(mps_fmt_create_A(&format, arena, &fmtA), "create format");
|
|
die(mps_chain_create(&chain, arena, 2, gen_params), "mps_chain_create");
|
|
die(mps_thread_reg(&thread, arena), "mps_thread_reg");
|
|
die(mps_root_create_thread(&root, arena, thread, stack_pointer), "mps_root_create_thread");
|
|
MPS_ARGS_BEGIN(args) {
|
|
MPS_ARGS_ADD(args, MPS_KEY_FORMAT, format);
|
|
MPS_ARGS_ADD(args, MPS_KEY_CHAIN, chain);
|
|
die(mps_pool_create_k(&pool, arena, mps_class_amc(), args), "mps_pool_create_k");
|
|
} MPS_ARGS_END(args);
|
|
|
|
die(mps_ap_create_k(&ap, pool, mps_args_none), "mps_ap_create_k");
|
|
|
|
/* Launch the extra thread, keep it alive until we are ready. We
|
|
* need to keep it alive since the MPS will stop trying to stop the
|
|
* thread during collections as soon as it realizes that it has
|
|
* terminated. Since this happens during collections, we don't want
|
|
* it to terminate too early.
|
|
*/
|
|
terminate_thread = CreateSemaphore(NULL, 0, 1, NULL);
|
|
thread_handle = CreateThread(NULL, 0, thread_fn, NULL, 0, NULL);
|
|
|
|
/* Allocate a number of cells to start with. */
|
|
root_cell = allocone(ap, NUM_CELLS);
|
|
for (i = 0; i < NUM_CELLS; i++) {
|
|
setref(root_cell, i, alloc_cell(ap, root_cell));
|
|
}
|
|
|
|
/* Now, continue to allocate things, forcing the MPS to do
|
|
* incremental collections from time to time. We do this until we
|
|
* find that the MPS is in the middle of a collection and has raised
|
|
* the read- and write-barrier for some of the objects.
|
|
*/
|
|
for (i = 0; i < MAX_ATTEMPTS; i++) {
|
|
/* Fill every other entry with new objects. This way, some will be
|
|
* in the nursery generation, and others will be in higher generations.
|
|
*/
|
|
for (j = 0; j < NUM_CELLS; j += 2)
|
|
setref(root_cell, j, alloc_cell(ap, root_cell));
|
|
|
|
/* Ask for some collection work (a very small amount). */
|
|
mps_arena_step(arena, 0.0001, 10000);
|
|
|
|
/* See if we are done. */
|
|
protected = any_protected(root_cell);
|
|
if (protected)
|
|
break;
|
|
}
|
|
|
|
/* Make sure we actually triggered the situation we are looking for. */
|
|
asserts(protected != NULL, "Failed to find a protected page after many attempts.");
|
|
|
|
/* Signal the semaphore and wait for the other thread to exit. In
|
|
* this way, we know that the MPS will try (and fail) to stop the
|
|
* thread when we hit the barrier later.
|
|
*/
|
|
ReleaseSemaphore(terminate_thread, 1, NULL);
|
|
WaitForSingleObject(thread_handle, INFINITE);
|
|
|
|
/* Now we have a page that will trip the barrier when we write to
|
|
* it. So we do that and observe the value of GetLastError().
|
|
*/
|
|
SetLastError(CHECK_ERROR_CODE);
|
|
|
|
/* Read from it. In the current implementation, reads are handled by
|
|
* scanning (which is what we want to trigger since that requires
|
|
* suspending threads), which is enough. A write would also work. */
|
|
getref(protected, 0);
|
|
|
|
/* Check so that the error code we set is preserved. Typically,
|
|
* GetLastError() will be 5 if it is not preserved (that is what
|
|
* SuspendThread() sets it to on failure). */
|
|
after_trip = GetLastError();
|
|
asserts(after_trip == CHECK_ERROR_CODE, "Last error was not properly preserved in the exception handler!");
|
|
|
|
/* Tear down MPS data structures. */
|
|
mps_arena_park(arena);
|
|
mps_ap_destroy(ap);
|
|
mps_root_destroy(root);
|
|
mps_thread_dereg(thread);
|
|
mps_thread_dereg(extra_thread);
|
|
mps_pool_destroy(pool);
|
|
mps_chain_destroy(chain);
|
|
mps_fmt_destroy(format);
|
|
mps_arena_destroy(arena);
|
|
|
|
/* Other cleanup. */
|
|
CloseHandle(thread_handle);
|
|
CloseHandle(terminate_thread);
|
|
}
|
|
|
|
#endif
|
|
|
|
int main(void)
|
|
{
|
|
run_test(test);
|
|
pass();
|
|
return 0;
|
|
}
|