diff --git a/code/comm.gmk b/code/comm.gmk index a2cd68baab..daaf0e6cfb 100644 --- a/code/comm.gmk +++ b/code/comm.gmk @@ -191,6 +191,7 @@ MPMCOMMON = \ meter.c \ mpm.c \ mpsi.c \ + mpsitr.c \ nailboard.c \ policy.c \ pool.c \ @@ -214,6 +215,7 @@ MPMCOMMON = \ trace.c \ traceanc.c \ tract.c \ + trans.c \ tree.c \ version.c \ vm.c \ @@ -293,7 +295,8 @@ TEST_TARGETS=\ teletest \ walkt0 \ zcoll \ - zmess + zmess \ + ztfm # This target records programs that we were once able to build but # can't at the moment: @@ -568,6 +571,9 @@ $(PFM)/$(VARIETY)/zcoll: $(PFM)/$(VARIETY)/zcoll.o \ $(PFM)/$(VARIETY)/zmess: $(PFM)/$(VARIETY)/zmess.o \ $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a +$(PFM)/$(VARIETY)/ztfm: $(PFM)/$(VARIETY)/ztfm.o \ + $(FMTDYTSTOBJ) $(TESTLIBOBJ) $(PFM)/$(VARIETY)/mps.a + $(PFM)/$(VARIETY)/mpseventcnv: $(PFM)/$(VARIETY)/eventcnv.o \ $(PFM)/$(VARIETY)/mps.a diff --git a/code/commpost.nmk b/code/commpost.nmk index 470db2519b..c669f48996 100644 --- a/code/commpost.nmk +++ b/code/commpost.nmk @@ -219,9 +219,6 @@ $(PFM)\$(VARIETY)\btcv.exe: $(PFM)\$(VARIETY)\btcv.obj \ $(PFM)\$(VARIETY)\bttest.exe: $(PFM)\$(VARIETY)\bttest.obj \ $(PFM)\$(VARIETY)\mps.lib $(TESTLIBOBJ) -$(PFM)\$(VARIETY)\cvmicv.exe: $(PFM)\$(VARIETY)\cvmicv.obj \ - $(PFM)\$(VARIETY)\mps.lib $(FMTTESTOBJ) $(TESTLIBOBJ) - $(PFM)\$(VARIETY)\djbench.exe: $(PFM)\$(VARIETY)\djbench.obj \ $(TESTLIBOBJ) $(TESTTHROBJ) diff --git a/code/commpre.nmk b/code/commpre.nmk index 236e08ad2e..61e076b37c 100644 --- a/code/commpre.nmk +++ b/code/commpre.nmk @@ -98,7 +98,8 @@ TEST_TARGETS=\ teletest.exe \ walkt0.exe \ zcoll.exe \ - zmess.exe + zmess.exe \ + ztfm.exe # Stand-alone programs go in EXTRA_TARGETS if they should always be # built, or in OPTIONAL_TARGETS if they should only be built if @@ -145,6 +146,7 @@ MPMCOMMON=\ [meter] \ [mpm] \ [mpsi] \ + [mpsitr] \ [nailboard] \ [policy] \ [pool] \ @@ -169,6 +171,7 @@ MPMCOMMON=\ [trace] \ [traceanc] \ [tract] \ + [trans] \ [tree] \ [version] \ [vm] \ diff --git a/code/mps.c b/code/mps.c index 41c8d076af..378965605a 100644 --- a/code/mps.c +++ b/code/mps.c @@ -80,6 +80,8 @@ #include "failover.c" #include "vm.c" #include "policy.c" +#include "trans.c" +#include "mpsitr.c" /* Additional pool classes */ diff --git a/code/mps.h b/code/mps.h index 4c56265c2e..0793224924 100644 --- a/code/mps.h +++ b/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, mps_addr_t, size_t); +typedef void (*mps_arena_contracted_t)(mps_arena_t, mps_addr_t, 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 @@ -247,6 +260,10 @@ extern const struct mps_key_s _mps_key_FMT_CLASS; #define MPS_KEY_FMT_CLASS (&_mps_key_FMT_CLASS) #define MPS_KEY_FMT_CLASS_FIELD fmt_class +extern const struct mps_key_s _mps_key_ap_hash_arrays; +#define MPS_KEY_AP_HASH_ARRAYS (&_mps_key_ap_hash_arrays) +#define MPS_KEY_AP_HASH_ARRAYS_FIELD b + /* Maximum length of a keyword argument list. */ #define MPS_ARGS_MAX 32 @@ -833,6 +850,15 @@ extern mps_res_t _mps_fix2(mps_ss_t, mps_addr_t *); MPS_END +/* Transforms interface. */ + +typedef struct mps_transform_s *mps_transform_t; +extern mps_res_t mps_transform_create(mps_transform_t *, mps_arena_t); +extern mps_res_t mps_transform_add_oldnew(mps_transform_t, mps_addr_t *, mps_addr_t *, size_t); +extern mps_res_t mps_transform_apply(mps_bool_t *, mps_transform_t); +extern void mps_transform_destroy(mps_transform_t); + + #endif /* mps_h */ diff --git a/code/mps.xcodeproj/project.pbxproj b/code/mps.xcodeproj/project.pbxproj index 42f60ccb58..be24cfea23 100644 --- a/code/mps.xcodeproj/project.pbxproj +++ b/code/mps.xcodeproj/project.pbxproj @@ -120,6 +120,7 @@ 3114A6B9156E9763001E0AA3 /* PBXTargetDependency */, 31D60063156D3F5C00337B26 /* PBXTargetDependency */, 31D60087156D3FE600337B26 /* PBXTargetDependency */, + 220FD3F419533E8F00967A35 /* PBXTargetDependency */, 3114A6D5156E9839001E0AA3 /* PBXTargetDependency */, 2265D72220E54020003019E8 /* PBXTargetDependency */, 2D07B9791636FCBD00DB751B /* PBXTargetDependency */, @@ -131,6 +132,13 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 220FD3DD195339C000967A35 /* fmtdy.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC6156BE48D00753214 /* fmtdy.c */; }; + 220FD3DE195339C000967A35 /* fmtdytst.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAC7156BE48D00753214 /* fmtdytst.c */; }; + 220FD3DF195339C000967A35 /* fmthe.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CAE4156BE6D500753214 /* fmthe.c */; }; + 220FD3E1195339C000967A35 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; + 220FD3E3195339C000967A35 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; + 220FD3F119533E7200967A35 /* fmtno.c in Sources */ = {isa = PBXBuildFile; fileRef = 3124CACC156BE4C200753214 /* fmtno.c */; }; + 220FD3F219533E7900967A35 /* ztfm.c in Sources */ = {isa = PBXBuildFile; fileRef = 220FD3F019533C3200967A35 /* ztfm.c */; }; 2215A9C9192A495F00E9E2CE /* pooln.c in Sources */ = {isa = PBXBuildFile; fileRef = 22FACEDE18880933000FDBC1 /* pooln.c */; }; 2231BB5118CA97D8002D6322 /* testlib.c in Sources */ = {isa = PBXBuildFile; fileRef = 31EEAC9E156AB73400714D05 /* testlib.c */; }; 2231BB5318CA97D8002D6322 /* libmps.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 31EEABFB156AAF9D00714D05 /* libmps.a */; }; @@ -349,6 +357,20 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 220FD3DA195339C000967A35 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 31EEABFA156AAF9D00714D05; + remoteInfo = mps; + }; + 220FD3F319533E8F00967A35 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 220FD3D8195339C000967A35; + remoteInfo = ztfm; + }; 2215A9AB192A47BB00E9E2CE /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 31EEABDA156AAE9E00714D05 /* Project object */; @@ -1038,6 +1060,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 220FD3E4195339C000967A35 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 2231BB5418CA97D8002D6322 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1493,6 +1524,12 @@ /* Begin PBXFileReference section */ 2213454C1DB0386600E14202 /* prmc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = prmc.h; sourceTree = ""; }; 2213454D1DB038D400E14202 /* prmcxc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = prmcxc.c; sourceTree = ""; }; + 220FD3E9195339C000967A35 /* ztfm */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ztfm; sourceTree = BUILT_PRODUCTS_DIR; }; + 220FD3EA195339E500967A35 /* mpsitr.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mpsitr.c; sourceTree = ""; }; + 220FD3EB195339F000967A35 /* trans.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = trans.c; sourceTree = ""; }; + 220FD3ED19533A8700967A35 /* mpscvm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpscvm.h; sourceTree = ""; }; + 220FD3EE19533A8700967A35 /* trans.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = trans.h; sourceTree = ""; }; + 220FD3F019533C3200967A35 /* ztfm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ztfm.c; sourceTree = ""; }; 2231BB5918CA97D8002D6322 /* locbwcss */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locbwcss; sourceTree = BUILT_PRODUCTS_DIR; }; 2231BB6718CA97DC002D6322 /* locusss */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locusss; sourceTree = BUILT_PRODUCTS_DIR; }; 2231BB6818CA9834002D6322 /* locbwcss.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = locbwcss.c; sourceTree = ""; }; @@ -1808,6 +1845,14 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 220FD3E2195339C000967A35 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 220FD3E3195339C000967A35 /* libmps.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2231BB5218CA97D8002D6322 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2394,6 +2439,7 @@ 3114A6BA156E9768001E0AA3 /* walkt0.c */, 31D6005E156D3F4A00337B26 /* zcoll.c */, 31D6007B156D3FCC00337B26 /* zmess.c */, + 220FD3F019533C3200967A35 /* ztfm.c */, ); name = Tests; sourceTree = ""; @@ -2485,6 +2531,7 @@ 223E796519EAB00B00DC26A6 /* sncss */, 22EA3F4520D2B0D90065F5B6 /* forktest */, 2265D71D20E53F9C003019E8 /* mpseventpy */, + 220FD3E9195339C000967A35 /* ztfm */, ); name = Products; sourceTree = ""; @@ -2539,7 +2586,9 @@ 311F2F6517398B3B00C15B6A /* mpsacl.h */, 311F2F6617398B3B00C15B6A /* mpsavm.h */, 22FACEDB188808D5000FDBC1 /* mpscmfs.h */, + 220FD3ED19533A8700967A35 /* mpscvm.h */, 31EEABF5156AAF7C00714D05 /* mpsi.c */, + 220FD3EA195339E500967A35 /* mpsitr.c */, 311F2F6717398B3B00C15B6A /* mpsio.h */, 311F2F6817398B3B00C15B6A /* mpslib.h */, 311F2F6917398B3B00C15B6A /* mpstd.h */, @@ -2583,6 +2632,8 @@ 31EEAC1F156AB2B200714D05 /* traceanc.c */, 31EEAC0D156AB27B00714D05 /* tract.c */, 311F2F7A17398B8E00C15B6A /* tract.h */, + 220FD3EB195339F000967A35 /* trans.c */, + 220FD3EE19533A8700967A35 /* trans.h */, 310F5D7118B6675F007EFCBC /* tree.c */, 310F5D7218B6675F007EFCBC /* tree.h */, 31EEAC44156AB32500714D05 /* version.c */, @@ -2667,6 +2718,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 220FD3D8195339C000967A35 /* ztfm */ = { + isa = PBXNativeTarget; + buildConfigurationList = 220FD3E5195339C000967A35 /* Build configuration list for PBXNativeTarget "ztfm" */; + buildPhases = ( + 220FD3DB195339C000967A35 /* Sources */, + 220FD3E2195339C000967A35 /* Frameworks */, + 220FD3E4195339C000967A35 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + 220FD3D9195339C000967A35 /* PBXTargetDependency */, + ); + name = ztfm; + productName = zmess; + productReference = 220FD3E9195339C000967A35 /* ztfm */; + productType = "com.apple.product-type.tool"; + }; 2231BB4C18CA97D8002D6322 /* locbwcss */ = { isa = PBXNativeTarget; buildConfigurationList = 2231BB5518CA97D8002D6322 /* Build configuration list for PBXNativeTarget "locbwcss" */; @@ -3610,6 +3679,7 @@ 3114A6AB156E9759001E0AA3 /* walkt0 */, 31D60053156D3F3500337B26 /* zcoll */, 31D60070156D3FBC00337B26 /* zmess */, + 220FD3D8195339C000967A35 /* ztfm */, 3114A6C5156E9815001E0AA3 /* mpseventcnv */, 2265D71120E53F9C003019E8 /* mpseventpy */, 2D07B9701636FC9900DB751B /* mpseventsql */, @@ -3695,6 +3765,19 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 220FD3DB195339C000967A35 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 220FD3F219533E7900967A35 /* ztfm.c in Sources */, + 220FD3DD195339C000967A35 /* fmtdy.c in Sources */, + 220FD3DE195339C000967A35 /* fmtdytst.c in Sources */, + 220FD3DF195339C000967A35 /* fmthe.c in Sources */, + 220FD3F119533E7200967A35 /* fmtno.c in Sources */, + 220FD3E1195339C000967A35 /* testlib.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 2231BB4F18CA97D8002D6322 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -4202,6 +4285,16 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 220FD3D9195339C000967A35 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 31EEABFA156AAF9D00714D05 /* mps */; + targetProxy = 220FD3DA195339C000967A35 /* PBXContainerItemProxy */; + }; + 220FD3F419533E8F00967A35 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 220FD3D8195339C000967A35 /* ztfm */; + targetProxy = 220FD3F319533E8F00967A35 /* PBXContainerItemProxy */; + }; 2215A9AA192A47BB00E9E2CE /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 3104AFF1156D37A0000A585A /* all */; @@ -4695,6 +4788,27 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 220FD3E6195339C000967A35 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 220FD3E7195339C000967A35 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + 220FD3E8195339C000967A35 /* RASH */ = { + isa = XCBuildConfiguration; + buildSettings = { + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = RASH; + }; 2215A9AE192A47BB00E9E2CE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6103,6 +6217,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 220FD3E5195339C000967A35 /* Build configuration list for PBXNativeTarget "ztfm" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 220FD3E6195339C000967A35 /* Debug */, + 220FD3E7195339C000967A35 /* Release */, + 220FD3E8195339C000967A35 /* RASH */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 2215A9AD192A47BB00E9E2CE /* Build configuration list for PBXAggregateTarget "testci" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/code/mpsitr.c b/code/mpsitr.c new file mode 100644 index 0000000000..522d809969 --- /dev/null +++ b/code/mpsitr.c @@ -0,0 +1,121 @@ +/* mpsitr.c: MEMORY POOL SYSTEM C INTERFACE LAYER TO TRANSFORMS + * + * $Id$ + * Copyright (c) 2011-2022 Ravenbrook Limited. See end of file for license. + * + * .purpose: This code bridges between the MPS interface to transforms in + * and the internal implementation of Transforms in . + */ + +#include "mpm.h" +#include "mps.h" +#include "trans.h" +#include "ss.h" + + +SRCID(mpsitr, "$Id$"); + + +mps_res_t mps_transform_create(mps_transform_t *mps_transform_o, + mps_arena_t arena) +{ + Transform transform = NULL; + Res res; + + AVER(mps_transform_o != NULL); + + ArenaEnter(arena); + res = TransformCreate(&transform, arena); + ArenaLeave(arena); + if (res != ResOK) + return res; + + *mps_transform_o = (mps_transform_t)transform; + return MPS_RES_OK; +} + + +mps_res_t mps_transform_add_oldnew(mps_transform_t transform, + mps_addr_t *mps_old_list, + mps_addr_t *mps_new_list, + size_t mps_count) +{ + Ref *old_list = (Ref *)mps_old_list; + Ref *new_list = (Ref *)mps_new_list; + Count count = mps_count; + Arena arena; + Res res; + + AVER(mps_old_list != NULL); + AVER(mps_new_list != NULL); + /* count: cannot check */ + + arena = TransformArena(transform); + + ArenaEnter(arena); + res = TransformAddOldNew(transform, old_list, new_list, count); + ArenaLeave(arena); + + return res; +} + + +mps_res_t mps_transform_apply(mps_bool_t *applied_o, + mps_transform_t transform) +{ + Arena arena; + Res res; + + AVER(applied_o != NULL); + + arena = TransformArena(transform); + ArenaEnter(arena); + STACK_CONTEXT_BEGIN(arena) { + res = TransformApply(applied_o, transform); + } STACK_CONTEXT_END(arena); + ArenaLeave(arena); + + return res; +} + + +void mps_transform_destroy(mps_transform_t transform) +{ + Arena arena; + + arena = TransformArena(transform); + + ArenaEnter(arena); + TransformDestroy(transform); + ArenaLeave(arena); +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2011-2022 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/code/protw3.c b/code/protw3.c index 6c57731ae4..956bc8293b 100644 --- a/code/protw3.c +++ b/code/protw3.c @@ -113,15 +113,16 @@ LONG WINAPI ProtSEHfilter(LPEXCEPTION_POINTERS info) /* ProtSetup -- set up the protection system */ +static void *vehHandle; + void ProtSetup(void) { - void *handler; /* See "AddVectoredExceptionHandler function (Windows)" */ /* ProtSetup is called only once per process, not once per arena, so this exception handler is only installed once. */ - handler = AddVectoredExceptionHandler(1uL, ProtSEHfilter); - AVER(handler != NULL); + vehHandle = AddVectoredExceptionHandler(1uL, ProtSEHfilter); + AVER(vehHandle != NULL); } diff --git a/code/testlib.h b/code/testlib.h index f140b0a9e1..9a414f2b5d 100644 --- a/code/testlib.h +++ b/code/testlib.h @@ -114,6 +114,7 @@ #if defined(MPS_OS_W3) && defined(MPS_ARCH_I6) #define PRIuLONGEST "llu" +#define PRIdLONGEST "lld" #define SCNuLONGEST "llu" #define SCNXLONGEST "llX" #define PRIXLONGEST "llX" @@ -122,6 +123,7 @@ typedef long long longest_t; #define MPS_WORD_CONST(n) (n##ull) #else #define PRIuLONGEST "lu" +#define PRIdLONGEST "ld" #define SCNuLONGEST "lu" #define SCNXLONGEST "lX" #define PRIXLONGEST "lX" diff --git a/code/trans.c b/code/trans.c new file mode 100644 index 0000000000..ef006a4f02 --- /dev/null +++ b/code/trans.c @@ -0,0 +1,377 @@ +/* trans.c: TRANSFORMS IMPLEMENTATION + * + * $Id$ + * Copyright 2011-2022 Ravenbrook Limited. See end of file for license. + * + * A transform is a special kind of garbage collection that replaces references + * to a set of objects. The transform is piggybacked onto a garbage + * collection by overriding the fix method for a trace. The mapping used to + * replace the references is built up in a hash table by the client. + * + * + * Rationale + * + * This design was arrived at after some pain. The MPS isn't really designed + * for this kind of thing, and the pools generally assume that they're doing + * a garbage collection when they're asked to condemn, scan, fix, and reclaim + * stuff. This makes it very hard to apply the transform without also doing + * a garbage collection. Changing this would require a significant reworking + * of the MPS to generalise its ideas, and would bloat the pool classes. + * + * + * Assumptions: + * + * - Single-threaded mutator. In fact this code might work if other mutator + * threads are running, since the shield ought to operate correctly. + * However, in this case there can only be one trace running so that the + * correct fix method is chosen by ScanStateInit. + */ + +#include "trans.h" +#include "table.h" + + +#define TransformSig ((Sig)0x51926A45) /* SIGnature TRANSform */ + +typedef struct mps_transform_s { + Sig sig; /* */ + Arena arena; /* owning arena */ + Table oldToNew; /* map to apply to refs */ + Epoch epoch; /* epoch in which transform was created */ + Bool aborted; /* no longer transforming, just GCing */ +} TransformStruct; + + +Bool TransformCheck(Transform transform) +{ + CHECKS(Transform, transform); + CHECKU(Arena, transform->arena); + /* .check.boot: avoid bootstrap problem in transformTableAlloc where + transformTableFree checks the transform while the table is being + destroyed */ + if (transform->oldToNew != NULL) + CHECKD(Table, transform->oldToNew); + CHECKL(BoolCheck(transform->aborted)); + CHECKL(transform->epoch <= ArenaEpoch(transform->arena)); + return TRUE; +} + + +/* Allocator functions for the Table oldToNew */ + +static void *transformTableAlloc(void *closure, size_t size) +{ + Transform transform = (Transform)closure; + Res res; + void *p; + + AVERT(Transform, transform); + + res = ControlAlloc(&p, transform->arena, size); + if (res != ResOK) + return NULL; + + return p; +} + +static void transformTableFree(void *closure, void *p, size_t size) +{ + Transform transform = (Transform)closure; + AVERT(Transform, transform); + ControlFree(transform->arena, p, size); +} + + +Res TransformCreate(Transform *transformReturn, Arena arena) +{ + Transform transform; + Res res; + void *p; + + AVER(transformReturn != NULL); + AVERT(Arena, arena); + + res = ControlAlloc(&p, arena, sizeof(TransformStruct)); + if (res != ResOK) + goto failAlloc; + transform = (Transform)p; + + transform->oldToNew = NULL; + transform->arena = arena; + transform->epoch = ArenaEpoch(arena); + transform->aborted = FALSE; + + transform->sig = TransformSig; + + AVERT(Transform, transform); + + res = TableCreate(&transform->oldToNew, + 0, /* don't grow table until TransformAddOldNew */ + transformTableAlloc, + transformTableFree, + transform, + 0, 1); /* these can't be old references */ + if (res != ResOK) + goto failTable; + + *transformReturn = transform; + return ResOK; + +failTable: + ControlFree(arena, transform, sizeof(TransformStruct)); +failAlloc: + return res; +} + + +void TransformDestroy(Transform transform) +{ + Arena arena; + Table oldToNew; + + AVERT(Transform, transform); + + /* TODO: Log some transform statistics. */ + + /* Workaround bootstrap problem, see .check.boot */ + oldToNew = transform->oldToNew; + transform->oldToNew = NULL; + TableDestroy(oldToNew); + + arena = TransformArena(transform); + transform->sig = SigInvalid; + ControlFree(arena, transform, sizeof(TransformStruct)); +} + + +/* TransformArena -- return transform's arena + * + * Must be thread-safe as it is called outside the arena lock. See + * + */ + +Arena TransformArena(Transform transform) +{ + Arena arena; + AVER(TESTT(Transform, transform)); + arena = transform->arena; + AVER(TESTT(Arena, arena)); + return arena; +} + + +Res TransformAddOldNew(Transform transform, + Ref old_list[], + Ref new_list[], + Count count) +{ + Res res; + Index i; + Count added = 0; + + AVERT(Transform, transform); + AVER(old_list != NULL); + AVER(new_list != NULL); + /* count: cannot check */ + + res = TableGrow(transform->oldToNew, count); + if (res != ResOK) + return res; + + for(i = 0; i < count; ++i) { + /* NOTE: If the mutator isn't adding references while the arena is parked, + we might need to access the client-provided lists, using ArenaRead. */ + if(old_list[i] == NULL) + continue; /* permitted, but no transform to do */ + if(old_list[i] == new_list[i]) + continue; /* ignore identity-transforms */ + + /* Old refs must be in managed memory. */ + { + Seg seg; + AVER(SegOfAddr(&seg, transform->arena, old_list[i])); + } + + res = TableDefine(transform->oldToNew, (Word)old_list[i], new_list[i]); + AVER(res != ResFAIL); /* It's a static error to add the same old twice. */ + if (res != ResOK) + return res; + + ++added; + } + + AVERT(Transform, transform); + + return ResOK; +} + + +/* TransformApply -- transform references on the heap */ + +static Res transformFix(Seg seg, ScanState ss, Ref *refIO) +{ + Ref ref; + Transform transform; + Res res; + + AVERT_CRITICAL(Seg, seg); + AVERT_CRITICAL(ScanState, ss); + AVER_CRITICAL(refIO != NULL); + + transform = ss->fixClosure; + AVERT_CRITICAL(Transform, transform); + + if (!transform->aborted) { + void *refNew; + + ref = *refIO; + + if (TableLookup(&refNew, transform->oldToNew, (Word)ref)) { + if (ss->rank == RankAMBIG) { + /* We rely on the fact that ambiguous references are fixed + first, so that no exact references have been transformed + yet. */ + transform->aborted = TRUE; + } else { + /* NOTE: We could fix refNew in the table before copying it, + since any summaries etc. collected in the scan state will still + apply when it's copied. That could save a few snap-outs. */ + *refIO = refNew; + } + } + } + + /* Now progress to a normal GC fix. */ + /* TODO: Make a clean interface to this kind of dynamic binding. */ + ss->fix = ss->arena->emergency ? SegFixEmergency : SegFix; + TRACE_SCAN_BEGIN(ss) { + res = TRACE_FIX12(ss, refIO); + } TRACE_SCAN_END(ss); + ss->fix = transformFix; + + return res; +} + + +static void transformCondemn(void *closure, Word old, void *value) +{ + Seg seg; + GenDesc gen; + Bool b; + Trace trace = closure; + + AVERT(Trace, trace); + UNUSED(value); + + /* Find segment containing old address. */ + b = SegOfAddr(&seg, trace->arena, (Ref)old); + AVER(b); /* old refs must be in managed memory, else client param error */ + + /* Condemn generation containing seg if not already condemned. */ + gen = PoolSegPoolGen(SegPool(seg), seg)->gen; + AVERT(GenDesc, gen); + if (RingIsSingle(&gen->trace[trace->ti].traceRing)) + GenDescStartTrace(gen, trace); +} + + +Res TransformApply(Bool *appliedReturn, Transform transform) +{ + Res res; + Arena arena; + Globals globals; + Trace trace; + double mortality; + + AVER(appliedReturn != NULL); + AVERT(Transform, transform); + + arena = TransformArena(transform); + + /* If there have been any flips since the transform was created, the old + and new pointers will be invalid, since they are not scanned as roots. + The client program must park the arena before applying the transform. */ + if (transform->epoch != ArenaEpoch(arena)) + return ResPARAM; + + globals = ArenaGlobals(arena); + AVERT(Globals, globals); + + ArenaPark(globals); + + res = TraceCreate(&trace, arena, TraceStartWhyEXTENSION); + AVER(res == ResOK); /* parking should make a trace available */ + if (res != ResOK) + return res; + + /* Condemn the generations containing the transform's old objects, + so that all references to them are scanned. */ + TraceCondemnStart(trace); + TableMap(transform->oldToNew, transformCondemn, trace); + res = TraceCondemnEnd(&mortality, trace); + if (res != ResOK) { + /* Nothing to transform. */ + TraceDestroyInit(trace); + goto done; + } + + trace->fix = transformFix; + trace->fixClosure = transform; + + res = TraceStart(trace, 1.0, 0.0); + AVER(res == ResOK); /* transformFix can't fail */ + + /* If transformFix during traceFlip found ambiguous references and + aborted the transform then the rest of the trace is just a normal GC. + Note that aborting a trace part-way through is pretty much impossible + without corrupting the mutator graph. We could safely + if (transform->aborted) { + trace->fix = PoolFix; + trace->fixClosure = NULL; + } + */ + + /* Force the trace to complete now. */ + ArenaPark(globals); + +done: + if (transform->aborted) { + *appliedReturn = FALSE; + } else { + *appliedReturn = TRUE; + /* I'm not sure why the interface is defined this way. RB 2012-08-03 */ + TransformDestroy(transform); + } + + return ResOK; +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2011-2022 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/code/trans.h b/code/trans.h new file mode 100644 index 0000000000..a572138601 --- /dev/null +++ b/code/trans.h @@ -0,0 +1,63 @@ +/* trans.h: TRANSFORMS INTERFACE + * + * $Id$ + * Copyright 2011-2022 Ravenbrook Limited. See end of file for license. + */ + +#ifndef trans_h +#define trans_h + +#include "mpm.h" + + +typedef struct mps_transform_s *Transform; + +typedef struct OldNewStruct *OldNew; + +extern Res TransformCreate(Transform *transformReturn, Arena arena); + +extern Res TransformAddOldNew(Transform transform, + Ref old_list[], + Ref new_list[], + Count count); + +extern Res TransformApply(Bool *appliedReturn, Transform transform); + +extern void TransformDestroy(Transform transform); + +extern Bool TransformCheck(Transform transform); + +extern Arena TransformArena(Transform transform); + + +#endif /* trans_h */ + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2011-2022 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/code/ztfm.c b/code/ztfm.c new file mode 100644 index 0000000000..2f2c237692 --- /dev/null +++ b/code/ztfm.c @@ -0,0 +1,1432 @@ +/* ztfm.c: Transforms test + * + * $Id$ + * Copyright (c) 2011-2022 Ravenbrook Limited. See end of file for license. + */ + +#include "fmtdy.h" +#include "fmtdytst.h" +#include "mps.h" +#include "mpsavm.h" +#include "mpscamc.h" +#include "mpslib.h" +#include "testlib.h" + +#include /* printf */ + + +#define progressf(args) \ + printf args; +#define Xprogressf(args) \ + do{if(0)printf args;}while(FALSE) + +/* testChain -- generation parameters for the test */ +#define genCOUNT 2 +static mps_gen_param_s testChain[genCOUNT] = { + { 100, 0.85 }, { 170, 0.45 } }; + + +/* myroot -- arrays of references that are the root */ +#define myrootAmbigCOUNT 30000 +static void *myrootAmbig[myrootAmbigCOUNT]; +#define myrootExactCOUNT 1000000 +static void *myrootExact[myrootExactCOUNT]; + +static mps_root_t root_stackreg; +static void *stack_start; +static mps_thr_t stack_thr; + + +/* =========== Transform ==============*/ +static void get(mps_arena_t arena); + +static ulongest_t serial = 0; + +/* Tree nodes + * + * To make a node: + * - use next unique serial; + * - choose an id (eg. next in sequence); + * - choose a version (eg. 0). + * + * To make a New node from (or in connection with) an Old: + * - use next unique serial; + * - copy id of Old; + * - ver = ver(Old) + 1. + * + * To invalidate an Old node, when adding an OldNew pair for it: + * - ver = -1 + */ + +struct node_t { + mps_word_t word0; + mps_word_t word1; + mps_word_t serial_dyi; /* unique across every node ever */ + mps_word_t id_dyi; /* .id: replacement nodes copy this */ + mps_word_t ver_dyi; /* .version: distinguish new from old */ + struct node_t *left; + struct node_t *right; + mps_word_t tour_dyi; /* latest tour that visited this node */ + mps_word_t tourIdHash_dyi; /* hash of node ids, computed last tour */ +}; + +/* Tour -- a particular journey to visit to every node in the world + * + * A tour starts with the node at world[0], tours the graph reachable + * from it, then any further bits of graph reachable from world[1], + * and so on. + * + * As it does so, the tour computes a tourReport, characterising the + * state of everything reachable (from world) in the form a few numbers. + * + * Each tour explores the subgraph rooted at each node exactly once. + * That is: if the tour re-encounters a node already visited on that + * tour, it uses the values already computed for that node. + * + * The tourIdHash deliberately depends on the order in which nodes are + * encountered. Therefore, the tourIdHash of a tour depends on the + * entirety of the state of the world and all the world-reachable nodes. + * + * The tourVerSum, being a simple sum, does not depend on the order of + * visiting. + */ + +enum { + cVer = 10 +}; +typedef struct tourReportStruct { + ulongest_t tour; /* tour serial */ + ulongest_t tourIdHash; /* hash of node ids, computed last tour */ + ulongest_t acNodesVer[cVer]; /* count of nodes of each Ver */ +} *tourReport; + +static void tour_subgraph(tourReport tr_o, struct node_t *node); + +static ulongest_t tourSerial = 0; + +static void tourWorld(tourReport tr_o, mps_addr_t *world, ulongest_t countWorld) +{ + ulongest_t i; + + tourSerial += 1; + tr_o->tour = tourSerial; + tr_o->tourIdHash = 0; + for(i = 0; i < cVer; i++) { + tr_o->acNodesVer[i] = 0; + } + Xprogressf(( "[tour %"SCNuLONGEST"] BEGIN, world: %p, countWorld: %"SCNuLONGEST"\n", + tourSerial, (void *)world, countWorld)); + + for(i = 0; i < countWorld; i++) { + struct node_t *node; + node = (struct node_t *)world[i]; + tour_subgraph(tr_o, node); + tr_o->tourIdHash += i * (node ? DYI_INT(node->tourIdHash_dyi) : 0); + } +} + +static void tour_subgraph(tourReport tr_o, struct node_t *node) +{ + ulongest_t tour; + ulongest_t ver; + ulongest_t id; + struct node_t *left; + struct node_t *right; + ulongest_t tourIdHashLeft; + ulongest_t tourIdHashRight; + ulongest_t tourIdHash; + + Insist(tr_o != NULL); + + /* node == NULL is permitted */ + if(node == NULL) + return; + + tour = tr_o->tour; + if(DYI_INT(node->tour_dyi) == tour) + return; /* already visited */ + + /* this is a newly discovered node */ + Insist(DYI_INT(node->tour_dyi) < tour); + + /* mark as visited */ + node->tour_dyi = INT_DYI(tour); + + /* 'local' idHash = id, used for any re-encounters while computing the computed idHash */ + node->tourIdHash_dyi = node->id_dyi; + + /* record this node in the array of ver counts */ + ver = DYI_INT(node->ver_dyi); + Insist(ver < cVer); + tr_o->acNodesVer[ver] += 1; + + /* tour the subgraphs (NULL is permitted) */ + left = node->left; + right = node->right; + tour_subgraph(tr_o, left); + tour_subgraph(tr_o, right); + + /* computed idHash of subgraph at this node */ + id = DYI_INT(node->id_dyi); + tourIdHashLeft = left ? DYI_INT(left->tourIdHash_dyi) : 0; + tourIdHashRight = right ? DYI_INT(right->tourIdHash_dyi) : 0; + tourIdHash = (13*id + 17*tourIdHashLeft + 19*tourIdHashRight) & DYLAN_UINT_MASK; + Insist(tourIdHash <= DYLAN_UINT_MAX); + node->tourIdHash_dyi = INT_DYI(tourIdHash); + + Insist(DYI_INT(node->tour_dyi) == tour); + Xprogressf(( "[tour %"SCNuLONGEST"] new completed node: %p, ver: %"SCNuLONGEST", tourIdHash: %"SCNuLONGEST"\n", + tour, (void*)node, ver, tourIdHash )); +} + +static struct tourReportStruct trBefore; +static struct tourReportStruct trAfter; + +static void before(mps_addr_t *world, ulongest_t countWorld) +{ + tourWorld(&trBefore, world, countWorld); +} + +static void after(mps_addr_t *world, ulongest_t countWorld, + ulongest_t verOld, + longest_t deltaCVerOld, + ulongest_t verNew, + longest_t deltaCVerNew) +{ + longest_t dCVerOld; + longest_t dCVerNew; + + tourWorld(&trAfter, world, countWorld); + + dCVerOld = ((long)trAfter.acNodesVer[verOld] - (long)trBefore.acNodesVer[verOld]); + dCVerNew = ((long)trAfter.acNodesVer[verNew] - (long)trBefore.acNodesVer[verNew]); + + progressf(("tourWorld: (%"PRIuLONGEST" %"PRIuLONGEST":%"PRIuLONGEST"/%"PRIuLONGEST":%"PRIuLONGEST") -> (%"PRIuLONGEST" %"PRIuLONGEST":%+"PRIdLONGEST"/%"PRIuLONGEST":%+"PRIdLONGEST"), %s\n", + trBefore.tourIdHash, + verOld, + trBefore.acNodesVer[verOld], + verNew, + trBefore.acNodesVer[verNew], + + trAfter.tourIdHash, + verOld, + dCVerOld, + verNew, + dCVerNew, + + trBefore.tourIdHash == trAfter.tourIdHash ? "same" : "XXXXX DIFFERENT XXXXX" + )); + Insist(trBefore.tourIdHash == trAfter.tourIdHash); + Insist(dCVerOld == deltaCVerOld); + Insist(dCVerNew == deltaCVerNew); +} + + +static mps_res_t mps_arena_transform_objects_list(mps_bool_t *transform_done_o, + mps_arena_t mps_arena, + mps_addr_t *old_list, + size_t old_list_count, + mps_addr_t *new_list, + size_t new_list_count) +{ + mps_res_t res; + mps_transform_t transform; + mps_bool_t applied = FALSE; + + Insist(old_list_count == new_list_count); + + res = mps_transform_create(&transform, mps_arena); + if(res == MPS_RES_OK) { + /* We have a transform */ + res = mps_transform_add_oldnew(transform, old_list, new_list, old_list_count); + if(res == MPS_RES_OK) { + res = mps_transform_apply(&applied, transform); + } + if(applied) { + /* Transform has been destroyed */ + Insist(res == MPS_RES_OK); + } else { + mps_transform_destroy(transform); + } + } + + /* Always set *transform_done_o (even if there is also a non-ResOK */ + /* return code): it is a status report, not a material return. */ + *transform_done_o = applied; + return res; +} + +static void Transform(mps_arena_t arena, mps_ap_t ap) +{ + ulongest_t i; + ulongest_t keepCount = 0; + mps_word_t v; + struct node_t *node; + mps_res_t res; + mps_bool_t transform_done; + ulongest_t old, new; + ulongest_t perset; + + mps_arena_park(arena); + + { + /* Test with sets of pre-built nodes, a known distance apart. + * + * This gives control over whether new nodes are on the same + * segment as the olds or not. + */ + + ulongest_t iPerset; + ulongest_t aPerset[] = {0, 1, 1, 10, 10, 1000, 1000}; + ulongest_t cPerset = NELEMS(aPerset); + ulongest_t stepPerset; + ulongest_t countWorld = 0; + + /* randomize the order of set sizes from aPerset */ + stepPerset = 1 + (rnd() % (cPerset - 1)); + + progressf(("INT_DYI(1): %"PRIuLONGEST"; DYI_INT(5): %"PRIuLONGEST"\n", + (ulongest_t)INT_DYI(1), (ulongest_t)DYI_INT(5) )); + progressf(("Will make and transform sets of old nodes into new nodes. Set sizes: ")); + for(iPerset = stepPerset; + aPerset[iPerset] != 0; + iPerset = (iPerset + stepPerset) % cPerset) { + countWorld += aPerset[iPerset] * 2; /* 2: old + new */ + progressf(("%"PRIuLONGEST", ", aPerset[iPerset])); + } + progressf(("total: %"PRIuLONGEST".\n", countWorld)); + Insist(countWorld <= myrootExactCOUNT); + + keepCount = 0; + + for(iPerset = stepPerset; + aPerset[iPerset] != 0; + iPerset = (iPerset + stepPerset) % cPerset) { + ulongest_t j; + ulongest_t first; + ulongest_t skip; + ulongest_t count; + + perset = aPerset[iPerset]; + first = keepCount; + skip = 0; + count = perset; + progressf(("perset: %"PRIuLONGEST", first: %"PRIuLONGEST"\n", perset, first)); + + /* Make a set of olds, and a set of news */ + for(j = 0; j < 2 * perset; j++) { + ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2; + /* make_dylan_vector: fills slots with INT_DYI(0) */ + die(make_dylan_vector(&v, ap, slots), "make_dylan_vector"); + node = (struct node_t *)v; + node->serial_dyi = INT_DYI(serial++); + node->id_dyi = INT_DYI(j % perset); + node->ver_dyi = INT_DYI(1 + (j >= perset)); + node->left = NULL; + node->right = NULL; + node->tour_dyi = INT_DYI(tourSerial); + node->tourIdHash_dyi = INT_DYI(0); + myrootExact[keepCount++ % myrootExactCOUNT] = (void*)v; + get(arena); + /*printf("Object %"PRIuLONGEST" at %p.\n", keepCount, (void*)v);*/ + } + v = 0; + + /* >=10? pick subset */ + if(perset >= 10) { + /* subset of [first..first+perset) */ + + skip = (rnd() % (2 * perset)); + if(skip > (perset - 1)) + skip = 0; + + count = 1 + rnd() % (2 * (perset - skip)); + if(skip + count > perset) + count = perset - skip; + + Insist(skip < perset); + Insist(count >= 1); + Insist(skip + count <= perset); + } + + /* >=10? sometimes build tree */ + if(perset >= 10 && count >= 4 && rnd() % 2 == 0) { + struct node_t **oldNodes = (struct node_t **)&myrootExact[first + skip]; + struct node_t **newNodes = (struct node_t **)&myrootExact[first + skip + perset]; + progressf(("Building tree in %"PRIuLONGEST" nodes.\n", count)); + for(j = 1; (2 * j) + 1 < count; j++) { + oldNodes[j]->left = oldNodes[2 * j]; + oldNodes[j]->right = oldNodes[(2 * j) + 1]; + if(1){newNodes[j]->left = newNodes[2 * j]; + newNodes[j]->right = newNodes[(2 * j) + 1];} + } + } + + /* transform {count} olds into {count} news */ + before(myrootExact, countWorld); + /* after(myrootExact, countWorld, 1, 0, 2, 0); */ + progressf(("Transform [%"PRIuLONGEST"..%"PRIuLONGEST") to [%"PRIuLONGEST"..%"PRIuLONGEST").\n", + first + skip, first + skip + count, first + skip + perset, first + skip + count + perset)); + res = mps_arena_transform_objects_list(&transform_done, arena, + &myrootExact[first + skip], count, + &myrootExact[first + skip + perset], count); + Insist(res == MPS_RES_OK); + Insist(transform_done); + /* Olds decrease; news were in world already so don't increase. */ + after(myrootExact, countWorld, 1, -(longest_t)count, 2, 0); + } + } + + { + /* Transforming in various situations + * + * First, make two sets of 1024 nodes. + */ + + perset = 1024; + Insist(2*perset < myrootExactCOUNT); + for(keepCount = 0; keepCount < 2*perset; keepCount++) { + ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2; + /* make_dylan_vector: fills slots with INT_DYI(0) */ + die(make_dylan_vector(&v, ap, slots), "make_dylan_vector"); + node = (struct node_t *)v; + node->serial_dyi = INT_DYI(serial++); + node->id_dyi = INT_DYI(keepCount % perset); + node->ver_dyi = INT_DYI(1 + (keepCount >= perset)); + node->left = NULL; + node->right = NULL; + myrootExact[keepCount % myrootExactCOUNT] = (void*)v; + get(arena); + /* printf("Object %u at %p.\n", keepCount, (void*)v); */ + } + v = 0; + + + /* Functions before() and after() checksum the world, and verify + * that the expected transform occurred. + */ + before(myrootExact, perset); + after(myrootExact, perset, 1, 0, 2, 0); + + /* Don't transform node 0: its ref coincides with a segbase, so + * there are probably ambiguous refs to it on the stack. + * Don't transform last node either: this test code may leave an + * ambiguous reference to it on the stack. + */ + + /* Refs in root */ + /* ============ */ + old = 1; + new = 1 + perset; + Insist(myrootExact[old] != myrootExact[new]); + before(myrootExact, perset); + res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1); + Insist(res == MPS_RES_OK); + Insist(transform_done); + Insist(myrootExact[old] == myrootExact[new]); + after(myrootExact, perset, 1, -1, 2, +1); + + /* Refs in root: ambiguous ref causes failure */ + /* ========================================== */ + old = 2; + new = 2 + perset; + Insist(myrootExact[old] != myrootExact[new]); + /* Make an ambiguous reference. This must make the transform fail. */ + myrootAmbig[1] = myrootExact[old]; + before(myrootExact, perset); + res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1); + Insist(res == MPS_RES_OK); + Insist(!transform_done); + Insist(myrootExact[old] != myrootExact[new]); + after(myrootExact, perset, 1, 0, 2, 0); + + /* Ref in an object */ + /* ================ */ + old = 3; + new = 3 + perset; + node = myrootExact[4]; + progressf(("node: %p\n", (void *)node)); + node->left = myrootExact[old]; + Insist(myrootExact[old] != myrootExact[new]); + before(myrootExact, perset); + res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1); + Insist(res == MPS_RES_OK); + Insist(transform_done); + Insist(myrootExact[old] == myrootExact[new]); + after(myrootExact, perset, 1, -1, 2, +1); + } + + { + /* Tests with mps_transform_t + * + * **** USES OBJECTS CREATED IN PREVIOUS TEST GROUP **** + */ + + mps_transform_t t1; + mps_transform_t t2; + mps_bool_t applied = FALSE; + ulongest_t k, l; + mps_addr_t nullref1 = NULL; + mps_addr_t nullref2 = NULL; + + k = 9; /* start with this object (in set of 1024) */ + + /* Destroy */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + mps_transform_destroy(t1); + t1 = NULL; + + /* Empty (no add) */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + res = mps_transform_apply(&applied, t1); + Insist(res == MPS_RES_OK); + Insist(applied); + t1 = NULL; + after(myrootExact, perset, 1, 0, 2, 0); + + /* Identity-transform */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + for(l = k + 4; k < l; k++) { + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 1); + } + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 10); + k += 10; + res = mps_transform_apply(&applied, t1); + Insist(res == MPS_RES_OK); + Insist(applied); + t1 = NULL; + after(myrootExact, perset, 1, 0, 2, 0); + + /* Mixed non-trivial, NULL- and identity-transforms */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + { + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 1); + k += 1; + /* NULL */ + mps_transform_add_oldnew(t1, &nullref1, &myrootExact[k + perset], 1); + k += 1; + /* identity */ + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 1); + k += 1; + /* NULL */ + mps_transform_add_oldnew(t1, &nullref2, &myrootExact[k + perset], 1); + k += 1; + } + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10); + k += 10; + res = mps_transform_apply(&applied, t1); + Insist(res == MPS_RES_OK); + Insist(applied); + t1 = NULL; + after(myrootExact, perset, 1, -11, 2, +11); + + /* Non-trivial transform */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + for(l = k + 4; k < l; k++) { + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 1); + } + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10); + k += 10; + res = mps_transform_apply(&applied, t1); + Insist(res == MPS_RES_OK); + Insist(applied); + t1 = NULL; + after(myrootExact, perset, 1, -14, 2, +14); + + /* Two transforms, first destroyed unused */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10); + k += 10; + l = k; + res = mps_transform_create(&t2, arena); + Insist(res == MPS_RES_OK); + mps_transform_add_oldnew(t2, &myrootExact[l], &myrootExact[l + perset], 10); + l += 10; + res = mps_transform_apply(&applied, t2); + Insist(res == MPS_RES_OK); + Insist(applied); + t2 = NULL; + mps_transform_destroy(t1); + after(myrootExact, perset, 1, -10, 2, +10); + + /* Two transforms, both live [-- not supported yet. RHSK 2010-12-16] */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10); + k += 10; + l = k; + res = mps_transform_create(&t2, arena); + Insist(res == MPS_RES_OK); + mps_transform_add_oldnew(t2, &myrootExact[l], &myrootExact[l + perset], 10); + l += 10; + res = mps_transform_apply(&applied, t2); + Insist(res == MPS_RES_OK); + Insist(applied); + t2 = NULL; + k = l; + after(myrootExact, perset, 1, -10, 2, +10); + + /* Attempt to destroy after applied. */ + before(myrootExact, perset); + res = mps_transform_create(&t1, arena); + Insist(res == MPS_RES_OK); + res = mps_transform_apply(&applied, t1); + Insist(res == MPS_RES_OK); + Insist(applied); + after(myrootExact, perset, 1, 0, 2, 0); + } + + /* Large number of objects */ + { + ulongest_t count; + mps_transform_t t; + mps_bool_t applied; + + /* LARGE! */ + perset = myrootExactCOUNT / 2; + + Insist(2*perset <= myrootExactCOUNT); + for(keepCount = 0; keepCount < 2*perset; keepCount++) { + ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2; + /* make_dylan_vector: fills slots with INT_DYI(0) */ + die(make_dylan_vector(&v, ap, slots), "make_dylan_vector"); + node = (struct node_t *)v; + node->serial_dyi = INT_DYI(serial++); + node->id_dyi = INT_DYI(keepCount % perset); + node->ver_dyi = INT_DYI(1 + (keepCount >= perset)); + node->left = NULL; + node->right = NULL; + myrootExact[keepCount % myrootExactCOUNT] = (void*)v; + get(arena); + /* printf("Object %u at %p.\n", keepCount, (void*)v); */ + } + v = 0; + + /* Refs in root */ + /* ============ */ + /* don't transform 0: its ref coincides with a segbase, so causes ambig refs on stack */ + /* don't transform last: its ambig ref may be left on the stack */ + old = 1; + new = 1 + perset; + count = perset - 2; + Insist(myrootExact[old] != myrootExact[new]); + before(myrootExact, perset); + res = mps_transform_create(&t, arena); + Insist(res == MPS_RES_OK); + for(i = 0; i < count; i++) { + res = mps_transform_add_oldnew(t, &myrootExact[old + i], &myrootExact[new + i], 1); + Insist(res == MPS_RES_OK); + } + res = mps_transform_apply(&applied, t); + Insist(applied); + Insist(myrootExact[old] == myrootExact[new]); + after(myrootExact, perset, 1, -(longest_t)count, 2, +(longest_t)count); + } + + printf(" ...made and kept: %"PRIuLONGEST" objects.\n", + keepCount); +} + + +static ulongest_t cols(size_t bytes) +{ + double M; /* Mebibytes */ + ulongest_t cM; /* hundredths of a Mebibyte */ + + M = (double)bytes / (1UL<<20); + cM = (ulongest_t)(M * 100 + 0.5); /* round to nearest */ + return cM; +} + +/* showStatsAscii -- present collection stats, 'graphically' + * + */ +static void showStatsAscii(size_t notcon, size_t con, size_t live, size_t alimit) +{ + ulongest_t n = cols(notcon); + ulongest_t c = cols(notcon + con); + ulongest_t l = cols(notcon + live); /* a fraction of con */ + ulongest_t a = cols(alimit); + ulongest_t count; + ulongest_t i; + + /* if we can show alimit within 200 cols, do so */ + count = (a < 200) ? a + 1 : c; + + for(i = 0; i < count; i++) { + printf( (i == a) ? "A" + : (i < n) ? "n" + : (i < l) ? "L" + : (i < c) ? "_" + : " " + ); + } + printf("\n"); +} + + +/* print_M -- print count of bytes as Mebibytes or Megabytes + * + * Print as a whole number, "m" for the decimal point, and + * then the decimal fraction. + * + * Input: 208896 + * Output: (Mebibytes) 0m199 + * Output: (Megabytes) 0m209 + */ +#if 0 +#define bPerM (1UL << 20) /* Mebibytes */ +#else +#define bPerM (1000000UL) /* Megabytes */ +#endif +static void print_M(size_t bytes) +{ + size_t M; /* M thingies */ + double Mfrac; /* fraction of an M thingy */ + + M = bytes / bPerM; + Mfrac = (double)(bytes % bPerM); + Mfrac = (Mfrac / bPerM); + + printf("%1"PRIuLONGEST"m%03.f", (ulongest_t)M, Mfrac * 1000); +} + + +/* showStatsText -- present collection stats + * + * prints: + * Coll End 0m137[->0m019 14%-live] (0m211-not ) + */ +static void showStatsText(size_t notcon, size_t con, size_t live) +{ + double liveFrac = (double)live / (double)con; + + print_M(con); + printf("[->"); + print_M(live); + printf("% 3.f%%-live]", liveFrac * 100); + printf(" ("); + print_M(notcon); + printf("-not "); + printf(")\n"); +} + +/* get -- get messages + * + */ +static void get(mps_arena_t arena) +{ + mps_message_type_t type; + + while (mps_message_queue_type(&type, arena)) { + mps_message_t message; + static mps_clock_t mclockBegin = 0; + static mps_clock_t mclockEnd = 0; + mps_word_t *obj; + mps_word_t objind; + mps_addr_t objaddr; + + cdie(mps_message_get(&message, arena, type), + "get"); + + switch(type) { + case mps_message_type_gc_start(): { + mclockBegin = mps_message_clock(arena, message); + printf(" %5"PRIuLONGEST": (%5"PRIuLONGEST")", + mclockBegin, mclockBegin - mclockEnd); + printf(" Coll Begin (%s)\n", + mps_message_gc_start_why(arena, message)); + break; + } + case mps_message_type_gc(): { + size_t con = mps_message_gc_condemned_size(arena, message); + size_t notcon = mps_message_gc_not_condemned_size(arena, message); + /* size_t other = 0; -- cannot determine; new method reqd */ + size_t live = mps_message_gc_live_size(arena, message); + size_t alimit = mps_arena_reserved(arena); + + mclockEnd = mps_message_clock(arena, message); + + printf(" %5"PRIuLONGEST": (%5"PRIuLONGEST")", + mclockEnd, mclockEnd - mclockBegin); + printf(" Coll End "); + showStatsText(notcon, con, live); + if(rnd()==0) showStatsAscii(notcon, con, live, alimit); + break; + } + case mps_message_type_finalization(): { + mps_message_finalization_ref(&objaddr, arena, message); + obj = objaddr; + objind = DYLAN_INT_INT(DYLAN_VECTOR_SLOT(obj, 0)); + printf(" Finalization for object %"PRIuLONGEST" at %p\n", (ulongest_t)objind, objaddr); + break; + } + default: { + cdie(0, "message type"); + break; + } + } + + mps_message_discard(arena, message); + } +} + + +/* .catalog: The Catalog client: + * + * This is an MPS client for testing the MPS. It simulates + * converting a multi-page "Catalog" document from a page-description + * into a bitmap. + * + * The intention is that this task will cause memory usage that is + * fairly realistic (much more so than randomly allocated objects + * with random interconnections. The patterns in common with real + * clients are: + * - the program input and its task are 'fractal', with a + * self-similar hierarchy; + * - object allocation is prompted by each successive element of + * the input/task; + * - objects are often used to store a transformed version of the + * program input; + * - there may be several stages of transformation; + * - at each stage, the old object (holding the untransformed data) + * may become dead; + * - sometimes a tree of objects becomes dead once an object at + * some level of the hierarchy has been fully processed; + * - there is more than one hierarchy, and objects in different + * hierarchies interact. + * + * The entity-relationship diagram is: + * Catalog -< Page -< Article -< Polygon + * v + * | + * Palette --------------------< Colour + * + * The first hierarchy is a Catalog, containing Pages, each + * containing Articles (bits of artwork etc), each composed of + * Polygons. Each polygon has a single colour. + * + * The second hierarchy is a top-level Palette, containing Colours. + * Colours (in this client) are expensive, large objects (perhaps + * because of complex colour modelling or colour blending). + * + * The things that matter for their effect on MPS behaviour are: + * - when objects are allocated, and how big they are; + * - how the reference graph mutates over time; + * - how the mutator accesses objects (barrier hits). + */ + +enum { + CatalogRootIndex = 0, + CatalogSig = 0x0000CA2A, /* CATAlog */ + CatalogFix = 1, + CatalogVar = 10, + PageSig = 0x0000BA9E, /* PAGE */ + PageFix = 1, + PageVar = 100, + ArtSig = 0x0000A621, /* ARTIcle */ + ArtFix = 1, + ArtVar = 100, + PolySig = 0x0000B071, /* POLYgon */ + PolyFix = 1, + PolyVar = 100 +}; + +static void CatalogCheck(void) +{ + mps_word_t w; + void *Catalog, *Page, *Art, *Poly; + ulongest_t Catalogs = 0, Pages = 0, Arts = 0, Polys = 0; + int i, j, k; + + /* retrieve Catalog from root */ + Catalog = myrootExact[CatalogRootIndex]; + if(!Catalog) + return; + Insist(DYLAN_VECTOR_SLOT(Catalog, 0) == DYLAN_INT(CatalogSig)); + Catalogs += 1; + + for(i = 0; i < CatalogVar; i += 1) { + /* retrieve Page from Catalog */ + w = DYLAN_VECTOR_SLOT(Catalog, CatalogFix + i); + /* printf("Page = 0x%8x\n", (unsigned int) w); */ + if(w == DYLAN_INT(0)) + break; + Page = (void *)w; + Insist(DYLAN_VECTOR_SLOT(Page, 0) == DYLAN_INT(PageSig)); + Pages += 1; + + for(j = 0; j < PageVar; j += 1) { + /* retrieve Art from Page */ + w = DYLAN_VECTOR_SLOT(Page, PageFix + j); + if(w == DYLAN_INT(0)) + break; + Art = (void *)w; + Insist(DYLAN_VECTOR_SLOT(Art, 0) == DYLAN_INT(ArtSig)); + Arts += 1; + + for(k = 0; k < ArtVar; k += 1) { + /* retrieve Poly from Art */ + w = DYLAN_VECTOR_SLOT(Art, ArtFix + k); + if(w == DYLAN_INT(0)) + break; + Poly = (void *)w; + Insist(DYLAN_VECTOR_SLOT(Poly, 0) == DYLAN_INT(PolySig)); + Polys += 1; + } + } + } + printf("Catalog ok with: Catalogs: %"PRIuLONGEST", Pages: %"PRIuLONGEST", Arts: %"PRIuLONGEST", Polys: %"PRIuLONGEST".\n", + Catalogs, Pages, Arts, Polys); +} + + +/* CatalogDo -- make a Catalog and its tree of objects + * + * .catalog.broken: this code, when compiled with + * moderate optimization, may have ambiguous interior pointers but + * lack corresponding ambiguous base pointers to MPS objects. This + * means the interior pointers are unmanaged references, and the + * code goes wrong. The hack in poolamc.c#4 cures this, but not very + * nicely. For further discussion, see: + * + */ +static void CatalogDo(mps_arena_t arena, mps_ap_t ap) +{ + mps_word_t v; + void *Catalog, *Page, *Art, *Poly; + int i, j, k; + + die(make_dylan_vector(&v, ap, CatalogFix + CatalogVar), "Catalog"); + DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(CatalogSig); + Catalog = (void *)v; + + /* store Catalog in root */ + myrootExact[CatalogRootIndex] = Catalog; + get(arena); + + fflush(stdout); + CatalogCheck(); + + for(i = 0; i < CatalogVar; i += 1) { + die(make_dylan_vector(&v, ap, PageFix + PageVar), "Page"); + DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(PageSig); + Page = (void *)v; + + /* store Page in Catalog */ + DYLAN_VECTOR_SLOT(Catalog, CatalogFix + i) = (mps_word_t)Page; + get(arena); + + printf("Page %d: make articles\n", i); + fflush(stdout); + + for(j = 0; j < PageVar; j += 1) { + die(make_dylan_vector(&v, ap, ArtFix + ArtVar), "Art"); + DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(ArtSig); + Art = (void *)v; + + /* store Art in Page */ + DYLAN_VECTOR_SLOT(Page, PageFix + j) = (mps_word_t)Art; + get(arena); + + for(k = 0; k < ArtVar; k += 1) { + die(make_dylan_vector(&v, ap, PolyFix + PolyVar), "Poly"); + DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(PolySig); + Poly = (void *)v; + + /* store Poly in Art */ + DYLAN_VECTOR_SLOT(Art, ArtFix + k) = (mps_word_t)Poly; + /* get(arena); */ + } + } + } + fflush(stdout); + CatalogCheck(); +} + + +/* MakeThing -- make an object of the size requested (in bytes) + * + * Any size is accepted. MakeThing may round it up (MakeThing always + * makes a dylan vector, which has a minimum size of 8 bytes). Vector + * slots, if any, are initialized to DYLAN_INT(0). + * + * After making the object, calls get(), to retrieve MPS messages. + * + * make_dylan_vector [fmtdytst.c] says: + * size = (slots + 2) * sizeof(mps_word_t); + * That is: a dylan vector has two header words before the first slot. + */ +static void* MakeThing(mps_arena_t arena, mps_ap_t ap, size_t size) +{ + mps_word_t v; + ulongest_t words; + ulongest_t slots; + + words = (size + (sizeof(mps_word_t) - 1) ) / sizeof(mps_word_t); + if(words < 2) + words = 2; + + slots = words - 2; + die(make_dylan_vector(&v, ap, slots), "make_dylan_vector"); + get(arena); + + return (void *)v; +} + +static void BigdropSmall(mps_arena_t arena, mps_ap_t ap, size_t big, char small_ref) +{ + static ulongest_t keepCount = 0; + ulongest_t i; + + mps_arena_park(arena); + for(i = 0; i < 100; i++) { + (void) MakeThing(arena, ap, big); + if(small_ref == 'A') { + myrootAmbig[keepCount++ % myrootAmbigCOUNT] = MakeThing(arena, ap, 1); + } else if(small_ref == 'E') { + myrootExact[keepCount++ % myrootExactCOUNT] = MakeThing(arena, ap, 1); + } else { + cdie(0, "BigdropSmall: small must be 'A' or 'E'.\n"); + } + } +} + + +/* df -- diversity function + * + * Either deterministic based on "number", or 'random' (ie. call rnd). + */ + +static ulongest_t df(unsigned randm, ulongest_t number) +{ + if(randm == 0) { + return number; + } else { + return rnd(); + } +} + +static void Make(mps_arena_t arena, mps_ap_t ap, unsigned randm, unsigned keep1in, unsigned keepTotal, unsigned keepRootspace, unsigned sizemethod) +{ + unsigned keepCount = 0; + ulongest_t objCount = 0; + + Insist(keepRootspace <= myrootExactCOUNT); + + objCount = 0; + while(keepCount < keepTotal) { + mps_word_t v; + unsigned slots = 2; /* minimum */ + switch(sizemethod) { + case 0: { + /* minimum */ + slots = 2; + break; + } + case 1: { + slots = 2; + if(df(randm, objCount) % 10000 == 0) { + printf("*"); + slots = 300000; + } + break; + } + case 2: { + slots = 2; + if(df(randm, objCount) % 6661 == 0) { /* prime */ + printf("*"); + slots = 300000; + } + break; + } + default: { + printf("bad script command: sizemethod %u unknown.\n", sizemethod); + cdie(FALSE, "bad script command!"); + break; + } + } + die(make_dylan_vector(&v, ap, slots), "make_dylan_vector"); + DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(objCount); + DYLAN_VECTOR_SLOT(v, 1) = (mps_word_t)NULL; + objCount++; + if(df(randm, objCount) % keep1in == 0) { + /* keep this one */ + myrootExact[df(randm, keepCount) % keepRootspace] = (void*)v; + keepCount++; + } + get(arena); + } + printf(" ...made and kept: %u objects, storing cyclically in " + "first %u roots " + "(actually created %"PRIuLONGEST" objects, in accord with " + "keep-1-in %u).\n", + keepCount, keepRootspace, objCount, keep1in); +} + + +static void Rootdrop(char rank_char) +{ + ulongest_t i; + + if(rank_char == 'A') { + for(i = 0; i < myrootAmbigCOUNT; ++i) { + myrootAmbig[i] = NULL; + } + } else if(rank_char == 'E') { + for(i = 0; i < myrootExactCOUNT; ++i) { + myrootExact[i] = NULL; + } + } else { + cdie(0, "Rootdrop: rank must be 'A' or 'E'.\n"); + } +} + +#if 0 +#define stackwipedepth 50000 +static void stackwipe(void) +{ + unsigned iw; + ulongest_t aw[stackwipedepth]; + + /* http://xkcd.com/710/ */ + /* I don't want my friends to stop calling; I just want the */ + /* compiler to stop optimising away my code. */ + + /* Do you ever get two even numbers next to each other? Hmmmm :-) */ + for(iw = 0; iw < stackwipedepth; iw++) { + if((iw & 1) == 0) { + aw[iw] = 1; + } else { + aw[iw] = 0; + } + } + for(iw = 1; iw < stackwipedepth; iw++) { + if(aw[iw - 1] + aw[iw] != 1) { + printf("Errrr....\n"); + break; + } + } +} +#endif + +static void StackScan(mps_arena_t arena, int on) +{ + if(on) { + Insist(root_stackreg == NULL); + die(mps_root_create_reg(&root_stackreg, arena, + mps_rank_ambig(), (mps_rm_t)0, stack_thr, + mps_stack_scan_ambig, stack_start, 0), + "root_stackreg"); + Insist(root_stackreg != NULL); + } else { + Insist(root_stackreg != NULL); + mps_root_destroy(root_stackreg); + root_stackreg = NULL; + Insist(root_stackreg == NULL); + } +} + + +/* checksi -- check count of sscanf items is correct + */ + +static void checksi(int si, int si_shouldBe, const char *script, const char *scriptAll) +{ + if(si != si_shouldBe) { + printf("bad script command (sscanf found wrong number of params) %s (full script %s).\n", script, scriptAll); + cdie(FALSE, "bad script command!"); + } +} + +/* testscriptC -- actually runs a test script + * + */ +static void testscriptC(mps_arena_t arena, mps_ap_t ap, const char *script) +{ + const char *scriptAll = script; + int si, sb; /* sscanf items, sscanf bytes */ + + while(*script != '\0') { + switch(*script) { + case 'C': { + si = sscanf(script, "Collect%n", + &sb); + checksi(si, 0, script, scriptAll); + script += sb; + printf(" Collect\n"); + /* stackwipe(); */ + mps_arena_collect(arena); + mps_arena_release(arena); + break; + } + case 'T': { + si = sscanf(script, "Transform%n", + &sb); + checksi(si, 0, script, scriptAll); + script += sb; + printf(" Transform\n"); + Transform(arena, ap); + break; + } + case 'K': { + si = sscanf(script, "Katalog()%n", + &sb); + checksi(si, 0, script, scriptAll); + script += sb; + printf(" Katalog()\n"); + CatalogDo(arena, ap); + break; + } + case 'B': { + ulongest_t big = 0; + char small_ref = ' '; + si = sscanf(script, "BigdropSmall(big %"SCNuLONGEST", small %c)%n", + &big, &small_ref, &sb); + checksi(si, 2, script, scriptAll); + script += sb; + printf(" BigdropSmall(big %"PRIuLONGEST", small %c)\n", big, small_ref); + BigdropSmall(arena, ap, big, small_ref); + break; + } + case 'M': { + unsigned randm = 0; + unsigned keep1in = 0; + unsigned keepTotal = 0; + unsigned keepRootspace = 0; + unsigned sizemethod = 0; + si = sscanf(script, "Make(random %u, keep-1-in %u, keep %u, rootspace %u, sizemethod %u)%n", + &randm, &keep1in, &keepTotal, &keepRootspace, &sizemethod, &sb); + checksi(si, 5, script, scriptAll); + script += sb; + printf(" Make(random %u, keep-1-in %u, keep %u, rootspace %u, sizemethod %u).\n", + randm, keep1in, keepTotal, keepRootspace, sizemethod); + Make(arena, ap, randm, keep1in, keepTotal, keepRootspace, sizemethod); + break; + } + case 'R': { + char drop_ref = ' '; + si = sscanf(script, "Rootdrop(rank %c)%n", + &drop_ref, &sb); + checksi(si, 1, script, scriptAll); + script += sb; + printf(" Rootdrop(rank %c)\n", drop_ref); + Rootdrop(drop_ref); + break; + } + case 'S': { + unsigned on = 0; + si = sscanf(script, "StackScan(%u)%n", + &on, &sb); + checksi(si, 1, script, scriptAll); + script += sb; + printf(" StackScan(%u)\n", on); + StackScan(arena, on != 0); + break; + } + case 'Z': { + ulongest_t s0; + si = sscanf(script, "ZRndStateSet(%"SCNuLONGEST")%n", + &s0, &sb); + checksi(si, 1, script, scriptAll); + script += sb; + printf(" ZRndStateSet(%"PRIuLONGEST")\n", s0); + rnd_state_set((unsigned long)s0); + break; + } + case ' ': + case ',': + case '.': { + script++; + break; + } + default: { + printf("unknown script command '%c' (script %s).\n", + *script, scriptAll); + cdie(FALSE, "unknown script command!"); + return; + } + } + get(arena); + } + +} + + +/* testscriptB -- create pools and objects; call testscriptC + * + * Is called via mps_tramp, so matches mps_tramp_t function prototype, + * and use trampDataStruct to pass parameters. + */ + +typedef struct trampDataStruct { + mps_arena_t arena; + mps_thr_t thr; + const char *script; +} trampDataStruct; + +static void *testscriptB(void *arg, size_t s) +{ + trampDataStruct trampData; + mps_arena_t arena; + mps_thr_t thr; + const char *script; + mps_fmt_t fmt; + mps_chain_t chain; + mps_pool_t amc; + int i; + mps_root_t root_table_Ambig; + mps_root_t root_table_Exact; + mps_ap_t ap; + void *stack_starts_here; /* stack scanning starts here */ + + Insist(s == sizeof(trampDataStruct)); + trampData = *(trampDataStruct*)arg; + arena = trampData.arena; + thr = trampData.thr; + script = trampData.script; + + die(mps_fmt_create_A(&fmt, arena, dylan_fmt_A()), "fmt_create"); + die(mps_chain_create(&chain, arena, genCOUNT, testChain), "chain_create"); + die(mps_pool_create(&amc, arena, mps_class_amc(), fmt, chain), + "pool_create amc"); + + for(i = 0; i < myrootAmbigCOUNT; ++i) { + myrootAmbig[i] = NULL; + } + die(mps_root_create_table(&root_table_Ambig, arena, mps_rank_ambig(), (mps_rm_t)0, + myrootAmbig, (size_t)myrootAmbigCOUNT), + "root_create - ambig"); + + for(i = 0; i < myrootExactCOUNT; ++i) { + myrootExact[i] = NULL; + } + die(mps_root_create_table(&root_table_Exact, arena, mps_rank_exact(), (mps_rm_t)0, + myrootExact, (size_t)myrootExactCOUNT), + "root_create - exact"); + + die(mps_ap_create(&ap, amc, mps_rank_exact()), "ap_create"); + + /* root_stackreg: stack & registers are ambiguous roots = mutator's workspace */ + stack_start = &stack_starts_here; + stack_thr = thr; + die(mps_root_create_reg(&root_stackreg, arena, + mps_rank_ambig(), (mps_rm_t)0, stack_thr, + mps_stack_scan_ambig, stack_start, 0), + "root_stackreg"); + + + mps_message_type_enable(arena, mps_message_type_gc_start()); + mps_message_type_enable(arena, mps_message_type_gc()); + mps_message_type_enable(arena, mps_message_type_finalization()); + + testscriptC(arena, ap, script); + + printf(" Destroy roots, pools, arena etc.\n\n"); + mps_root_destroy(root_stackreg); + mps_ap_destroy(ap); + mps_root_destroy(root_table_Exact); + mps_root_destroy(root_table_Ambig); + mps_pool_destroy(amc); + mps_chain_destroy(chain); + mps_fmt_destroy(fmt); + + return NULL; +} + + +/* testscriptA -- create arena, thr; call testscriptB + */ +static void testscriptA(const char *script) +{ + mps_arena_t arena; + int si, sb; /* sscanf items, sscanf bytes */ + ulongest_t arenasize = 0; + mps_thr_t thr; + trampDataStruct trampData; + + si = sscanf(script, "Arena(size %"SCNuLONGEST")%n", &arenasize, &sb); + cdie(si == 1, "bad script command: Arena(size %%"PRIuLONGEST")"); + script += sb; + printf(" Create arena, size = %"PRIuLONGEST".\n", arenasize); + + /* arena */ + die(mps_arena_create(&arena, mps_arena_class_vm(), arenasize), + "arena_create"); + + /* thr: used to stop/restart multiple threads */ + die(mps_thread_reg(&thr, arena), "thread"); + + /* call testscriptB! */ + trampData.arena = arena; + trampData.thr = thr; + trampData.script = script; + testscriptB(&trampData, sizeof trampData); + + mps_thread_dereg(thr); + mps_arena_destroy(arena); +} + + +/* main -- runs various test scripts + * + */ +int main(int argc, char *argv[]) +{ + randomize(argc, argv); + mps_lib_assert_fail_install(assert_die); + + /* 1<<19 == 524288 == 1/2 Mebibyte */ + /* 16<<20 == 16777216 == 16 Mebibyte */ + + testscriptA("Arena(size 500000000), " + "Transform" + /*", Collect, Rootdrop(rank E), Collect, Collect"*/ + "."); + + printf("%s: Conclusion: Failed to find any defects.\n", argv[0]); + return 0; +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2011-2022 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/design/transform.txt b/design/transform.txt new file mode 100644 index 0000000000..503e8b5cd0 --- /dev/null +++ b/design/transform.txt @@ -0,0 +1,149 @@ +.. mode: -*- rst -*- + + +Transforms +========== + +:Tag: design.mps.transform +:Author: Richard Brooksby +:Date: 2012-09-04 +:Status: complete +:Revision: $Id$ +:Copyright: See `Copyright and License`_. +:Index terms: + pair: transforms; design + + +Introduction +------------ + +This document describes the Transform mechanism of the Memory Pool System. +Transforms allow the client code to replace a set of object references on the +heap. + +The readership of this document is any developer intending to modify the +Transform implementation. + + +Background +---------- + +Göran Rydqvist of Configura originally expressed the requirement for the +MPS to support the change of layout of objects in CET [GR_2010-02-25]_. +Ravenbrook proposed several methods [RHSK_2010-09-21]_ including: + + If you need to add fields, then use a special new MPS function (that + doesn't exist yet):: + + mps_arena_transform_objects(&my_transform_function); + + This traverses the object graph, lets your transform_function + basically ``realloc()`` the field-block, and MPS fixes up all + references from other objects to point to the new field-block. + + Unfortunately, this idea is probably killed off by ambiguous + references :-(. You could only run the patch if you could + *guarantee* there are no ambiguous refs you want. In other words, + any object refs on the stack would become instant death (or worse: + subtle slow death :-). Therefore we don't really like this idea + (unfortunately). There are safer and simpler ways to do it, we + think... + +which Configura selected [GR_2010-09-22]_. + +An initial implementation was made by RHSK and released to Configura as +"experimental", however Configura put it into production. + +During work on adapting the MPS to 64-bit Windows, RB reformed and +reimplemented transforms based on RHSK's original work. + + +Overview +-------- + +The client program builds a table mapping "old" references to "new" ones +in a ``Transform`` object. This is then "applied", causing a garbage +collection trace in which the fix function is substituted by +``transformFix()``, which spots "old" references and replaces them with +"new" ones, in addition to applying the usual garbage collection fix +function. + + +Not yet written +--------------- + +* Ambiguous references and aborting the transform. + +* How ambiguous references are avoided using ``arena->stackWarm``. + +* How the implementation is an add-on to the MPS. + +* How the code is kept separate from the mainstream MPS. + +* Why it has its own hash table implementation (no good reason). + +* Why it does a garbage collection and not just a transforming scan. + +* Nice side-effect is that "old" objects are killed. + +* Why the arena must be parked. + + + +References +---------- + +.. [GR_2010-02-25] + "Incremental object" (e-mail); + Göran Rydqvist; Configura; 2010-02-25; + . + +.. [RHSK_2010-09-21] + "Incremental object ideas" (e-mail); + Richard Kistruck; Ravenbrook Limited; 2010-09-21; + . + +.. [GR_2010-09-22] + "Incremental object ideas" (e-mail); + Göran Rydqvist; Configura; 2010-09-22; + . + + +Document History +---------------- + +- 2012-09-04 RB_ First draft. + +- 2022-01-23 GDR_ Converted to reStructuredText. + +.. _RB: https://www.ravenbrook.com/consultants/rb/ +.. _GDR: https://www.ravenbrook.com/consultants/gdr/ + + +Copyright and License +--------------------- + +Copyright © 2012–2020 `Ravenbrook Limited `_. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/manual/source/design/index.rst b/manual/source/design/index.rst index 7e568a89d7..5087eeeefb 100644 --- a/manual/source/design/index.rst +++ b/manual/source/design/index.rst @@ -45,6 +45,7 @@ Design testthr thread-manager thread-safety + transform type version-library vm diff --git a/manual/source/glossary/t.rst b/manual/source/glossary/t.rst index 4d094735d7..879f7a8a54 100644 --- a/manual/source/glossary/t.rst +++ b/manual/source/glossary/t.rst @@ -233,6 +233,15 @@ Memory Management Glossary: T objects are reachable. Those that were not reachable may be :term:`reclaimed`. + transform + + .. mps:specifc:: + + A mapping from old :term:`references` to new references, + represented by :c:type:`mps_transform_t`, that can be applied + to all references managed by the MPS. See + :ref:`topic-transform`. + translation buffer translation lookaside buffer diff --git a/manual/source/pool/amc.rst b/manual/source/pool/amc.rst index 4760f928bd..3e4009d7ee 100644 --- a/manual/source/pool/amc.rst +++ b/manual/source/pool/amc.rst @@ -139,3 +139,65 @@ AMC interface MPS_ARGS_ADD(args, MPS_KEY_FORMAT, fmt); res = mps_pool_create_k(&pool, arena, mps_class_amc(), args); } MPS_ARGS_END(args); + + When creating an :term:`allocation point` on an AMC pool, + :c:func:`mps_ap_create_k` accepts one optional keyword argument: + + * :c:macro:`MPS_KEY_AP_HASH_ARRAYS` (type :c:type:`mps_bool_t`, + defaulting to false) specifies (if true) that blocks allocated + from the allocation point do not contribute to the *new size* of + the :term:`nursery space` for the purposes of deciding whether + to start a collection of that generation. See + :ref:`pool-amc-hash-arrays`. + + +.. index:: + pair: AMC pool class; hash arrays + +.. _pool-amc-hash-arrays: + +Hash arrays +----------- + +The :term:`location dependency` feature of the MPS allows the +:term:`client program` to implement address-based hash tables in pools +like AMC that use a :term:`moving memory manager`, re-hashing the +tables when the addresses they contain might have moved. + +However, when a frequently-used hash table grows large enough, the +following sequence of events may take place: + +1. The hash table discovers that its location dependency is stale. + +2. A new array is allocated to contain the re-hashed keys. + +3. The new array is large enough to push the *new size* of the + :term:`nursery space` (that is, the amount of newly allocated + memory since the last collection in the first :term:`generation` in + the :term:`generation chain` for the pool containing the array) + close to its capacity. + +4. A small amount of additional allocation causes the new size of the + nursery generation to exceed its capacity, which causes the MPS to + start a new collection of that generation. This in turn causes the + hash table to become stale again. + +When the hash table reaches this critical size, the client program may +find that a large fraction of its time is being spent re-hashing the +table. + +In order to avoid this happening, the MPS provides a mechanism for +specifying that the newly allocated array does not contibute to the +new size of the nursery space: this cuts off the vicious cycle at step +3. To use this mechanism, pass true for the +:c:macro:`MPS_KEY_AP_HASH_ARRAYS` keyword argument to +:c:func:`mps_ap_create_k`. For example:: + + MPS_ARGS_BEGIN(args) { + MPS_ARGS_ADD(args, MPS_KEY_AP_HASH_ARRAYS, TRUE); + res = mps_ap_create_k(&ap, pool, args); + } MPS_ARGS_END(args); + +See :ref:`topic-collection-schedule` for an explanation of the *new +size* of a generation, and how the MPS uses this to determine when to +start a collection of that generation. diff --git a/manual/source/release.rst b/manual/source/release.rst index 6ff3db2958..653f886c0a 100644 --- a/manual/source/release.rst +++ b/manual/source/release.rst @@ -44,6 +44,23 @@ New features :ref:`topic-scanning-protocol`. This allows the client program to safely update references in the visited objects. +#. The new **transforms** feature updates references throughout the + automatically managed portion of the heap. For some use cases this + may be more convenient than :c:func:`mps_pool_walk`. See + :ref:`topic-transform`. + +#. 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`. + +#. An :term:`allocation point` for a pool belonging to the class + :ref:`pool-amc` can now be configured so that allocations do not + provoke garbage collections, reducing the amount of re-hashing for + address-based hash tables using :term:`location dependency`. See + :ref:`pool-amc-hash-arrays`. + Interface changes ................. diff --git a/manual/source/topic/arena.rst b/manual/source/topic/arena.rst index 0e6e086e9b..4b48e03671 100644 --- a/manual/source/topic/arena.rst +++ b/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,17 @@ Client arenas arena 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 when the arena is *extended*: + that is, when 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 when the arena is + *contracted*: that is, when 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 +994,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 +1051,79 @@ 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`. + + +.. 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:: + + Arena extension callbacks are only supported by :term:`virtual + memory arenas`. diff --git a/manual/source/topic/index.rst b/manual/source/topic/index.rst index b3139c16fb..c592c98d21 100644 --- a/manual/source/topic/index.rst +++ b/manual/source/topic/index.rst @@ -26,6 +26,7 @@ Reference debugging telemetry weak + transform plinth platform porting diff --git a/manual/source/topic/transform.rst b/manual/source/topic/transform.rst new file mode 100644 index 0000000000..75ea1a441d --- /dev/null +++ b/manual/source/topic/transform.rst @@ -0,0 +1,151 @@ +.. index:: + single: transform; introduction + +.. _topic-transform: + +Transforms +========== + +In a long-running interactive system, it may be desirable to change the format of live objects. In some programming languages (notably Smalltalk), when the programmer edits a class definition, objects belonging to the class must be updated so that they are valid instances of the redefined class. This may involve adding or removing fields from each instance and so changing the size of the allocated objects. + +If the object has grown as a result of the redefinition, this +redefinition can't be done in-place, so what actually happens is that +for each instance of the old version of the class, a corresponding +instance of the new version of the class is created, and all +:term:`references` to the old instance are rewritten to refer to the new +instance. Discovering "all references" to an object is a task that falls +to the garbage collector. + +*Transforms* are a general mechanism by which the client program requests the MPS to replace references to one set of objects (the *old* objects) with references to another (the *new* objects). The MPS performs this task by carrying out a complete garbage collection, in the course of which all references to old objects are discovered and substituted with references to the corresponding new object. + + +Cautions +-------- + +1. The arena must be :term:`parked ` (for example, by + calling :c:func:`mps_arena_park`) before creating the transform and + not :term:`unclamped ` before applying the + transform. + +2. A transform cannot be applied if there is an :term:`ambiguous + reference` to any of the old objects. (Because the MPS cannot know + whether or not the reference should be updated to point to the new + object.) + +.. warning:: + + The second caution means that transforms may be unsuitable for + client programs that treat the :term:`registers` and :term:`control + stack` as a :term:`root`, by using :c:func:`mps_root_create_thread` + and similar functions, unless the program can guarantee that none of + the old references will be referenced by this root. + + An alternative and more robust approach is to segregate the + :term:`formatted objects` that need to be updated into a suitable + :term:`pool`, and iterate over them using the function + :c:func:`mps_pool_walk`. + + +.. index:: + single: transform; interface + +Interface +--------- + +:: + + #include "mps.h" + + +.. c:type:: mps_transform_t + + The type of :term:`transforms`. A transform represents a mapping from *old* + :term:`references` to *new* references. + + +.. c:function:: mps_res_t mps_transform_create(mps_transform_t *transform_o, mps_arena_t arena) + + Create an empty :term:`transform`. + + ``transform_o`` points to a location that will hold the address of + the new transform. + + ``arena`` is the :term:`arena` in which to create the transform. + + :c:func:`mps_transform_create` returns :c:macro:`MPS_RES_OK` if + successful. The MPS may exhaust some resource in the course of + :c:func:`mps_transform_create` and will return an appropriate + :term:`result code` if so. + + .. note:: + + The arena must be :term:`parked ` (for example, + by calling :c:func:`mps_arena_park`) before creating a + transform, and if :c:func:`mps_transform_apply` is called on + a transform, it must be called before the arena is + :term:`unclamped `. + + +.. c:function:: mps_res_t mps_transform_add_oldnew(mps_transform_t transform, mps_addr_t *old_array, mps_addr_t *new_array, size_t count) + + Add mappings from an old :term:`reference` to a new reference to a + :term:`transform`. + + ``transform`` is the transform to which the mappings will be added. + + ``old_array`` points to an array of old references. + + ``new_array`` points to an array of corresponding new references. + + ``count`` is the number of references in both arrays. + + :c:func:`mps_transform_add_oldnew` returns :c:macro:`MPS_RES_OK` + if successful. The MPS may exhaust some resource in the course of + :c:func:`mps_transform_add_oldnew` and will return an appropriate + :term:`result code` if so. + + .. note:: + + Each old reference must be added at most once to a given + transform. + + +.. c:function:: mps_res_t mps_transform_apply(mps_bool_t *applied_o, mps_transform_t transform) + + Attempt to apply a :term:`transform`. + + ``applied_o`` points to a location that will hold a Boolean + indicating whether or not the transform was applied. + + ``transform`` is the transform to apply. + + If the :term:`arena` is currently incapable of applying the + transform, then an appropriate :term:`result code` is returned, and + the location pointed to by ``applied_o`` is not updated. Possible + causes include (but are not limited to) the arena not being in the + :term:`parked state` (in which case the result code is + :c:macro:`MPS_RES_LIMIT`), or a collection having taken place since + ``transform`` was created (in which case the result code is + :c:macro:`MPS_RES_PARAM`). + + If the arena is *capable* of applying the transform, then the MPS + carries out a :term:`garbage collection`, the arena is left in the + :term:`parked state`, :c:func:`mps_transform_apply` returns + :c:macro:`MPS_RES_OK`, and the location pointed to by ``applied_o`` + is updated. + + If in the course of the application, an :term:`ambiguous reference` + was discovered, then the transform is aborted and ``*applied_o`` is + set to false. In this case, *no* references to the old objects are + updated. (That is, either *all* of the transform is applied, or + *none* of it.) + + If the transform was successfully applied, it is destroyed, as if + :c:func:`mps_transform_destroy` had been called. + + +.. c:function:: void mps_transform_destroy(mps_transform_t transform) + + Destroy a :term:`transform`. + + ``transform`` is the transform to destroy. diff --git a/procedure/release-build.rst b/procedure/release-build.rst index eff46a9299..dc7bf55c96 100644 --- a/procedure/release-build.rst +++ b/procedure/release-build.rst @@ -199,10 +199,10 @@ On a Unix (including macOS) machine: p4 -c $CLIENT client -d $CLIENT rm -rf /tmp/$CLIENT -#. Edit the index of releases (``release/index.html``) and add the +#. Edit the index of releases (``release/index.*``) and add the release to the table, in a manner consistent with previous releases. -#. Edit the index of versions (``version/index.html``) and add the +#. Edit the index of versions (``version/index.*``) and add the release to the list of releases for *VERSION*, in a manner consistent with previous releases. diff --git a/tool/testcases.txt b/tool/testcases.txt index b19de92d12..787ba139d0 100644 --- a/tool/testcases.txt +++ b/tool/testcases.txt @@ -43,6 +43,7 @@ teletest =N interactive walkt0 zcoll =L zmess +ztfm =L ============= ================ ========================================== Key to flags