diff --git a/code/comm.gmk b/code/comm.gmk index 3a241d84ff..390ec78550 100644 --- a/code/comm.gmk +++ b/code/comm.gmk @@ -214,6 +214,7 @@ MPMCOMMON = \ trace.c \ traceanc.c \ tract.c \ + trans.c \ tree.c \ version.c \ vm.c \ @@ -294,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: @@ -572,6 +574,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 a94c87b17e..e3993899ba 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 4a41ea5641..082ea5cc06 100644 --- a/code/commpre.nmk +++ b/code/commpre.nmk @@ -99,7 +99,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 @@ -170,6 +171,7 @@ MPMCOMMON=\ [trace] \ [traceanc] \ [tract] \ + [trans] \ [tree] \ [version] \ [vm] \ diff --git a/code/mps.c b/code/mps.c index 41c8d076af..e7872b3c14 100644 --- a/code/mps.c +++ b/code/mps.c @@ -80,6 +80,7 @@ #include "failover.c" #include "vm.c" #include "policy.c" +#include "trans.c" /* Additional pool classes */ diff --git a/code/mps.h b/code/mps.h index 96214c8966..44727b9739 100644 --- a/code/mps.h +++ b/code/mps.h @@ -846,6 +846,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/mpsi.c b/code/mpsi.c index 5f19333639..10e26ab07a 100644 --- a/code/mpsi.c +++ b/code/mpsi.c @@ -1,7 +1,7 @@ /* mpsi.c: MEMORY POOL SYSTEM C INTERFACE LAYER * * $Id$ - * Copyright (c) 2001-2020 Ravenbrook Limited. See end of file for license. + * Copyright (c) 2001-2023 Ravenbrook Limited. See end of file for license. * Portions copyright (c) 2002 Global Graphics Software. * * .purpose: This code bridges between the MPS interface to C, @@ -47,6 +47,7 @@ #include "mpm.h" #include "mps.h" #include "sac.h" +#include "trans.h" #include @@ -2081,9 +2082,87 @@ void _mps_args_set_key(mps_arg_s args[MPS_ARGS_MAX], unsigned i, } +/* Transforms */ + + +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) 2001-2020 Ravenbrook Limited . + * Copyright (C) 2001-2023 Ravenbrook Limited . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are 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/trace.c b/code/trace.c index 9507eb8f72..0a3d95f451 100644 --- a/code/trace.c +++ b/code/trace.c @@ -1,11 +1,12 @@ /* trace.c: GENERIC TRACER IMPLEMENTATION * * $Id$ - * Copyright (c) 2001-2020 Ravenbrook Limited. + * Copyright (c) 2001-2023 Ravenbrook Limited. * See end of file for license. * Portions copyright (C) 2002 Global Graphics Software. * - * .design: . */ + * .design: design.mps.trace. + */ #include "locus.h" #include "mpm.h" @@ -81,10 +82,10 @@ void ScanStateInit(ScanState ss, TraceSet ts, Arena arena, AVERT(Rank, rank); /* white is arbitrary and can't be checked */ - /* NOTE: We can only currently support scanning for a set of traces - with the same fix method. To remove this restriction, it would be - necessary to dispatch to the fix methods of sets of traces in - TraceFix. */ + /* .fix.single: NOTE: We can only currently support scanning for a + set of traces with the same fix method. To remove this + restriction, it would be necessary to dispatch to the fix methods + of sets of traces in TraceFix. See also impl.c.trans.park. */ ss->fix = NULL; ss->fixClosure = NULL; TRACE_SET_ITER(ti, trace, ts, arena) { @@ -1920,7 +1921,7 @@ Res TraceDescribe(Trace trace, mps_lib_FILE *stream, Count depth) /* C. COPYRIGHT AND LICENSE * - * Copyright (C) 2001-2020 Ravenbrook Limited . + * Copyright (C) 2001-2023 Ravenbrook Limited . * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are diff --git a/code/trans.c b/code/trans.c new file mode 100644 index 0000000000..2fa6bac059 --- /dev/null +++ b/code/trans.c @@ -0,0 +1,369 @@ +/* trans.c: TRANSFORMS IMPLEMENTATION + * + * $Id$ + * Copyright 2011-2023 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 + * (design.mps.trace.fix). The mapping used to replace the references + * is built up in a hash table by the client. See + * design.mps.transform. + */ + +#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, /* no point guessing size before TransformAddOldNew */ + transformTableAlloc, + transformTableFree, + transform, + 0, 1); /* use invalid refs as special keys */ + 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; + Arena arena; + + AVERT(Transform, transform); + AVER(old_list != NULL); + AVER(new_list != NULL); + /* count: cannot check */ + + /* .assume.parked: If the mutator isn't adding references while the + arena is parked, we might need to access the client-provided + lists (old_list, new_list), using ArenaRead. Insisting on + parking keeps things simple. */ + arena = transform->arena; + AVER(ArenaGlobals(arena)->clamped); + AVER(arena->busyTraces == TraceSetEMPTY); + + res = TableGrow(transform->oldToNew, count); + if (res != ResOK) + return res; + + for (i = 0; i < count; ++i) { + if (old_list[i] == NULL) + continue; /* permitted, but no transform to do */ + if (old_list[i] == new_list[i]) + continue; /* ignore identity-transforms */ + + /* .old-white: Old refs must be in managed memory, because + transformFix is only reached when a reference is to something + in the condemned set. Other referenes are eliminated by + TraceFix, and we can't (currently) transformation of them. */ + { + 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 = NULL; /* suppress "may be used uninitialized" from GCC 11.3.0 */ + 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); + + /* .park: Parking the arena ensures that there is a trace available + and that no other traces are running, so that the tracer will + dispatch to transformFix correctly. See + impl.c.trace.fix.single. */ + 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: + *appliedReturn = !transform->aborted; + + return ResOK; +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2011-2023 Ravenbrook Limited . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/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..f5a497130c --- /dev/null +++ b/code/ztfm.c @@ -0,0 +1,1459 @@ +/* ztfm.c: Transforms test + * + * $Id$ + * Copyright (c) 2011-2022 Ravenbrook Limited. See end of file for license. + * + * .overview: This test creates data structures and then applies MPS + * transforms (see design.mps.transform) to them and verifies the + * result. It uses various checksums to verify that the structures + * are equivalent before and after the transform. + * + * .issues: TODO: This test has issues and needs refactoring: + * + * - GitHub issue #242 + * "Testing of transforms is unclear and overengineered" + * + * - GitHub issue #243 + * "ztfm.c fails to meet its requirement to test transforms on all + * platforms" + */ + +#include "fmtdy.h" +#include "fmtdytst.h" +#include "mps.h" +#include "mpsavm.h" +#include "mpscamc.h" +#include "mpslib.h" +#include "testlib.h" + +#include /* printf */ + +/* Hacky progress output with switches. */ +/* TODO: Tidy this up by extending testlib if necessary. */ +#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 + * + * The node structure is punned with a Dylan vector. The first two + * fields map to the Dylan wrapper and vector length. The fields with + * suffix "dyi" will be tagged as Dylan integers. TODO: Fix this + * somewhat unsafe punning. + * + * TODO: Is this actually a tree or a graph? + * + * 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; /* Dylan wrapper pointer */ + mps_word_t word1; /* Dylan vector length */ + 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 every node in the world calculating report with hash + * + * 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. + */ + +/* Maximum count of node versions. TODO: Should not be an enum. */ +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); + } + Insist(!applied || res == MPS_RES_OK); + 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) { + void **oldNodes = &myrootExact[first + skip]; + void **newNodes = &myrootExact[first + skip + perset]; + progressf(("Building tree in %"PRIuLONGEST" nodes.\n", count)); + for(j = 1; (2 * j) + 1 < count; j++) { + /* You might be tempted to lift some of these gnarly casts + into local variables, but if you do you will probably + create ambiguous references in stack slots and prevent + the transform from succeeding, as observed in Git commit + a7ebcbdf0. */ + ((struct node_t *)oldNodes[j])->left = oldNodes[2 * j]; + ((struct node_t *)oldNodes[j])->right = oldNodes[(2 * j) + 1]; + ((struct node_t *)newNodes[j])->left = newNodes[2 * j]; + ((struct node_t *)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); + mps_transform_destroy(t1); + 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); + mps_transform_destroy(t1); + 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); + mps_transform_destroy(t1); + 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); + mps_transform_destroy(t1); + 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); + mps_transform_destroy(t2); + t2 = NULL; + mps_transform_destroy(t1); + t1 = NULL; + 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); + mps_transform_destroy(t2); + t2 = NULL; + /* TODO: This test block does not destroy t1. Why is that? RB 2023-06-16 */ + k = l; + after(myrootExact, perset, 1, -10, 2, +10); + } + + /* 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); + mps_transform_destroy(t); + 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/index.txt b/design/index.txt index ab28baaa74..fcd59cf587 100644 --- a/design/index.txt +++ b/design/index.txt @@ -108,6 +108,7 @@ testthr_ Multi-threaded testing thread-manager_ Thread manager thread-safety_ Thread safety in the MPS trace_ Tracer +transform_ Transforms type_ General MPS types version-library_ Library version mechanism vm_ Virtual mapping @@ -185,6 +186,7 @@ writef_ The WriteF function .. _thread-manager: thread-manager .. _thread-safety: thread-safety .. _trace: trace +.. _transform: transform .. _type: type .. _version-library: version-library .. _vm: vm @@ -226,6 +228,7 @@ Document History - 2016-03-22 RB_ Add write-barier. - 2016-03-27 RB_ Goodbye pool MV *sniff*. - 2020-08-31 GDR_ Add walk. +- 2023-06-16 RB_ Add transform. .. _RB: https://www.ravenbrook.com/consultants/rb .. _NB: https://www.ravenbrook.com/consultants/nb @@ -236,7 +239,7 @@ Document History Copyright and License --------------------- -Copyright © 2002–2020 `Ravenbrook Limited `_. +Copyright © 2002–2023 `Ravenbrook Limited `_. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are diff --git a/design/transform.txt b/design/transform.txt new file mode 100644 index 0000000000..0975215a01 --- /dev/null +++ b/design/transform.txt @@ -0,0 +1,158 @@ +.. 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. + +This design was arrived at after some pain. The MPS isn't really +designed for generalized transformation of the object graph, 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. + + +Not yet written +--------------- + +* Ambiguous references and aborting the transform. + +* How ambiguous references are avoided using ``arena->stackWarm``. + +* Why it does a garbage collection and not just a transforming scan. + [This is partly explained in Overview_ above. RB 2023-06-16] + +* Nice side-effect is that "old" objects are killed. + +* Why the arena must be parked [When writing this up see + impl.c.trans.park and impl.c.trans.assume.parked. RB 2023-06-16]. + +* Why we can't transform arbitrary references (see + impl.c.trans.old-white). + + +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. + +- 2023-06-16 RB_ Updated and improved in order to make Transforms part + of the public MPS. + +.. _RB: https://www.ravenbrook.com/consultants/rb/ +.. _GDR: https://www.ravenbrook.com/consultants/gdr/ + + +Copyright and License +--------------------- + +Copyright © 2012–2023 `Ravenbrook Limited `_. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/manual/source/design/index.rst b/manual/source/design/index.rst index 8b074f61e9..8a7a95a65d 100644 --- a/manual/source/design/index.rst +++ b/manual/source/design/index.rst @@ -62,6 +62,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..b1ea2e1c33 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:specific:: + + 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/release.rst b/manual/source/release.rst index 6674cf691c..cabf9158f2 100644 --- a/manual/source/release.rst +++ b/manual/source/release.rst @@ -42,10 +42,15 @@ New features experimental: the implementation is likely to change in future versions of the MPS. See :ref:`design-monitor`. +#. The newly-public **transforms** feature updates references to a set + of objects throughout the automatically managed portion of the + heap, allowing them to be replaced by new versions. See + :ref:`topic-transform`. + #. The new function :c:func:`mps_pool_walk` visits all areas of :term:`formatted objects` in a pool using the - :ref:`topic-scanning-protocol`. This allows the client program to - safely update references in the visited objects. + :ref:`topic-scanning-protocol`, support hot reloading and + serialization. See :ref:`design-walk`. #. A :term:`virtual memory arena` can now be configured to call functions when it acquires a new chunk of :term:`address space`, 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..22b82fe2a2 --- /dev/null +++ b/manual/source/topic/transform.rst @@ -0,0 +1,166 @@ +.. 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, all of which + must be to objects in pools whose blocks are automatically managed + (see :ref:`pool-properties`). + + ``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 are: + + - the arena not being in the :term:`parked state` (in which case + the result code is :c:macro:`MPS_RES_LIMIT`) + + - 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.) + + The transform can only be applied once, and should be destroyed + after use, using :c:func:`mps_transform_destroy`. + + +.. c:function:: void mps_transform_destroy(mps_transform_t transform) + + Destroy a :term:`transform`, allowing its resources to be recycled. + + ``transform`` is the transform to destroy. diff --git a/tool/testcases.txt b/tool/testcases.txt index bf5c4e258c..721ed9a3f4 100644 --- a/tool/testcases.txt +++ b/tool/testcases.txt @@ -44,6 +44,7 @@ teletest =N interactive walkt0 zcoll =L zmess +ztfm =L =W ============= ================ ========================================== Key to flags