diff --git a/mps/code/addrobj.c b/mps/code/addrobj.c new file mode 100644 index 00000000000..f4fe524771a --- /dev/null +++ b/mps/code/addrobj.c @@ -0,0 +1,240 @@ +/* addrobj.c: BASE ADDRESS FROM INTERIOR POINTER TEST + * + * Copyright (c) 2023 Ravenbrook Limited. See end of file for license. + * + * .overview This test is for mps_addr_object(). Its intention is to + * verify that the function returns the appropriate base pointer to an + * object when provided with an interior pointer. It also tests that the + * function fails appropriately when the provided with a pointer to + * unmanaged memory, or to an object in a pool that doesn't support this + * feature. + * + * .limitations Objects that have been moved should cause the function to + * fail with MPS_RES_FAIL, however this is not tested. It could be tested if + * a testbench deliberately created a forwarding object, however this might + * confuse a pool that does automatic garbage collection such as AMC or AMCZ, + * so any such test would need to be designed to handle that. + * This test only examines behaviour in AMCZ and MVFF pools, i.e. A pool (AMCZ) + * which currently implements mps_addr_object() and one (MVFF) that doesn't. + */ + +#include "mps.h" +#include "testlib.h" +#include "fmtdy.h" +#include "fmtdytst.h" +#include "mpsavm.h" +#include "mpscamc.h" +#include "mpscmvff.h" +#include "stdio.h" +#include + +/* Define an object size to allocate. The size chosen doesn't matter much, except that this testbench assumes + that the object is large enough that a pointer could point to the interior of the object, without also + pointing to the base pointer of the object at the same time. For char pointers, this is probably 2 bytes. + Since we are using the Dylan library, we define the size of the object in terms of Dylan slots. See + fmtdytst.c for details of the Dylan object structure.*/ +#define N_SLOT_TESTOBJ 100 + +static void test_main(void) +{ + mps_arena_t arena; + mps_pool_t amcz_pool, mvff_pool; + mps_ap_t obj_ap; + mps_fmt_t obj_fmt; + mps_root_t testobj_root; + mps_res_t res; + /* In another testbench (extcon.c) we observed unreliable failures to do with registering the cold end + of the stack. See GitHub issue #210 + . For now, we + declare this as a separate root. */ + static mps_addr_t testobj; + mps_addr_t out, in; + + /* Create arena */ + die(mps_arena_create_k(&arena, mps_arena_class_vm(), mps_args_none), "mps_arena_create_k"); + + + /* INTRO TO TESTS: There are several tests. They test the expected "normal" operation of the + function, using an interior pointer, also corner cases where the interior pointer equals the + base pointer, where it equals the limit pointer. We also test asking about an address in unmanaged + memory, and about an address in a pool which currently does not support mps_addr_object. If you write + more tests, describe them here.*/ + + + /* TEST 1: Test using an interior pointer in an object in an AMCZ pool. + At the time of writing this test, the AMCZ pool is the only pool where + there exists a requirement to provide base addresses from interior pointers. + Currently, the AMCZ pool (and by extension, the AMC pool which shares the same + module as AMCZ) is the only pool for which mps_addr_object is implemented */ + + /* Use the dylan format for convenience */ + die(dylan_fmt(&obj_fmt, arena), "dylan_fmt"); + + /* Create the pool */ + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_FORMAT, obj_fmt); + die(mps_pool_create_k(&amcz_pool, arena, mps_class_amcz(), args), "mps_pool_create_k amcz"); + } MPS_ARGS_END(args); + + /* Create an area of ambiguous pointers to keep the object alive and in place, in this case + the area only contains room for a single reference since we are only using one object to test */ + die(mps_root_create_area(&testobj_root, arena, + mps_rank_ambig(), (mps_rm_t)0, + &testobj, &testobj+1, + mps_scan_area, NULL), + "mps_root_create_area"); + + /* Create the allocation point */ + die(mps_ap_create_k(&obj_ap, amcz_pool, mps_args_none), "mps_ap_create_k"); + + /* Make a Dylan object, size = (N_SLOT_TESTOBJ+2) * sizeof(mps_word_t). + (See fmtdytst.c for size calculation) */ + { + /* Because make_dylan_vector returns its pointer-to-object as an mps_word_t rather than an + mps_addr_t, and commits the object, we need to somehow safely allocate our object without + type punning and without risking that our object be destroyed. + Rather than redefine our reference table with type mps_word_t, which hides the intention of the table, + park the arena to disable garbage collection. Allocate our dylan object on the (unregistered) stack + storing its address in an mps_word_t. Then store this mps_word_t as an mps_addr_t in our reference + table, and release the arena since our object is now safely pinned. + Another approach would be to create another static registered root for ambiguous references of type + mps_word_t and then copy to the mps_addr_t root, which would avoid needing to park the arena. + */ + mps_word_t p_word; + mps_arena_park(arena); + die(make_dylan_vector(&p_word, obj_ap, N_SLOT_TESTOBJ), "make_dylan_vector"); + /* If we hadn't parked the arena, our vector might have been GC'd here */ + testobj = (mps_addr_t)p_word; + mps_arena_release(arena); + } + + /* Construct a pointer to roughly halfway inside the object */ + in = (mps_addr_t)((char *)testobj + (N_SLOT_TESTOBJ/2) * sizeof(mps_word_t)); + + /* Ensure that this is an interior pointer, and not the base pointer, + since we want to make sure we are testing with a true interior pointer and not + one that also happens to be the base pointer. This Insist is intended to protect + against the testbench losing its ability to test "true" interior pointers (i.e. ones + which don't match the base pointer) if the test object sizes were changed to be very + small. Note that we don't currently consider the "limit" of the object as a corner case + (so we don't Insist(in != limit) ) but we do consider limit+1, i.e. the pointer to the + next object to be a corner case. This test could be updated to consider in == limit as a + corner case. */ + Insist(in > testobj); + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(out == testobj); + Insist(res == MPS_RES_OK); + printf("Interior pointer input: passed\n"); + + + /* TEST 2: Test using the base pointer itself as an input*/ + + in = testobj; + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(out == testobj); + Insist(res == MPS_RES_OK); + printf("Base pointer input: passed\n"); + + + + /* TEST 3: Test using a pointer one-off-the-end of the object*/ + + in = (mps_addr_t)((char *)testobj + (N_SLOT_TESTOBJ + 2) * sizeof(mps_word_t)); + + /* Do Test */ + res = mps_addr_object(&out, arena, in); + Insist(res == MPS_RES_FAIL); + printf("Pointer to next object input: passed\n"); + + + /* Clean up from above tests */ + mps_root_destroy(testobj_root); + mps_ap_destroy(obj_ap); + mps_pool_destroy(amcz_pool); + mps_fmt_destroy(obj_fmt); + + + /* TEST 4: Test using a pointer in unmanaged memory */ + + /* Use malloc to allocate non-mps-managed memory on the heap */ + in = malloc(sizeof(mps_word_t)); + Insist(NULL != in); + + /* Do the test */ + res = mps_addr_object(&out, arena, in); + + /* Expect MPS to fail to find a base pointer for addresses not in managed memory */ + Insist(res == MPS_RES_FAIL); + printf("Pointer to unmanaged memory input: passed\n"); + + /* clean up from this test */ + if (NULL != in) + free(in); + + + /* TEST 5: Test using a pointer in a pool which currently doesn't implement mps_addr_object */ + + /* Create mvff pool for which mps_addr_object is not implemented */ + die(mps_pool_create_k(&mvff_pool, arena, mps_class_mvff(), mps_args_none), "mps_pool_create_k mvff"); + + /* allocate an object (just some memory) in this pool */ + die(mps_alloc(&in, mvff_pool, sizeof(mps_word_t)), "mps_alloc"); + + /* Do the test */ + res = mps_addr_object(&out, arena, in); + + Insist(res == MPS_RES_UNIMPL); + printf("Pointer to object in pool where mps_addr_object not implemented: passed\n"); + + + /* If more tests are added here, briefly describe them above under "INTRO TO TESTS" comment */ + + /* Final clean up */ + mps_free(mvff_pool, in, sizeof(mps_word_t)); + mps_pool_destroy(mvff_pool); + mps_arena_destroy(arena); +} + +int main(int argc, char *argv[]) +{ + testlib_init(argc, argv); + + test_main(); + + printf("%s: Conculsion, failed to find any defects.\n", argv[0]); + + return 0; +} + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2022-2023 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/mps/code/arena.c b/mps/code/arena.c index 32de9f0aec1..7139ff4bc86 100644 --- a/mps/code/arena.c +++ b/mps/code/arena.c @@ -1364,6 +1364,22 @@ Bool ArenaHasAddr(Arena arena, Addr addr) return TractOfAddr(&tract, arena, addr); } +/* ArenaAddrObject -- return base pointer of managed object */ +Res ArenaAddrObject(Addr *pReturn, Arena arena, Addr addr) +{ + Tract tract; + + AVER(pReturn != NULL); + AVERT(Arena, arena); + + if (!TractOfAddr(&tract, arena, addr)) { + /* address does not belong to the arena */ + return ResFAIL; + } + + return PoolAddrObject(pReturn, TractPool(tract), addr); +} + /* C. COPYRIGHT AND LICENSE * diff --git a/mps/code/arenavm.c b/mps/code/arenavm.c index 19bc8d82a5b..50708e95638 100644 --- a/mps/code/arenavm.c +++ b/mps/code/arenavm.c @@ -428,6 +428,10 @@ static Bool vmChunkDestroy(Tree tree, void *closure) { Chunk chunk; VMChunk vmChunk; + Arena arena; + Addr base; + Size size; + VMArena vmArena; AVERT(Tree, tree); AVER(closure == UNUSED_POINTER); @@ -437,8 +441,14 @@ static Bool vmChunkDestroy(Tree tree, void *closure) AVERT(Chunk, chunk); vmChunk = Chunk2VMChunk(chunk); AVERT(VMChunk, vmChunk); + arena = ChunkArena(chunk); + vmArena = MustBeA(VMArena, arena); + base = chunk->base; + size = ChunkSize(chunk); - (void)vmArenaUnmapSpare(ChunkArena(chunk), ChunkSize(chunk), chunk); + (*vmArena->contracted)(arena, base, size); + + (void)vmArenaUnmapSpare(arena, size, chunk); SparseArrayFinish(&vmChunk->pages); @@ -778,6 +788,7 @@ static void VMArenaDestroy(Arena arena) * */ arena->primary = NULL; TreeTraverseAndDelete(&arena->chunkTree, vmChunkDestroy, UNUSED_POINTER); + AVER(arena->chunkTree == TreeEMPTY); /* Must wait until the chunks are destroyed, since vmChunkDestroy calls vmArenaUnmapSpare which uses the spare land. */ @@ -1223,7 +1234,6 @@ static Bool vmChunkCompact(Tree tree, void *closure) { Chunk chunk; Arena arena = closure; - VMArena vmArena = MustBeA(VMArena, arena); AVERT(Tree, tree); @@ -1232,11 +1242,6 @@ static Bool vmChunkCompact(Tree tree, void *closure) if(chunk != arena->primary && BTIsResRange(chunk->allocTable, 0, chunk->pages)) { - Addr base = chunk->base; - Size size = ChunkSize(chunk); - /* Callback before destroying the chunk, as the arena is (briefly) - invalid afterwards. See job003893. */ - (*vmArena->contracted)(arena, base, size); vmChunkDestroy(tree, UNUSED_POINTER); return TRUE; } else { diff --git a/mps/code/comm.gmk b/mps/code/comm.gmk index a2cd68baab9..b5ced1136c7 100644 --- a/mps/code/comm.gmk +++ b/mps/code/comm.gmk @@ -253,6 +253,7 @@ LIB_TARGETS=mps.a mpsplan.a TEST_TARGETS=\ abqtest \ + addrobj \ airtest \ amcss \ amcsshe \ @@ -267,6 +268,7 @@ TEST_TARGETS=\ btcv \ bttest \ djbench \ + extcon \ finalcv \ finaltest \ forktest \ @@ -445,6 +447,9 @@ ifdef VARIETY $(PFM)/$(VARIETY)/abqtest: $(PFM)/$(VARIETY)/abqtest.o \ $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a +$(PFM)/$(VARIETY)/addrobj: $(PFM)/$(VARIETY)/addrobj.o \ + $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a + $(PFM)/$(VARIETY)/airtest: $(PFM)/$(VARIETY)/airtest.o \ $(FMTSCMOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a @@ -487,6 +492,9 @@ $(PFM)/$(VARIETY)/bttest: $(PFM)/$(VARIETY)/bttest.o \ $(PFM)/$(VARIETY)/djbench: $(PFM)/$(VARIETY)/djbench.o \ $(TESTLIBOBJ) $(TESTTHROBJ) +$(PFM)/$(VARIETY)/extcon: $(PFM)/$(VARIETY)/extcon.o \ + $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a + $(PFM)/$(VARIETY)/finalcv: $(PFM)/$(VARIETY)/finalcv.o \ $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a diff --git a/mps/code/commpost.nmk b/mps/code/commpost.nmk index 470db2519b7..94152ca28db 100644 --- a/mps/code/commpost.nmk +++ b/mps/code/commpost.nmk @@ -177,6 +177,9 @@ $(PFM)\cool\mps.lib: $(MPMOBJ) $(PFM)\$(VARIETY)\abqtest.exe: $(PFM)\$(VARIETY)\abqtest.obj \ $(PFM)\$(VARIETY)\mps.lib $(TESTLIBOBJ) +$(PFM)\$(VARIETY)\addrobj.exe: $(PFM)\$(VARIETY)\addrobj.obj \ + $(PFM)\$(VARIETY)\mps.lib $(FMTTESTOBJ) $(TESTLIBOBJ) + $(PFM)\$(VARIETY)\airtest.exe: $(PFM)\$(VARIETY)\airtest.obj \ $(PFM)\$(VARIETY)\mps.lib $(FMTSCHEMEOBJ) $(TESTLIBOBJ) @@ -225,6 +228,9 @@ $(PFM)\$(VARIETY)\cvmicv.exe: $(PFM)\$(VARIETY)\cvmicv.obj \ $(PFM)\$(VARIETY)\djbench.exe: $(PFM)\$(VARIETY)\djbench.obj \ $(TESTLIBOBJ) $(TESTTHROBJ) +$(PFM)\$(VARIETY)\extcon.exe: $(PFM)\$(VARIETY)\extcon.obj \ + $(PFM)\$(VARIETY)\mps.lib $(FMTTESTOBJ) $(TESTLIBOBJ) + $(PFM)\$(VARIETY)\finalcv.exe: $(PFM)\$(VARIETY)\finalcv.obj \ $(PFM)\$(VARIETY)\mps.lib $(FMTTESTOBJ) $(TESTLIBOBJ) diff --git a/mps/code/commpre.nmk b/mps/code/commpre.nmk index 236e08ad2eb..b485d25d44b 100644 --- a/mps/code/commpre.nmk +++ b/mps/code/commpre.nmk @@ -59,6 +59,7 @@ LIB_TARGETS=mps.lib TEST_TARGETS=\ abqtest.exe \ + addrobj.exe \ airtest.exe \ amcss.exe \ amcsshe.exe \ @@ -73,6 +74,7 @@ TEST_TARGETS=\ btcv.exe \ bttest.exe \ djbench.exe \ + extcon.exe \ finalcv.exe \ finaltest.exe \ fotest.exe \ diff --git a/mps/code/extcon.c b/mps/code/extcon.c new file mode 100644 index 00000000000..4bd04a14b22 --- /dev/null +++ b/mps/code/extcon.c @@ -0,0 +1,306 @@ +/* extcon.c: ARENA EXTENDED AND CONTRACTED CALLBACK TEST + * + * $Id$ + * Copyright (c) 2022-2023 Ravenbrook Limited. See end of file for license. + * + * .overview: This test case allocates a bunch of large objects, of a size + * similar to the size of the arena, to force the arena to extend. It then + * discards the base pointers to those objects, and forces a collection. + * + * .limitations: This test checks that the EXTENDED and CONTRACTED + * callbacks were called at least once, and that they are called the + * same number of times. It does not check that the extensions and + * contractions themselves were performed correctly, nor does it check + * that an appropriate number of extensions and contractions took + * place, nor does it check that they took place at sensible times. + * + * .dylan: This test uses Dylan format objects in common with most + * other tests for convenience and brevity. + */ + +#include "mps.h" +#include "testlib.h" +#include "fmtdy.h" +#include "fmtdytst.h" +#include "mpsavm.h" +#include "mpscamc.h" +#include +#include + +/* Number of test objects to allocate */ +#define N_TESTOBJ 100 +/* The number of slots determines the size of each object */ +#define N_SLOT_TESTOBJ 10000 +/* The initial arena size is requested to be bigger the test object by + this many bytes */ +#define SIZEDIFF 10 + +/* Set alignment to mps_word_ts */ +#define ALIGNMENT sizeof(mps_word_t) + +/* Align size upwards to the next multiple of the word size. */ +#define ALIGN_WORD(size) \ + (((size) + ALIGNMENT - 1) & ~(ALIGNMENT - 1)) + +/* Global objects*/ +static mps_arena_t arena; /* the arena */ +static mps_pool_t obj_pool; /* pool for test objects */ +static mps_ap_t obj_ap; /* allocation point used to allocate objects */ + +/* Count of number of arena contractions and extensions */ +static int n_contract = 0; +static int n_extend = 0; + +/* Callback functions for arena extension and contraction */ +static void arena_extended_cb(mps_arena_t arena_in, mps_addr_t addr, size_t size) +{ + testlib_unused(arena_in); + testlib_unused(addr); + testlib_unused(size); + printf("Arena extended by %"PRIuLONGEST" bytes\n", (ulongest_t)size); + n_extend++; +} + +static void arena_contracted_cb(mps_arena_t arena_in, mps_addr_t addr, size_t size) +{ + testlib_unused(arena_in); + testlib_unused(addr); + testlib_unused(size); + printf("Arena contracted by %"PRIuLONGEST" bytes\n", (ulongest_t)size); + n_contract++; +} + +/* Messages for testbench debugging */ +static void print_messages(void) +{ + mps_message_type_t type; + + while (mps_message_queue_type(&type, arena)) { + mps_message_t message; + + cdie(mps_message_get(&message, arena, type), + "get"); + + switch(type) { + case mps_message_type_gc_start(): + printf("GC start at %"PRIuLONGEST": %s\n", + (ulongest_t)mps_message_clock(arena, message), + mps_message_gc_start_why(arena, message)); + break; + + case mps_message_type_gc(): + printf("GC end at %"PRIuLONGEST" " + "condemned %"PRIuLONGEST" " + "not condemned %"PRIuLONGEST" " + "live %"PRIuLONGEST"\n", + (ulongest_t)mps_message_clock(arena, message), + (ulongest_t)mps_message_gc_condemned_size(arena, message), + (ulongest_t)mps_message_gc_not_condemned_size(arena, message), + (ulongest_t)mps_message_gc_live_size(arena, message)); + break; + + default: + cdie(0, "message type"); + break; + } + + mps_message_discard(arena, message); + } +} + +/* Disabling inlining is necessary (but perhaps not sufficient) if using stack roots. + See comment below with link to GitHub issue*/ +ATTRIBUTE_NOINLINE +static void test_main(void *cold_stack_end) +{ + mps_fmt_t obj_fmt; + mps_thr_t thread; + mps_root_t stack_root, testobj_root; + size_t arena_size, obj_size; + int i; + /* In the original version of extcon this was a stack root, but we + observed unreliable failures to do with registering the cold end + of the stack. See GitHub issue #210 + . For now, we + declare this as a separate root. */ + static mps_word_t testobj[N_TESTOBJ]; + + /* The testobj array must be below (on all current Posix platforms) + the cold end of the stack in order for the MPS to scan it. We + have observed a Heisenbug where GCC will inline test_main into + main and lose this condition if the expression below is removed. + This is a problem we are analysing in GitHub issue #210 + . For now, we + disable this Insist to allow the test to run with a static + testobj array. */ +#if 0 + Insist((void *)&testobj[N_TESTOBJ] <= cold_stack_end); + if ((void *)&testobj[N_TESTOBJ] > cold_stack_end) + printf("Cold stack marker invalid!\n"); + else + printf("Cold stack marker probably valid.\n"); +#endif + + /* Make initial arena size slightly bigger than the test object size to force an extension as early as possible */ + /* See definition of make_dylan_vector() in fmtdytst.c for calculation of vector size */ + obj_size = ALIGN_WORD((N_SLOT_TESTOBJ + 2) * sizeof(mps_word_t)); + arena_size = ALIGN_WORD(obj_size + SIZEDIFF); + + /* Create arena and register callbacks */ + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_ARENA_SIZE, arena_size); + MPS_ARGS_ADD(args, MPS_KEY_ARENA_EXTENDED, (mps_fun_t)&arena_extended_cb); + MPS_ARGS_ADD(args, MPS_KEY_ARENA_CONTRACTED, (mps_fun_t)&arena_contracted_cb); + die(mps_arena_create_k(&arena, mps_arena_class_vm(), args), "mps_arena_create_k"); + } MPS_ARGS_END(args); + + printf("Initial reservation %"PRIuLONGEST".\n", (ulongest_t)mps_arena_reserved(arena)); + + die(dylan_fmt(&obj_fmt, arena), "dylan_fmt()"); + + /* Create new pool */ + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_FORMAT, obj_fmt); + die(mps_pool_create_k(&obj_pool, arena, mps_class_amcz(), args), + "mps_pool_create_k"); + } MPS_ARGS_END(args); + + /* Register thread */ + die(mps_thread_reg(&thread, arena), "Thread reg"); + + /* Register stack roots */ + /* Since this testbench is currently not using a stack root, #IF 0 this out */ + testlib_unused(cold_stack_end); + testlib_unused(stack_root); +#if 0 + die(mps_root_create_thread(&stack_root, arena, thread, cold_stack_end), "Create Stack root"); +#endif + + /* Register ambiguous array of object roots. */ + die(mps_root_create_area(&testobj_root, arena, + mps_rank_ambig(), (mps_rm_t)0, + &testobj[0], &testobj[N_TESTOBJ], + mps_scan_area, NULL), + "root_create_area(testobj)"); + + /* Create allocation point */ + die(mps_ap_create_k(&obj_ap, obj_pool, mps_args_none), "Create Allocation point"); + + mps_message_type_enable(arena, mps_message_type_gc_start()); + mps_message_type_enable(arena, mps_message_type_gc()); + + /* Allocate objects and force arena extension */ + for (i = 0; i < N_TESTOBJ; i++) { + + die(make_dylan_vector(&testobj[i], obj_ap, N_SLOT_TESTOBJ), "make_dylan_vector"); + + printf("Object %d committed. " + "Arena reserved: %"PRIuLONGEST".\n", + i, + (ulongest_t)mps_arena_reserved(arena)); + + print_messages(); + } + + /* overwrite all the references to the objects*/ + for (i = 0; i < N_TESTOBJ; i++) { + + /* bonus test of mps_addr_object */ +#if 0 /* Comment this out until mps_addr_object becomes available. */ + mps_addr_t out; + Insist(N_TESTOBJ <= N_INT_TESTOBJ); + + /* use "i" to as a convenient way to generate different interior pointers + To guarantee the i index will give us an interior pointer the number of test + objects must be <= the number of integers in each object */ + Insist(N_TESTOBJ <= N_INT_TESTOBJ); + die(mps_addr_object(&out, arena, &(testobj[i])->int_array[i]), "Address object"); + + Insist(out == testobj[i]); + + /* end piggy back testbench */ +#endif + + /* now overwrite the ref */ + testobj[i] = (mps_word_t)NULL; + + print_messages(); + } + + /* Collect */ + mps_arena_collect(arena); + + print_messages(); + + /* Clean up */ + mps_root_destroy(testobj_root); + /* mps_root_destroy(stack_root);*/ /*commented out while not using stack root */ + mps_thread_dereg(thread); + mps_ap_destroy(obj_ap); + mps_pool_destroy(obj_pool); + mps_fmt_destroy(obj_fmt); + mps_arena_destroy(arena); + + /* Destroying the arena should cause contraction callbacks on all + remaining chunks, even if they had contents. */ + Insist(n_extend == n_contract); + + printf("Arena extended %d times\n", n_extend); + printf("Arena contracted %d times\n", n_contract); + + /* comment out some diagnostics for investigating issue #210 mentioned above */ +#if 0 + printf("&testobj[N_TESTOBJ] = %p\n", (void *)&testobj[N_TESTOBJ]); + printf("cold_stack_end = %p\n", cold_stack_end); +#endif + if (n_extend == 0) + printf("No callbacks received upon arena extended!\n"); + if (n_contract == 0) + printf("No callbacks received upon arena contracted!\n"); + + if (n_contract == 0 || n_extend == 0) + exit(EXIT_FAILURE); +} + +int main(int argc, char* argv[]) +{ + void *stack_marker = &stack_marker; + + testlib_init(argc, argv); + + test_main(stack_marker); + + printf("%s: Conclusion: Failed to find any defects.\n", argv[0]); + + return 0; +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2022-2023 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/mps/code/mpm.h b/mps/code/mpm.h index 9979d20180d..e072a7a1ea7 100644 --- a/mps/code/mpm.h +++ b/mps/code/mpm.h @@ -236,6 +236,7 @@ extern Res PoolTraceBegin(Pool pool, Trace trace); extern void PoolFreeWalk(Pool pool, FreeBlockVisitor f, void *p); extern Size PoolTotalSize(Pool pool); extern Size PoolFreeSize(Pool pool); +extern Res PoolAddrObject(Addr *pReturn, Pool pool, Addr addr); extern Res PoolAbsInit(Pool pool, Arena arena, PoolClass klass, ArgList arg); extern void PoolAbsFinish(Inst inst); @@ -267,6 +268,7 @@ extern void PoolTrivFreeWalk(Pool pool, FreeBlockVisitor f, void *p); extern PoolDebugMixin PoolNoDebugMixin(Pool pool); extern BufferClass PoolNoBufferClass(void); extern Size PoolNoSize(Pool pool); +extern Res PoolTrivAddrObject(Addr *pReturn, Pool pool, Addr addr); /* See .critical.macros. */ #define PoolFreeMacro(pool, old, size) Method(Pool, pool, free)(pool, old, size) @@ -536,6 +538,7 @@ extern Res ArenaStartCollect(Globals globals, TraceStartWhy why); extern Res ArenaCollect(Globals globals, TraceStartWhy 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); extern void ArenaChunkRemoved(Arena arena, Chunk chunk); extern void ArenaAccumulateTime(Arena arena, Clock start, Clock now); diff --git a/mps/code/mpmst.h b/mps/code/mpmst.h index e03033ae999..f5ba00b8d64 100644 --- a/mps/code/mpmst.h +++ b/mps/code/mpmst.h @@ -60,6 +60,7 @@ typedef struct mps_pool_class_s { PoolRampEndMethod rampEnd; /* end a ramp pattern */ PoolFramePushMethod framePush; /* push an allocation frame */ PoolFramePopMethod framePop; /* pop an allocation frame */ + PoolAddrObjectMethod addrObject; /* return object's base pointer */ PoolFreeWalkMethod freewalk; /* walk over free blocks */ PoolBufferClassMethod bufferClass; /* default BufferClass of pool */ PoolDebugMixinMethod debugMixin; /* find the debug mixin, if any */ diff --git a/mps/code/mpmtypes.h b/mps/code/mpmtypes.h index d913c445f76..c219e908e06 100644 --- a/mps/code/mpmtypes.h +++ b/mps/code/mpmtypes.h @@ -214,6 +214,7 @@ typedef Res (*PoolFramePushMethod)(AllocFrame *frameReturn, Pool pool, Buffer buf); typedef Res (*PoolFramePopMethod)(Pool pool, Buffer buf, AllocFrame frame); +typedef Res (*PoolAddrObjectMethod)(Addr *pReturn, Pool pool, Addr addr); typedef void (*PoolFreeWalkMethod)(Pool pool, FreeBlockVisitor f, void *p); typedef BufferClass (*PoolBufferClassMethod)(void); typedef PoolDebugMixin (*PoolDebugMixinMethod)(Pool pool); diff --git a/mps/code/mps.h b/mps/code/mps.h index ae8b4a13359..f48d2635e81 100644 --- a/mps/code/mps.h +++ b/mps/code/mps.h @@ -120,6 +120,13 @@ typedef mps_addr_t (*mps_fmt_isfwd_t)(mps_addr_t); typedef void (*mps_fmt_pad_t)(mps_addr_t, size_t); typedef mps_addr_t (*mps_fmt_class_t)(mps_addr_t); +/* Callbacks indicating that the arena has extended or contracted. + * These are used to register chunks with RtlInstallFunctionTableCallback + * + * so that the client can unwind the stack through functions in the arena. + */ +typedef void (*mps_arena_extended_t)(mps_arena_t, void *, size_t); +typedef void (*mps_arena_contracted_t)(mps_arena_t, void *, size_t); /* Keyword argument lists */ @@ -171,6 +178,12 @@ extern const struct mps_key_s _mps_key_ARENA_SIZE; extern const struct mps_key_s _mps_key_ARENA_ZONED; #define MPS_KEY_ARENA_ZONED (&_mps_key_ARENA_ZONED) #define MPS_KEY_ARENA_ZONED_FIELD b +extern const struct mps_key_s _mps_key_arena_extended; +#define MPS_KEY_ARENA_EXTENDED (&_mps_key_arena_extended) +#define MPS_KEY_ARENA_EXTENDED_FIELD fun +extern const struct mps_key_s _mps_key_arena_contracted; +#define MPS_KEY_ARENA_CONTRACTED (&_mps_key_arena_contracted) +#define MPS_KEY_ARENA_CONTRACTED_FIELD fun extern const struct mps_key_s _mps_key_FORMAT; #define MPS_KEY_FORMAT (&_mps_key_FORMAT) #define MPS_KEY_FORMAT_FIELD format @@ -835,6 +848,8 @@ extern mps_res_t _mps_fix2(mps_ss_t, mps_addr_t *); (ss)->_ufs = _mps_ufs; \ MPS_END +/* Misc interface */ +extern mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr); #endif /* mps_h */ diff --git a/mps/code/mps.xcodeproj/project.pbxproj b/mps/code/mps.xcodeproj/project.pbxproj index 42f60ccb58a..909dbfbd601 100644 --- a/mps/code/mps.xcodeproj/project.pbxproj +++ b/mps/code/mps.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ buildPhases = ( ); dependencies = ( + 319F7A192A30D2F000E5B418 /* PBXTargetDependency */, 3104AFF6156D37BC000A585A /* PBXTargetDependency */, 3114A644156E94FB001E0AA3 /* PBXTargetDependency */, 22FACEF1188809B5000FDBC1 /* PBXTargetDependency */, @@ -89,8 +90,6 @@ 3114A677156E961C001E0AA3 /* PBXTargetDependency */, 3114A612156E943B001E0AA3 /* PBXTargetDependency */, 22B2BC3D18B643B300C33E63 /* PBXTargetDependency */, - 2291A5E6175CB207001D4920 /* PBXTargetDependency */, - 2291A5E8175CB20E001D4920 /* PBXTargetDependency */, 3114A5CC156E932C001E0AA3 /* PBXTargetDependency */, 3114A5EA156E93C4001E0AA3 /* PBXTargetDependency */, 22EA3F4820D2B23F0065F5B6 /* PBXTargetDependency */, @@ -158,16 +157,6 @@ 2291A5B5175CAB2F001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; 2291A5B7175CAB2F001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 2291A5BE175CAB4E001D4920 /* awlutth.c in Sources */ = {isa = PBXBuildFile; fileRef = 2291A5A9175CAA9B001D4920 /* awlutth.c */; }; - 2291A5C5175CAFCA001D4920 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; - 2291A5C6175CAFCA001D4920 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; - 2291A5C7175CAFCA001D4920 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; - 2291A5C8175CAFCA001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; - 2291A5CB175CAFCA001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; - 2291A5D8175CB05F001D4920 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; - 2291A5D9175CB05F001D4920 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; - 2291A5DA175CB05F001D4920 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; - 2291A5DB175CB05F001D4920 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; - 2291A5DD175CB05F001D4920 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 2291A5ED175CB5E2001D4920 /* landtest.c in Sources */ = {isa = PBXBuildFile; fileRef = 2291A5E9175CB4EC001D4920 /* landtest.c */; }; 22B2BC2E18B6434F00C33E63 /* mps.c in Sources */ = {isa = PBXBuildFile; fileRef = 31A47BA3156C1E130039B1C2 /* mps.c */; }; 22B2BC3718B6437C00C33E63 /* scheme-advanced.c in Sources */ = {isa = PBXBuildFile; fileRef = 22B2BC2B18B6434000C33E63 /* scheme-advanced.c */; }; @@ -301,6 +290,12 @@ 3124CAFC156BE82900753214 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 3150AE53156ABA2500A6E22A /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; 318DA8D31892B27E0089718C /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; + 319F7A082A30D08500E5B418 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; + 319F7A0A2A30D08500E5B418 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; + 319F7A0B2A30D08500E5B418 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; + 319F7A0C2A30D08500E5B418 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; + 319F7A0E2A30D08500E5B418 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; + 319F7A172A30D11400E5B418 /* addrobj.c in Sources */ = {isa = PBXBuildFile; fileRef = 319F7A152A30D11400E5B418 /* addrobj.c */; }; 31A47BA4156C1E130039B1C2 /* mps.c in Sources */ = {isa = PBXBuildFile; fileRef = 31A47BA3156C1E130039B1C2 /* mps.c */; }; 31D60007156D3C6200337B26 /* segsmss.c in Sources */ = {isa = PBXBuildFile; fileRef = 31D60006156D3C5F00337B26 /* segsmss.c */; }; 31D60008156D3C7400337B26 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; @@ -468,20 +463,6 @@ remoteGlobalIDString = 2291A5AC175CAB2F001D4920; remoteInfo = awlutth; }; - 2291A5C3175CAFCA001D4920 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 31EEABFA156AAF9D00714D05; - remoteInfo = mps; - }; - 2291A5D5175CB05F001D4920 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 31EEABFA156AAF9D00714D05; - remoteInfo = mps; - }; 229E228719EAB10D00E21417 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; @@ -923,6 +904,20 @@ remoteGlobalIDString = 31108A3A1C6B90E900E728EA; remoteInfo = tagtest; }; + 319F7A062A30D08500E5B418 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31EEABFA156AAF9D00714D05; + remoteInfo = mps; + }; + 319F7A182A30D2F000E5B418 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 319F7A042A30D08500E5B418; + remoteInfo = addrobj; + }; 31A47BA9156C210D0039B1C2 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; @@ -1092,24 +1087,6 @@ ); runOnlyForDeploymentPostprocessing = 1; }; - 2291A5CC175CAFCA001D4920 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; - 2291A5DE175CB05F001D4920 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; 22B2BC3118B6434F00C33E63 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1407,6 +1384,15 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + 319F7A0F2A30D08500E5B418 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 31D6000B156D3CB200337B26 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1729,6 +1715,8 @@ 31942AA91C8EC446001AAF32 /* sp.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = sp.txt; path = ../design/sp.txt; sourceTree = ""; }; 31942AAB1C8EC446001AAF32 /* stack-scan.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = "stack-scan.txt"; path = "../design/stack-scan.txt"; sourceTree = ""; }; 31942AB01C8EC446001AAF32 /* testthr.txt */ = {isa = PBXFileReference; lastKnownFileType = text; name = testthr.txt; path = ../design/testthr.txt; sourceTree = ""; }; + 319F7A142A30D08500E5B418 /* addrobj */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = addrobj; sourceTree = BUILT_PRODUCTS_DIR; }; + 319F7A152A30D11400E5B418 /* addrobj.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = addrobj.c; sourceTree = ""; }; 31A47BA3156C1E130039B1C2 /* mps.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mps.c; sourceTree = ""; }; 31C83ADD1786281C0031A0DB /* protxc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = protxc.h; sourceTree = ""; }; 31CD33BB173A9F1500524741 /* mpscams.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = mpscams.h; sourceTree = ""; }; @@ -1856,22 +1844,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 2291A5CA175CAFCA001D4920 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2291A5CB175CAFCA001D4920 /* libmps.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 2291A5DC175CB05F001D4920 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 2291A5DD175CB05F001D4920 /* libmps.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 22B2BC3018B6434F00C33E63 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2134,6 +2106,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 319F7A0D2A30D08500E5B418 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 319F7A0E2A30D08500E5B418 /* libmps.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 31D6000A156D3CB200337B26 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2340,6 +2320,7 @@ 3124CAB3156BE1B700753214 /* Tests */ = { isa = PBXGroup; children = ( + 319F7A152A30D11400E5B418 /* addrobj.c */, 3114A63D156E94EA001E0AA3 /* abqtest.c */, 22FACED1188807FF000FDBC1 /* airtest.c */, 3124CAF5156BE81100753214 /* amcss.c */, @@ -2485,6 +2466,7 @@ 223E796519EAB00B00DC26A6 /* sncss */, 22EA3F4520D2B0D90065F5B6 /* forktest */, 2265D71D20E53F9C003019E8 /* mpseventpy */, + 319F7A142A30D08500E5B418 /* addrobj */, ); name = Products; sourceTree = ""; @@ -3365,6 +3347,24 @@ productReference = 318DA8CD1892B0F30089718C /* djbench */; productType = "com.apple.product-type.tool"; }; + 319F7A042A30D08500E5B418 /* addrobj */ = { + isa = PBXNativeTarget; + buildConfigurationList = 319F7A102A30D08500E5B418 /* Build configuration list for PBXNativeTarget "addrobj" */; + buildPhases = ( + 319F7A072A30D08500E5B418 /* Sources */, + 319F7A0D2A30D08500E5B418 /* Frameworks */, + 319F7A0F2A30D08500E5B418 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 319F7A052A30D08500E5B418 /* PBXTargetDependency */, + ); + name = addrobj; + productName = finalcv; + productReference = 319F7A142A30D08500E5B418 /* addrobj */; + productType = "com.apple.product-type.tool"; + }; 31D6000C156D3CB200337B26 /* awluthe */ = { isa = PBXNativeTarget; buildConfigurationList = 31D60014156D3CB200337B26 /* Build configuration list for PBXNativeTarget "awluthe" */; @@ -3555,6 +3555,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = 31EEABD8156AAE9E00714D05; @@ -3617,6 +3618,7 @@ 31FCAE0917692403008C034C /* scheme */, 22B2BC2C18B6434F00C33E63 /* scheme-advanced */, 31108A3A1C6B90E900E728EA /* tagtest */, + 319F7A042A30D08500E5B418 /* addrobj */, ); }; /* End PBXProject section */ @@ -4090,6 +4092,18 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 319F7A072A30D08500E5B418 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 319F7A172A30D11400E5B418 /* addrobj.c in Sources */, + 319F7A082A30D08500E5B418 /* testlib.c in Sources */, + 319F7A0A2A30D08500E5B418 /* fmtdy.c in Sources */, + 319F7A0B2A30D08500E5B418 /* fmtdytst.c in Sources */, + 319F7A0C2A30D08500E5B418 /* fmtno.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 31D60009156D3CB200337B26 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4287,16 +4301,6 @@ target = 2291A5AC175CAB2F001D4920 /* awlutth */; targetProxy = 2291A5BF175CAB5F001D4920 /* PBXContainerItemProxy */; }; - 2291A5C2175CAFCA001D4920 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 31EEABFA156AAF9D00714D05 /* mps */; - targetProxy = 2291A5C3175CAFCA001D4920 /* PBXContainerItemProxy */; - }; - 2291A5D4175CB05F001D4920 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 31EEABFA156AAF9D00714D05 /* mps */; - targetProxy = 2291A5D5175CB05F001D4920 /* PBXContainerItemProxy */; - }; 229E228819EAB10D00E21417 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 223E795819EAB00B00DC26A6 /* sncss */; @@ -4612,6 +4616,16 @@ target = 31108A3A1C6B90E900E728EA /* tagtest */; targetProxy = 314CB6EA1C6D272A0073CA42 /* PBXContainerItemProxy */; }; + 319F7A052A30D08500E5B418 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31EEABFA156AAF9D00714D05 /* mps */; + targetProxy = 319F7A062A30D08500E5B418 /* PBXContainerItemProxy */; + }; + 319F7A192A30D2F000E5B418 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 319F7A042A30D08500E5B418 /* addrobj */; + targetProxy = 319F7A182A30D2F000E5B418 /* PBXContainerItemProxy */; + }; 31A47BAA156C210D0039B1C2 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 31EEABFA156AAF9D00714D05 /* mps */; @@ -4891,34 +4905,6 @@ }; name = Release; }; - 2291A5CE175CAFCA001D4920 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 2291A5CF175CAFCA001D4920 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 2291A5E0175CB05F001D4920 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 2291A5E1175CB05F001D4920 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; 22B2BC3318B6434F00C33E63 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5633,20 +5619,6 @@ }; name = RASH; }; - 318DA8E51892C0D00089718C /* RASH */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = RASH; - }; - 318DA8E61892C0D00089718C /* RASH */ = { - isa = XCBuildConfiguration; - buildSettings = { - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = RASH; - }; 318DA8E71892C0D00089718C /* RASH */ = { isa = XCBuildConfiguration; buildSettings = { @@ -5816,6 +5788,27 @@ }; name = RASH; }; + 319F7A112A30D08500E5B418 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 319F7A122A30D08500E5B418 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 319F7A132A30D08500E5B418 /* RASH */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = RASH; + }; 31D60015156D3CB200337B26 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6553,6 +6546,16 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 319F7A102A30D08500E5B418 /* Build configuration list for PBXNativeTarget "addrobj" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 319F7A112A30D08500E5B418 /* Debug */, + 319F7A122A30D08500E5B418 /* Release */, + 319F7A132A30D08500E5B418 /* RASH */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 31D60014156D3CB200337B26 /* Build configuration list for PBXNativeTarget "awluthe" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme new file mode 100644 index 00000000000..fc6990f251f --- /dev/null +++ b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/addrobj.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme new file mode 100644 index 00000000000..67ef98c4301 --- /dev/null +++ b/mps/code/mps.xcodeproj/xcshareddata/xcschemes/finaltest.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mps/code/mpsi.c b/mps/code/mpsi.c index 5f19333639e..a2fe1461590 100644 --- a/mps/code/mpsi.c +++ b/mps/code/mpsi.c @@ -448,6 +448,37 @@ mps_bool_t mps_addr_pool(mps_pool_t *mps_pool_o, } +/* mps_addr_object -- find base pointer of a managed object */ + +mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr) +{ + Res res; + Addr p; + + AVER(p_o != NULL); + + /* This function cannot be called while walking the heap, unlike + * mps_arena_has_addr(). This is because it is designed to be called + * with an active mutator, so takes the arena lock. This is in order + * that it sees a consistent view of MPS structures and the heap, + * and can peek behind the barrier. + */ + ArenaEnter(arena); + AVERT(Arena, arena); + res = ArenaAddrObject(&p, arena, (Addr)addr); + ArenaLeave(arena); + /* We require the object to be ambiguously referenced (hence pinned) + * so that p doesn't become invalid before it is written to *p_o. + * (We can't simply put this write before the ArenaLeave(), because + * p_o could point to MPS-managed memory that is behind a barrier.) + */ + if (res == ResOK) + *p_o = (mps_addr_t)p; + + return res; +} + + /* mps_addr_fmt -- what format might this address have? * * .per-pool: There's no reason why all objects in a pool should have diff --git a/mps/code/pool.c b/mps/code/pool.c index ea6971025e5..fc04563175b 100644 --- a/mps/code/pool.c +++ b/mps/code/pool.c @@ -57,6 +57,7 @@ Bool PoolClassCheck(PoolClass klass) CHECKL(FUNCHECK(klass->debugMixin)); CHECKL(FUNCHECK(klass->totalSize)); CHECKL(FUNCHECK(klass->freeSize)); + CHECKL(FUNCHECK(klass->addrObject)); /* Check that pool classes overide sets of related methods. */ CHECKL((klass->init == PoolAbsInit) == @@ -303,6 +304,21 @@ Size PoolFreeSize(Pool pool) } +/* PoolAddrObject -- return base pointer from interior pointer + * + * Note: addr is not necessarily inside the pool, even though + * mps_addr_object dispatches via the tract table. This allows this + * function to be used more generally internally. The pool should + * check (it has to anyway). + */ + +Res PoolAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + AVER(pReturn != NULL); + AVERT(Pool, pool); + return Method(Pool, pool, addrObject)(pReturn, pool, addr); +} + /* PoolDescribe -- describe a pool */ Res PoolDescribe(Pool pool, mps_lib_FILE *stream, Count depth) diff --git a/mps/code/poolabs.c b/mps/code/poolabs.c index f7fe65a8e41..d6eae2dd046 100644 --- a/mps/code/poolabs.c +++ b/mps/code/poolabs.c @@ -173,6 +173,7 @@ DEFINE_CLASS(Pool, AbstractPool, klass) klass->debugMixin = PoolNoDebugMixin; klass->totalSize = PoolNoSize; klass->freeSize = PoolNoSize; + klass->addrObject = PoolTrivAddrObject; klass->sig = PoolClassSig; AVERT(PoolClass, klass); } @@ -476,6 +477,16 @@ Size PoolNoSize(Pool pool) } +Res PoolTrivAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + AVERT(Pool, pool); + AVER(pReturn != NULL); + UNUSED(addr); + + return ResUNIMPL; +} + + /* C. COPYRIGHT AND LICENSE * * Copyright (C) 2001-2020 Ravenbrook Limited . diff --git a/mps/code/poolamc.c b/mps/code/poolamc.c index bd228e82355..c526c8e56fc 100644 --- a/mps/code/poolamc.c +++ b/mps/code/poolamc.c @@ -1892,6 +1892,90 @@ static void amcWalkAll(Pool pool, FormattedObjectsVisitor f, void *p, size_t s) } } +/* AMCAddrObject -- return base pointer from interior pointer + * + * amcAddrObjectSearch implements the scan for an object containing + * the interior pointer by skipping using format methods. + * + * AMCAddrObject locates the segment containing the interior pointer + * and wraps amcAddrObjectSearch in the necessary shield operations to + * give it access. + */ + +static Res amcAddrObjectSearch(Addr *pReturn, + Pool pool, + Addr objBase, + Addr searchLimit, + Addr addr) +{ + Format format; + Size hdrSize; + + AVER(pReturn != NULL); + AVERT(Pool, pool); + AVER(objBase <= searchLimit); + + format = pool->format; + hdrSize = format->headerSize; + while (objBase < searchLimit) { + Addr objRef = AddrAdd(objBase, hdrSize); + Addr objLimit = AddrSub((*format->skip)(objRef), hdrSize); + AVER(objBase < objLimit); + + if (addr < objLimit) { + AVER(objBase <= addr); + AVER(addr < objLimit); + + /* Don't return base pointer if object is moved */ + if (NULL == (*format->isMoved)(objRef)) { + *pReturn = objRef; + return ResOK; + } + break; + } + objBase = objLimit; + } + return ResFAIL; +} + +static Res AMCAddrObject(Addr *pReturn, Pool pool, Addr addr) +{ + Res res; + Arena arena; + Addr base, limit; + Buffer buffer; + Seg seg; + + AVER(pReturn != NULL); + AVERT(Pool, pool); + + arena = PoolArena(pool); + if (!SegOfAddr(&seg, arena, addr) || SegPool(seg) != pool) + return ResFAIL; + + base = SegBase(seg); + if (SegBuffer(&buffer, seg)) + /* We use BufferGetInit here (and not BufferScanLimit) because we + * want to be able to find objects that have been allocated and + * committed since the last flip. These objects lie between the + * addresses returned by BufferScanLimit (which returns the value + * of init at the last flip) and BufferGetInit. + * + * Strictly speaking we only need a limit that is at least the + * maximum of the objects on the segments. This is because addr + * *must* point inside a live object and we stop skipping once we + * have found it. The init pointer serves this purpose. + */ + limit = BufferGetInit(buffer); + else + limit = SegLimit(seg); + + ShieldExpose(arena, seg); + res = amcAddrObjectSearch(pReturn, pool, base, limit, addr); + ShieldCover(arena, seg); + return res; +} + /* AMCTotalSize -- total memory allocated from the arena */ @@ -2007,6 +2091,7 @@ DEFINE_CLASS(Pool, AMCZPool, klass) klass->bufferClass = amcBufClassGet; klass->totalSize = AMCTotalSize; klass->freeSize = AMCFreeSize; + klass->addrObject = AMCAddrObject; AVERT(PoolClass, klass); } diff --git a/mps/code/vmix.c b/mps/code/vmix.c index 167ab614fa3..4bdd0caefad 100644 --- a/mps/code/vmix.c +++ b/mps/code/vmix.c @@ -50,7 +50,7 @@ #include /* sig_atomic_t */ #include /* see .feature.li in config.h */ #include /* mmap, munmap */ -#include /* getpagesize */ +#include /* sysconf, _SC_PAGESIZE */ SRCID(vmix, "$Id$"); @@ -59,10 +59,11 @@ SRCID(vmix, "$Id$"); Size PageSize(void) { - int pageSize; + long pageSize; - /* Find out the operating system page size */ - pageSize = getpagesize(); + /* Find out the operating system page size + (see design.mps.vm.impl.ix.page.size) */ + pageSize = sysconf(_SC_PAGESIZE); /* Check the page size will fit in a Size. */ AVER((unsigned long)pageSize <= (unsigned long)(Size)-1); diff --git a/mps/design/vm.txt b/mps/design/vm.txt index f4bc2e4588b..457959b83ce 100644 --- a/mps/design/vm.txt +++ b/mps/design/vm.txt @@ -269,7 +269,13 @@ Unix implementation _`.impl.ix`: In ``vmix.c``. -_`.impl.ix.page.size`: The page size is given by ``getpagesize()``. +_`.impl.ix.page.size`: The page size is given by +``sysconf(_SC_PAGESIZE)``. We avoid ``getpagesize()``, which is a +legacy function in Posix: + + Applications should use the sysconf() function instead. + + — `The Single UNIX ® Specification, Version 2 `__ _`.impl.ix.param`: Decodes no keyword arguments. diff --git a/mps/manual/source/release.rst b/mps/manual/source/release.rst index 117cad00dcc..c92aafb4b99 100644 --- a/mps/manual/source/release.rst +++ b/mps/manual/source/release.rst @@ -53,6 +53,18 @@ New features address-based hash tables using :term:`location dependency`. See :ref:`pool-amc-hash-arrays`. +#. The new function :c:func:`mps_addr_object` allows clients to + discover the base pointer of an object from a pointer to anywhere + inside the object. This is intended to support stack tracing and + debugging for client programs that allocate their code on the + heap. + +#. A :term:`virtual memory arena` can now be configured to call + functions when it acquires a new chunk of :term:`address space`, + and when it returns a chunk of address space to the operation + system. This is intended to support dynamic function tables in + Windows. See :ref:`topic-arena-extension`. + Interface changes ................. diff --git a/mps/manual/source/topic/arena.rst b/mps/manual/source/topic/arena.rst index 520c8179d1e..61302d249a5 100644 --- a/mps/manual/source/topic/arena.rst +++ b/mps/manual/source/topic/arena.rst @@ -139,7 +139,7 @@ Client arenas * :c:macro:`MPS_KEY_ARENA_SIZE` (type :c:type:`size_t`) is its size. - It also accepts three optional keyword arguments: + It also accepts five optional keyword arguments: * :c:macro:`MPS_KEY_COMMIT_LIMIT` (type :c:type:`size_t`) is the maximum amount of memory, in :term:`bytes (1)`, that the MPS @@ -159,6 +159,18 @@ Client arenas may pause the :term:`client program` for. See :c:func:`mps_arena_pause_time_set` for details. + * :c:macro:`MPS_KEY_ARENA_EXTENDED` (type :c:type:`mps_fun_t`) is + a function that will be called immediately after the arena is + *extended*: that is, just after it acquires a new chunk of address + space from the operating system. See :ref:`topic-arena-extension` + for details. + + * :c:macro:`MPS_KEY_ARENA_CONTRACTED` (type :c:type:`mps_fun_t`) + is a function that will be called immediately before the arena is + *contracted*: that is, just before it finishes with a chunk of + address space and returns it to the operating system. See + :ref:`topic-arena-extension` for details. + For example:: MPS_ARGS_BEGIN(args) { @@ -983,8 +995,8 @@ Arena introspection and debugging 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 + .. |RtlInstallFunctionTableCallback| replace:: :c:func:`RtlInstallFunctionTableCallback` + .. _RtlInstallFunctionTableCallback: https://docs.microsoft.com/en-gb/windows/win32/api/winnt/nf-winnt-rtlinstallfunctiontablecallback If this happens, in order to allow the debugger to finish decoding the call stack, the only remedy is to put the arena @@ -1040,3 +1052,138 @@ Arena introspection and debugging :c:func:`mps_addr_pool`, and to find out which :term:`object format` describes the object at the address, use :c:func:`mps_addr_fmt`. + + +.. c:function:: mps_res_t mps_addr_object(mps_addr_t *p_o, mps_arena_t arena, mps_addr_t addr) + + Find the :term:`base pointer` of an :term:`object` if provided with an + :term:`interior pointer` to that object, or the object's base pointer, + provided the object exists in a pool that supports this feature. + + ``p_o`` points to a location that will hold the object's base pointer. + + ``arena`` is an arena. + + ``addr`` is an address that might be an interior or base pointer. + + Returns MPS_RES_OK if a base pointer to an object into which ``addr`` + points was successfully returned. + + Returns MPS_RES_FAIL if ``addr`` points to memory not managed by the + ``arena`` or if ``addr`` points to the interior of an object which has + been moved by a :term:`moving memory manager`. + + Returns MPS_RES_UNIMPL if ``addr`` is found to be managed by a :term:`pool` + which does not currently implement this feature. + + :c:func:`mps_addr_object` allows client programs that allocate + code on the heap to implement debugging and stack tracing, in that it provides + a way to unwind a client program's stack by finding the block of code to which the + program counter or function return addresses currently point. It can be called + multiple times as needed to build a complete trace of the client program's stack. + + This function does not support debugging in situations where the arena + itself has encountered a runtime error. For cases where the MPS encounters + runtime errors, see :c:func:`mps_arena_postmortem`. + + .. 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, i.e. as part of the regular operation of code in + production, since it is not optimized for performance. If you find yourself + wanting to use this function other than in the use case described, there may + be a better way to meet your requirements: please + :ref:`contact us `. + + If you would like this function to work in a pool in which it's currently + unimplemented, please :ref:`contact us `. + + +.. index:: + single: arena extension callbacks; introduction + single: extension callbacks; introduction + single: arena contraction callbacks; introduction + single: contraction callbacks; introduction + +.. _topic-arena-extension: + +Arena extension callbacks +------------------------- + +There are situations in which the :term:`client program` needs to be +informed about the chunks of address space that an :term:`arena` is +managing. To support this, the MPS allows the client program to +specify two callback functions when creating a :term:`virtual memory +arena`: one function is called when the arena is *extended* (that is, +when it acquires a new chunk of address space from the operating +system), and the other when the arena is *contracted* (that is, when +it returns a chunk of address space to the operating system). + +The use case that this feature is designed to support is debugging of +dynamically generated code in 64-bit Windows. Microsoft's +documentation for |RtlInstallFunctionTableCallback|_ says: + + Function tables are used on 64-bit Windows to determine how to + unwind or walk the stack. These tables are usually generated by + the compiler and stored as part of the image. However, + applications must provide the function table for dynamically + generated code. + +An application may install a dynamic function table by calling +|RtlInstallFunctionTableCallback|_, passing the region of memory in +which the dynamically generated functions can be found, and may later +delete the table by calling |RtlDeleteFunctionTable|_. + +.. |RtlDeleteFunctionTable| replace:: :c:func:`RtlDeleteFunctionTable` +.. _RtlDeleteFunctionTable: https://docs.microsoft.com/en-gb/windows/win32/api/winnt/nf-winnt-rtldeletefunctiontable + +So if the client program is storing dynamically generated functions in +MPS-managed memory, then it could define callback functions that +install and delete the function table callback for the dynamically +generated code, like this:: + + void arena_extended(mps_arena_t arena, void *base, size_t size) + { + RtlInstallFunctionTableCallback(...); + } + + void arena_contracted(mps_arena_t arena, void *base, size_t size) + { + RtlDeleteFunctionTable(...); + } + +and then pass these two functions using :term:`keyword arguments` to +:c:func:`mps_arena_create_k`:: + + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_ARENA_EXTENDED, (mps_fun_t)arena_extended); + MPS_ARGS_ADD(args, MPS_KEY_ARENA_CONTRACTED, (mps_fun_t)arena_contracted); + /* ... other keyword arguments ... */ + res = mps_arena_create_k(&arena, mps_arena_class_vm(), args); + } MPS_ARGS_END(args); + +The callback functions receive three arguments: ``arena`` (the arena +being extended or contracted), ``base`` (the base address of the chunk +of address space that has just been acquired from, or is about to be +returned to, the operating system), and ``size`` (the size of the +chunk, in bytes). They must not call any function in the MPS, and must +not access any memory managed by the MPS. + +.. note:: + + The extenstion callback is also called immediately after the arena + is created, in other words, the creation of the arena is treated as + a special example of an extension of the arena. + + The contraction callback is called on all remaining chunks when + the arena is destroyed. There will be at least one callback. + + Every contraction of the arena will match one-to-one with the arena + extensions that have already taken place. After creation, any + contractions performed by the arena will be the same size as the + extensions that have already taken place. Contractions never occur as + amalgamations nor as fractions of previous arena extensions. + + Arena extension callbacks are only supported by :term:`virtual + memory arenas`. diff --git a/mps/tool/testcases.txt b/mps/tool/testcases.txt index b19de92d12f..56787740ae2 100644 --- a/mps/tool/testcases.txt +++ b/mps/tool/testcases.txt @@ -2,6 +2,7 @@ Test case Flags Notes ============= ================ ========================================== abqtest +addrobj airtest amcss =P amcsshe =P @@ -16,6 +17,7 @@ awlutth =T btcv bttest =N interactive djbench =N benchmark +extcon =W TODO: Enable when we can update Xcode project. See GitHub issue #217 . finalcv =P finaltest =P forktest =X