From 4ce753ec45db274d3d2c73faeedba75a0a4ebe22 Mon Sep 17 00:00:00 2001 From: Richard Brooksby Date: Wed, 12 Feb 2014 17:32:20 +0000 Subject: [PATCH] Abstracting partially mapped page tables into a sparsearray abstract datatype, removing a great deal of complexity from the vm arena, and some unnecessary double-initialisation and scanning loops during allocation. Copied from Perforce Change: 184333 ServerID: perforce.ravenbrook.com --- mps/code/arenacl.c | 9 + mps/code/arenavm.c | 404 ++++++------------------- mps/code/mps.c | 1 + mps/code/mps.xcodeproj/project.pbxproj | 4 + mps/code/sa.c | 208 +++++++++++++ mps/code/sa.h | 89 ++++++ mps/code/tract.c | 10 - 7 files changed, 402 insertions(+), 323 deletions(-) create mode 100644 mps/code/sa.c create mode 100644 mps/code/sa.h diff --git a/mps/code/arenacl.c b/mps/code/arenacl.c index 0ff7a21d3f2..5b233e7d936 100644 --- a/mps/code/arenacl.c +++ b/mps/code/arenacl.c @@ -145,7 +145,9 @@ failBootInit: static Res ClientChunkInit(Chunk chunk, BootBlock boot) { + Res res; ClientChunk clChunk; + void *p; /* chunk is supposed to be uninitialized, so don't check it. */ clChunk = Chunk2ClientChunk(chunk); @@ -154,6 +156,13 @@ static Res ClientChunkInit(Chunk chunk, BootBlock boot) clChunk->freePages = chunk->pages; /* too large @@@@ */ + /* Put the page table as late as possible, as in VM systems we don't want */ + /* to map it. */ + res = BootAlloc(&p, boot, chunk->pageTablePages << chunk->pageShift, chunk->pageSize); + if (res != ResOK) + return res; + chunk->pageTable = p; + return ResOK; } diff --git a/mps/code/arenavm.c b/mps/code/arenavm.c index 1722e0905ea..048c38db025 100644 --- a/mps/code/arenavm.c +++ b/mps/code/arenavm.c @@ -24,6 +24,7 @@ #include "boot.h" #include "tract.h" #include "bt.h" +#include "sa.h" #include "mpm.h" #include "mpsavm.h" @@ -45,8 +46,8 @@ typedef struct VMChunkStruct *VMChunk; typedef struct VMChunkStruct { ChunkStruct chunkStruct; /* generic chunk */ VM vm; /* virtual memory handle */ - Addr overheadMappedLimit; /* limit of pages mapped for overhead */ - BT pageTableMapped; /* indicates mapped state of page table */ + Addr overheadMappedLimit; /* limit of pages mapped for overhead */ + SparseArrayStruct pages; /* to manage backing store of page table */ Sig sig; /* */ } VMChunkStruct; @@ -112,10 +113,14 @@ static Bool VMChunkCheck(VMChunk vmchunk) CHECKL(VMAlign(vmchunk->vm) == ChunkPageSize(chunk)); CHECKL(vmchunk->overheadMappedLimit <= (Addr)chunk->pageTable); /* check pageTableMapped table */ + /* FIXME: Check sa's tables */ + CHECKD(SparseArray, &vmchunk->pages); +#if 0 CHECKL(vmchunk->pageTableMapped != NULL); CHECKL((Addr)vmchunk->pageTableMapped >= chunk->base); CHECKL(AddrAdd((Addr)vmchunk->pageTableMapped, BTSize(chunk->pageTablePages)) <= vmchunk->overheadMappedLimit); +#endif /* .improve.check-table: Could check the consistency of the tables. */ return TRUE; @@ -367,24 +372,36 @@ failVMCreate: static Res VMChunkInit(Chunk chunk, BootBlock boot) { - size_t btSize; VMChunk vmChunk; Addr overheadLimit; void *p; Res res; + BT saMapped, saPages; /* chunk is supposed to be uninitialized, so don't check it. */ vmChunk = Chunk2VMChunk(chunk); AVERT(BootBlock, boot); - - btSize = (size_t)BTSize(chunk->pageTablePages); - res = BootAlloc(&p, boot, btSize, MPS_PF_ALIGN); + + res = BootAlloc(&p, boot, BTSize(chunk->pages), MPS_PF_ALIGN); if (res != ResOK) - goto failPageTableMapped; - vmChunk->pageTableMapped = p; + goto failSaMapped; + saMapped = p; + + res = BootAlloc(&p, boot, BTSize(chunk->pageTablePages), MPS_PF_ALIGN); + if (res != ResOK) + goto failSaPages; + saPages = p; + + overheadLimit = AddrAdd(chunk->base, (Size)BootAllocated(boot)); + + /* Put the page table as late as possible, as in VM systems we don't want */ + /* to map it. */ + res = BootAlloc(&p, boot, chunk->pageTablePages << chunk->pageShift, chunk->pageSize); + if (res != ResOK) + goto failAllocPageTable; + chunk->pageTable = p; /* Actually commit all the tables. .@@@@ */ - overheadLimit = AddrAdd(chunk->base, (Size)BootAllocated(boot)); if (vmChunk->overheadMappedLimit < overheadLimit) { overheadLimit = AddrAlignUp(overheadLimit, ChunkPageSize(chunk)); res = vmArenaMap(VMChunkVMArena(vmChunk), vmChunk->vm, @@ -394,13 +411,19 @@ static Res VMChunkInit(Chunk chunk, BootBlock boot) vmChunk->overheadMappedLimit = overheadLimit; } - BTResRange(vmChunk->pageTableMapped, 0, chunk->pageTablePages); + SparseArrayInit(&vmChunk->pages, + chunk->pageTable, + sizeof(PageUnion), + chunk->pages, + saMapped, saPages, vmChunk->vm); return ResOK; /* .no-clean: No clean-ups needed for boot, as we will discard the chunk. */ failTableMap: -failPageTableMapped: +failSaPages: +failAllocPageTable: +failSaMapped: return res; } @@ -418,10 +441,8 @@ static void vmChunkDestroy(Chunk chunk) chunkUnmapSpare(chunk); - /* This check will also ensure that there are no non-free pages in the - chunk, because those pages would require mapped page table entries. */ - AVER(BTIsResRange(vmChunk->pageTableMapped, 0, chunk->pageTablePages)); - + SparseArrayFinish(&vmChunk->pages); + vmChunk->sig = SigInvalid; vm = vmChunk->vm; ChunkFinish(chunk); @@ -679,153 +700,6 @@ static Size VMArenaReserved(Arena arena) } -/* Page Table Partial Mapping - * - * Some helper functions - */ - - -/* tablePageBaseIndex -- index of the first page descriptor falling - * (at least partially) on this table page - * - * .repr.table-page: Table pages are passed as the page's base address. - * - * .division: We calculate it by dividing the offset from the beginning - * of the page table by the size of a table element. This relies on - * .vm.addr-is-star. - */ -#define tablePageBaseIndex(chunk, tablePage) \ - (AddrOffset((Addr)(chunk)->pageTable, (tablePage)) \ - / sizeof(PageUnion)) - - -/* tablePageWholeBaseIndex - * - * Index of the first page descriptor wholly on this table page. - * Table page specified by address (not index). - */ -#define tablePageWholeBaseIndex(chunk, tablePage) \ - (AddrOffset((Addr)(chunk)->pageTable, \ - AddrAdd((tablePage), sizeof(PageUnion)-1)) \ - / sizeof(PageUnion)) - - -/* tablePageLimitIndex -- index of the first page descriptor falling - * (wholly) on the next table page - * - * Similar to tablePageBaseIndex, see .repr.table-page and .division. - */ -#define tablePageLimitIndex(chunk, tablePage) \ - ((AddrOffset((Addr)(chunk)->pageTable, (tablePage)) \ - + ChunkPageSize(chunk) - 1) \ - / sizeof(PageUnion) \ - + 1) - -/* tablePageWholeLimitIndex - * - * Index of the first page descriptor falling partially on the next - * table page. - */ -#define tablePageWholeLimitIndex(chunk, tablePage) \ - ((AddrOffset((Addr)(chunk)->pageTable, (tablePage)) \ - + ChunkPageSize(chunk)) \ - / sizeof(PageUnion)) - - -/* tablePagesUsed - * - * Takes a range of pages identified by [pageBase, pageLimit), and - * returns the pages occupied by the page table which store the - * PageUnion descriptors for those pages. - */ -static void tablePagesUsed(Index *tableBaseReturn, Index *tableLimitReturn, - Chunk chunk, Index pageBase, Index pageLimit) -{ - *tableBaseReturn = - PageTablePageIndex(chunk, - AddrPageBase(chunk, addrOfPageDesc(chunk, pageBase))); - *tableLimitReturn = - PageTablePageIndex(chunk, - AddrAlignUp(addrOfPageDesc(chunk, pageLimit), - ChunkPageSize(chunk))); - return; -} - - -/* tablePagesEnsureMapped -- ensure needed part of page table is mapped - * - * Pages from baseIndex to limitIndex are about to be allocated. - * Ensure that the relevant pages occupied by the page table are mapped. - */ -static Res tablePagesEnsureMapped(VMChunk vmChunk, - Index baseIndex, Index limitIndex) -{ - /* tableBaseIndex, tableLimitIndex, tableCursorIndex, */ - /* unmappedBase, unmappedLimit are all indexes of pages occupied */ - /* by the page table. */ - Index tableBaseIndex, tableLimitIndex; - Index tableCursorIndex; - Index unmappedBaseIndex, unmappedLimitIndex; - Index i; - Chunk chunk; - Res res; - - chunk = VMChunk2Chunk(vmChunk); - - tablePagesUsed(&tableBaseIndex, &tableLimitIndex, - chunk, baseIndex, limitIndex); - - tableCursorIndex = tableBaseIndex; - - while(BTFindLongResRange(&unmappedBaseIndex, &unmappedLimitIndex, - vmChunk->pageTableMapped, - tableCursorIndex, tableLimitIndex, - 1)) { - Addr unmappedBase = TablePageIndexBase(chunk, unmappedBaseIndex); - Addr unmappedLimit = TablePageIndexBase(chunk, unmappedLimitIndex); - /* There might be a page descriptor overlapping the beginning */ - /* of the range of table pages we are about to map. */ - /* We need to work out whether we should touch it. */ - if (unmappedBaseIndex == tableBaseIndex - && unmappedBaseIndex > 0 - && !BTGet(vmChunk->pageTableMapped, unmappedBaseIndex - 1)) { - /* Start with first descriptor wholly on page */ - baseIndex = tablePageWholeBaseIndex(chunk, unmappedBase); - } else { - /* start with first descriptor partially on page */ - baseIndex = tablePageBaseIndex(chunk, unmappedBase); - } - /* Similarly for the potentially overlapping page descriptor */ - /* at the end. */ - if (unmappedLimitIndex == tableLimitIndex - && unmappedLimitIndex < chunk->pageTablePages - && !BTGet(vmChunk->pageTableMapped, unmappedLimitIndex)) { - /* Finish with last descriptor wholly on page */ - limitIndex = tablePageBaseIndex(chunk, unmappedLimit); - } else if (unmappedLimitIndex == chunk->pageTablePages) { - /* Finish with last descriptor in chunk */ - limitIndex = chunk->pages; - } else { - /* Finish with last descriptor partially on page */ - limitIndex = tablePageWholeBaseIndex(chunk, unmappedLimit); - } - res = vmArenaMap(VMChunkVMArena(vmChunk), - vmChunk->vm, unmappedBase, unmappedLimit); - if (res != ResOK) - return res; - BTSetRange(vmChunk->pageTableMapped, unmappedBaseIndex, unmappedLimitIndex); - for(i = baseIndex; i < limitIndex; ++i) { - PageInit(chunk, i); - } - tableCursorIndex = unmappedLimitIndex; - if (tableCursorIndex == tableLimitIndex) - break; - } - - return ResOK; -} - - /* pagesFindFreeInArea -- find a range of free pages in a given address range * * Search for a free run of pages in the free table, between the given @@ -1210,31 +1084,6 @@ static Res VMNZAllocPolicy(Index *baseIndexReturn, VMChunk *chunkReturn, } -/* pageDescIsMapped -- is the page descriptor for a page mapped? */ - -static Bool pageDescIsMapped(VMChunk vmChunk, Index pi) -{ - Index pageTableBaseIndex; - Index pageTableLimitIndex; - Chunk chunk = VMChunk2Chunk(vmChunk); - - AVER(pi < chunk->pages); - - /* Note that unless the pi'th PageUnion crosses a page boundary */ - /* Base and Limit will differ by exactly 1. */ - /* They will differ by at most 2 assuming that */ - /* sizeof(PageUnion) <= ChunkPageSize(chunk) (!) */ - tablePagesUsed(&pageTableBaseIndex, &pageTableLimitIndex, chunk, pi, pi+1); - /* using unsigned arithmetic overflow to use just one comparison */ - AVER(pageTableLimitIndex - pageTableBaseIndex - 1 < 2); - - /* We can examine the page descriptor iff both table pages */ - /* are mapped. */ - return BTGet(vmChunk->pageTableMapped, pageTableBaseIndex) && - BTGet(vmChunk->pageTableMapped, pageTableLimitIndex - 1); -} - - /* pageState -- determine page state, even if unmapped * * Parts of the page table may be unmapped if their corresponding pages are @@ -1244,7 +1093,7 @@ static Bool pageDescIsMapped(VMChunk vmChunk, Index pi) static unsigned pageState(VMChunk vmChunk, Index pi) { Chunk chunk = VMChunk2Chunk(vmChunk); - if (pageDescIsMapped(vmChunk, pi)) + if (SparseArrayIsMapped(&vmChunk->pages, pi)) return PageState(&chunk->pageTable[pi]); return PageStateFREE; } @@ -1270,150 +1119,81 @@ static void sparePageRelease(VMChunk vmChunk, Index pi) } -/* tablePagesUnmap -- unmap page table pages describing a page range - * - * The pages in the range [basePI, limitPI) have been freed, and this - * function then attempts to unmap the corresponding part of the page - * table. This may not be possible because other parts of those pages may - * be in use. This function extends the range as far as possible across - * free pages, so that such cases will be cleaned up eventually. - * - * This code corresponds to tablePagesEnsureMapped, but is defensive, and - * not constructed in the same way. We expect only to find one extra - * page table page at the top and bottom of the range that we could unmap, - * because previous unmappings should have cleaned up, but if we find more - * then this function cleans them up too. - */ - -static void tablePagesUnmap(VMChunk vmChunk, Index basePI, Index limitPI) +static Res pageDescMap(VMChunk vmChunk, Index basePI, Index limitPI) { - Addr base, limit; - Chunk chunk; - - chunk = VMChunk2Chunk(vmChunk); - AVER(basePI < chunk->pages); - AVER(limitPI <= chunk->pages); - AVER(basePI < limitPI); + Size before = VMMapped(vmChunk->vm); + Arena arena = VMArena2Arena(VMChunkVMArena(vmChunk)); + Res res = SparseArrayMap(&vmChunk->pages, basePI, limitPI); + arena->committed += VMMapped(vmChunk->vm) - before; + return res; +} - /* Now attempt to unmap the part of the page table that's no longer - in use because we've made a run of pages free. This scan will - also catch any adjacent unused pages, though they ought to have - been caught by previous scans. */ - - /* Lower basePI until we reach a desciptor we can't unmap, or the - beginning of the table. We scan right down to page zero even - though allocations start at chunk->allocBase so that the first table - page can be unmapped. */ - AVER(pageState(vmChunk, basePI) == PageStateFREE); - while (basePI > 0) { - Bool mapped = pageDescIsMapped(vmChunk, basePI - 1); - if (mapped && PageState(&chunk->pageTable[basePI - 1]) != PageStateFREE) - break; - --basePI; - if (!mapped) - break; - } - AVER(pageState(vmChunk, basePI) == PageStateFREE); - - /* Calculate the base of the range we can unmap. */ - base = AddrAlignUp(addrOfPageDesc(chunk, basePI), ChunkPageSize(chunk)); - - /* Raise limitPI until we reach a descriptor we can't unmap, or the end - of the table. */ - AVER(pageState(vmChunk, limitPI - 1) == PageStateFREE); - while (limitPI < chunk->pages) { - Bool mapped = pageDescIsMapped(vmChunk, limitPI); - if (mapped && PageState(&chunk->pageTable[limitPI]) != PageStateFREE) - break; - ++limitPI; - if (!mapped) - break; - } - AVER(pageState(vmChunk, limitPI - 1) == PageStateFREE); - - /* Calculate the limit of the range we can unmap. */ - if (limitPI < chunk->pages) - limit = AddrAlignDown(addrOfPageDesc(chunk, limitPI), ChunkPageSize(chunk)); - else - limit = AddrAlignUp(addrOfPageDesc(chunk, limitPI), ChunkPageSize(chunk)); - - /* Base and limit may be equal or out of order, if there were few - descriptors in the range. In that case, we can't unmap anything. */ - if (base < limit) { - vmArenaUnmap(VMChunkVMArena(vmChunk), vmChunk->vm, base, limit); - BTResRange(vmChunk->pageTableMapped, - PageTablePageIndex(chunk, base), - PageTablePageIndex(chunk, limit)); - } +static void pageDescUnmap(VMChunk vmChunk, Index basePI, Index limitPI) +{ + Size before = VMMapped(vmChunk->vm); + Arena arena = VMArena2Arena(VMChunkVMArena(vmChunk)); + SparseArrayUnmap(&vmChunk->pages, basePI, limitPI); + arena->committed += VMMapped(vmChunk->vm) - before; } /* pagesMarkAllocated -- Mark the pages allocated */ static Res pagesMarkAllocated(VMArena vmArena, VMChunk vmChunk, - Index baseIndex, Count pages, Pool pool) + Index basePI, Count pages, Pool pool) { - Index i, mappedLimit, limitIndex; + Index cursor, i, j, k; + Index limitPI; Chunk chunk = VMChunk2Chunk(vmChunk); Res res; + + limitPI = basePI + pages; + AVER(limitPI <= chunk->pages); - /* Ensure that the page descriptors we need are on mapped pages. */ - limitIndex = baseIndex + pages; - AVER(limitIndex <= chunk->pages); - res = tablePagesEnsureMapped(vmChunk, baseIndex, limitIndex); - if (res != ResOK) - goto failTableMap; + /* NOTE: We could find a reset bit range in vmChunk->pages.pages in order + to skip across hundreds of pages at once. That could speed up really + big block allocations (hundreds of pages long). */ - /* We're not expecting zero-sized allocations. */ - AVER(baseIndex < limitIndex); - - i = baseIndex; - mappedLimit = baseIndex; - while (i < limitIndex) { - Addr freeBase; - - /* Allocate a run of spare pages. */ - while(i < limitIndex && PageState(&chunk->pageTable[i]) == PageStateSPARE) { + cursor = basePI; + while (BTFindLongResRange(&j, &k, vmChunk->pages.mapped, cursor, limitPI, 1)) { + for (i = cursor; i < j; ++i) { sparePageRelease(vmChunk, i); PageAlloc(chunk, i, pool); - ++i; } - - if (i >= limitIndex) - return ResOK; - - /* Allocate a run of free pages. */ - freeBase = PageIndexBase(chunk, i); - AVER(PageState(&chunk->pageTable[i]) == PageStateFREE); - while (i < limitIndex && PageState(&chunk->pageTable[i]) == PageStateFREE) { - PageAlloc(chunk, i, pool); - ++i; - } - - /* Map the memory for those free pages. */ - res = vmArenaMap(vmArena, vmChunk->vm, freeBase, PageIndexBase(chunk, i)); + res = pageDescMap(vmChunk, j, k); if (res != ResOK) - goto failPagesMap; - mappedLimit = i; + goto failSAMap; + res = vmArenaMap(vmArena, vmChunk->vm, + PageIndexBase(chunk, j), PageIndexBase(chunk, k)); + if (res != ResOK) + goto failVMMap; + for (i = j; i < k; ++i) { + PageInit(chunk, i); + PageAlloc(chunk, i, pool); + } + cursor = k; + if (cursor == limitPI) + return ResOK; + } + for (i = cursor; i < limitPI; ++i) { + sparePageRelease(vmChunk, i); + PageAlloc(chunk, i, pool); } - return ResOK; -failPagesMap: - /* region from baseIndex to mappedLimit needs unmapping */ - /* TODO: Consider making them spare instead, then purging. */ - if (baseIndex < mappedLimit) { +failVMMap: + pageDescUnmap(vmChunk, j, k); +failSAMap: + /* region from basePI to j needs deallocating */ + /* TODO: Consider making pages spare instead, then purging. */ + if (basePI < j) { vmArenaUnmap(vmArena, vmChunk->vm, - PageIndexBase(chunk, baseIndex), - PageIndexBase(chunk, mappedLimit)); + PageIndexBase(chunk, basePI), + PageIndexBase(chunk, j)); + for (i = basePI; i < j; ++i) + PageFree(chunk, i); + pageDescUnmap(vmChunk, basePI, j); } - while (i > baseIndex) { - --i; - TractFinish(PageTract(&chunk->pageTable[i])); - PageFree(chunk, i); - } - tablePagesUnmap(vmChunk, baseIndex, limitIndex); -failTableMap: return res; } @@ -1569,7 +1349,6 @@ static Size chunkUnmapAroundPage(Chunk chunk, Size size, Page page) do { sparePageRelease(vmChunk, limitPI); - PageInit(chunk, limitPI); ++limitPI; purged += pageSize; } while (purged < size && @@ -1580,7 +1359,6 @@ static Size chunkUnmapAroundPage(Chunk chunk, Size size, Page page) pageState(vmChunk, basePI - 1) == PageStateSPARE) { --basePI; sparePageRelease(vmChunk, basePI); - PageInit(chunk, basePI); purged += pageSize; } @@ -1589,7 +1367,7 @@ static Size chunkUnmapAroundPage(Chunk chunk, Size size, Page page) PageIndexBase(chunk, basePI), PageIndexBase(chunk, limitPI)); - tablePagesUnmap(vmChunk, basePI, limitPI); + pageDescUnmap(vmChunk, basePI, limitPI); return purged; } diff --git a/mps/code/mps.c b/mps/code/mps.c index 2c8efbb4013..329c67de9a4 100644 --- a/mps/code/mps.c +++ b/mps/code/mps.c @@ -73,6 +73,7 @@ #include "abq.c" #include "range.c" #include "freelist.c" +#include "sa.c" /* Additional pool classes */ diff --git a/mps/code/mps.xcodeproj/project.pbxproj b/mps/code/mps.xcodeproj/project.pbxproj index 7297c986e5f..2b0c12bae70 100644 --- a/mps/code/mps.xcodeproj/project.pbxproj +++ b/mps/code/mps.xcodeproj/project.pbxproj @@ -1160,6 +1160,8 @@ 3104B02F156D39F2000A585A /* amssshe.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = amssshe.c; sourceTree = ""; }; 3104B03D156D3AD7000A585A /* segsmss */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = segsmss; sourceTree = BUILT_PRODUCTS_DIR; }; 3107DC4E173B03D100F705C8 /* arg.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = arg.h; sourceTree = ""; }; + 3112ED3A18ABC57F00CC531A /* sa.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sa.h; sourceTree = ""; }; + 3112ED3B18ABC75200CC531A /* sa.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = sa.c; sourceTree = ""; }; 3114A590156E913C001E0AA3 /* locv */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = locv; sourceTree = BUILT_PRODUCTS_DIR; }; 3114A5A1156E9168001E0AA3 /* locv.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = locv.c; sourceTree = ""; }; 3114A5A7156E92C0001E0AA3 /* qs */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = qs; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1908,6 +1910,8 @@ 311F2F7A17398B8E00C15B6A /* tract.h */, 31EEAC44156AB32500714D05 /* version.c */, 31EEAC0E156AB27B00714D05 /* walk.c */, + 3112ED3A18ABC57F00CC531A /* sa.h */, + 3112ED3B18ABC75200CC531A /* sa.c */, ); name = "MPM Core"; sourceTree = ""; diff --git a/mps/code/sa.c b/mps/code/sa.c new file mode 100644 index 00000000000..bd2d91fe46d --- /dev/null +++ b/mps/code/sa.c @@ -0,0 +1,208 @@ +/* sa.c: SPARSE ARRAY IMPLEMENTATION + * + * $Id$ + * Copyright (c) 2014 Ravenbrook Limited. See end of file for license. + */ + +#include "sa.h" +#include "mpmst.h" + +static Index pagesLength(SparseArray sa) +{ + return (sa->length * sa->elementSize + VMAlign(sa->vm) - 1) >> sa->shift; +} + +void SparseArrayInit(SparseArray sa, + void *base, Size elementSize, Index length, + BT mapped, BT pages, VM vm) +{ + AVER(sa != NULL); + + sa->base = base; + sa->elementSize = elementSize; + sa->length = length; + sa->mapped = mapped; + sa->pages = pages; + sa->vm = vm; + AVER(SizeIsP2(VMAlign(vm))); + sa->shift = SizeLog2(VMAlign(vm)); + BTResRange(mapped, 0, length); + BTResRange(pages, 0, pagesLength(sa)); + + sa->sig = SparseArraySig; + AVERT(SparseArray, sa); +} + +void SparseArrayFinish(SparseArray sa) +{ + AVERT(SparseArray, sa); + AVER(BTIsResRange(sa->mapped, 0, sa->length)); + AVER(BTIsResRange(sa->pages, 0, pagesLength(sa))); + sa->sig = SigInvalid; +} + +Bool SparseArrayCheck(SparseArray sa) +{ + CHECKL(sa != NULL); + CHECKL(sa->sig == SparseArraySig); + CHECKL(sa->base != NULL); + CHECKL(sa->elementSize >= 1); + CHECKL(VMCheck(sa->vm)); /* TODO: CHECKD(VM, sa->vm); */ + CHECKL(sa->elementSize <= VMAlign(sa->vm)); + CHECKL(sa->length > 0); + CHECKL(BTCheck(sa->mapped)); + CHECKL(BTCheck(sa->pages)); + CHECKL(sa->shift == SizeLog2(VMAlign(sa->vm))); + return TRUE; +} + + +/* SparseArrayMap -- map memory for a range of elements in the array + * + * Ensures that the array elements in the unmapped range [baseEI, limitEI) + * have memory. The array elements may then be accessed, but their contents + * will be undefined. + * + * In the MPS we expect this to be called frequently when allocating in + * the arena, and so it's worth having the pages bit table to make this + * fast. Compare with SparseArrayUnmap. + */ + +Res SparseArrayMap(SparseArray sa, Index baseEI, Index limitEI) +{ + Index baseMI, limitMI; + + AVERT(SparseArray, sa); + AVER(NONNEGATIVE(baseEI)); + AVER(baseEI < limitEI); + AVER(limitEI <= sa->length); + AVER(BTIsResRange(sa->mapped, baseEI, limitEI)); + + /* Calculate the index of the page on which the base element resides. + If that's already mapped (because some other element below baseEI + is defined) bump up to the next page. */ + baseMI = (baseEI * sa->elementSize) >> sa->shift; + if (BTGet(sa->pages, baseMI)) + ++baseMI; + + /* Calculate the index of the page on which the last element resides. + If that's already mapped (because some other element not below + limitEI is defined) bump down to the previous page. */ + limitMI = ((limitEI * sa->elementSize - 1) >> sa->shift) + 1; + if (BTGet(sa->pages, limitMI - 1)) + --limitMI; + + if (baseMI < limitMI) { + Addr base, limit; + Res res; + AVER(BTIsResRange(sa->pages, baseMI, limitMI)); + base = AddrAdd(sa->base, baseMI << sa->shift); + limit = AddrAdd(sa->base, limitMI << sa->shift); + res = VMMap(sa->vm, base, limit); + if (res != ResOK) + return res; + BTSetRange(sa->pages, baseMI, limitMI); + } + + BTSetRange(sa->mapped, baseEI, limitEI); + + return ResOK; +} + + +/* SparseArrayUnmap -- unmap memory for a range of elements in the array + * + * Declare that the array elements in the range [baseEI, limitEI) can be + * unmapped. After this call they may not be accessed. + * + * In the MPS we expect this to be called infrequently when purging large + * numbers of spare pages at once, so scanning a range of bits to determine + * whether we can unmap isn't too bad. + * + * TODO: Consider keeping a count of the number of array elements defined + * on each page, rather than a bit table, then we can unmap pages with + * zero counts rather than scanning. + */ + +void SparseArrayUnmap(SparseArray sa, Index baseEI, Index limitEI) +{ + Index baseMI, limitMI, i; + + AVERT(SparseArray, sa); + AVER(NONNEGATIVE(baseEI)); + AVER(baseEI < limitEI); + AVER(limitEI <= sa->length); + AVER(BTIsSetRange(sa->mapped, baseEI, limitEI)); + + /* Calculate the index of the lowest element that might be occupying + the page on which the base element resides. If any elements between + there and baseMI are defined, we can't unmap that page, so bump up. */ + baseMI = (baseEI * sa->elementSize) >> sa->shift; + i = SizeAlignDown(baseEI * sa->elementSize, VMAlign(sa->vm)) / sa->elementSize; + if (i < baseEI && !BTIsResRange(sa->mapped, i, baseEI)) + ++baseMI; + + /* Calculate the index of the highest element that might be occupying + the page on which the last element resides. If any elements between + limitMI and there are defined, we can't unmap that page, so bump down. */ + limitMI = ((limitEI * sa->elementSize - 1) >> sa->shift) + 1; + i = (SizeAlignUp(limitEI * sa->elementSize, VMAlign(sa->vm)) + + sa->elementSize - 1) / sa->elementSize; + if (i > sa->length) + i = sa->length; + if (i > limitEI && !BTIsResRange(sa->mapped, limitEI, i)) + --limitMI; + + if (baseMI < limitMI) { + Addr base, limit; + AVER(BTIsSetRange(sa->pages, baseMI, limitMI)); + base = AddrAdd(sa->base, baseMI << sa->shift); + limit = AddrAdd(sa->base, limitMI << sa->shift); + VMUnmap(sa->vm, base, limit); + BTResRange(sa->pages, baseMI, limitMI); + } + + BTResRange(sa->mapped, baseEI, limitEI); +} + + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2014 Ravenbrook Limited . + * All rights reserved. This is an open source license. Contact + * Ravenbrook for commercial licensing options. + * + * 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. + * + * 3. Redistributions in any form must be accompanied by information on how + * to obtain complete source code for this software and any accompanying + * software that uses this software. The source code must either be + * included in the distribution or be available for no more than the cost + * of distribution plus a nominal fee, and must be freely redistributable + * under reasonable conditions. For an executable file, complete source + * code means the source code for all modules it contains. It does not + * include source code for modules or files that typically accompany the + * major components of the operating system on which the executable file + * runs. + * + * 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, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/mps/code/sa.h b/mps/code/sa.h new file mode 100644 index 00000000000..40aa133bbf2 --- /dev/null +++ b/mps/code/sa.h @@ -0,0 +1,89 @@ +/* sa.h: SPARSE ARRAY INTERFACE + * + * $Id$ + * Copyright (c) 2014 Ravenbrook Limited. See end of file for license. + * + * A sparse array is an array whose storage is partially mapped from a VM. + * Each element in the array is its own "mapped" status, and may only + * be used if it is mapped. + * + * The main use of sparse arrays is partially mapped page tables in the + * VM arena, where they provide a fast lookup from an address within + * a chunk to a page descriptor, while avoiding mapping memory for + * page descriptors for unused areas of address space, such as unused + * zone stripes or gaps between those stripes. + */ + +#ifndef sa_h +#define sa_h + +#include "mpmtypes.h" + +typedef struct SparseArrayStruct *SparseArray; + +#define SparseArraySig ((Sig)0x5195BA66) /* SIGnature SParse ARRAy */ + +typedef struct SparseArrayStruct { + Sig sig; + void *base; /* base of array, page aligned */ + Size elementSize; /* size of array elements, <= page size */ + Index length; /* number of elements in the array */ + BT mapped; /* whether elements exist in the array */ + BT pages; /* whether underlying pages are mapped */ + VM vm; /* where pages are mapped from */ + Shift shift; /* SizeLog2(VMAlign(vm)) TODO: VMShift(vm) */ +} SparseArrayStruct; + +extern void SparseArrayInit(SparseArray sa, + void *base, Size elementSize, Index length, + BT defined, BT mapped, VM vm); +extern void SparseArrayFinish(SparseArray sa); +extern Bool SparseArrayCheck(SparseArray sa); + +#define SparseArrayIsMapped(sa, i) BTGet((sa)->mapped, i) + +extern Res SparseArrayMap(SparseArray sa, Index baseEI, Index limitEI); +extern void SparseArrayUnmap(SparseArray sa, Index baseEI, Index limitEI); + +#endif /* sa_h */ + +/* C. COPYRIGHT AND LICENSE + * + * Copyright (C) 2014 Ravenbrook Limited . + * All rights reserved. This is an open source license. Contact + * Ravenbrook for commercial licensing options. + * + * 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. + * + * 3. Redistributions in any form must be accompanied by information on how + * to obtain complete source code for this software and any accompanying + * software that uses this software. The source code must either be + * included in the distribution or be available for no more than the cost + * of distribution plus a nominal fee, and must be freely redistributable + * under reasonable conditions. For an executable file, complete source + * code means the source code for all modules it contains. It does not + * include source code for modules or files that typically accompany the + * major components of the operating system on which the executable file + * runs. + * + * 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, FITNESS FOR A PARTICULAR + * PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ diff --git a/mps/code/tract.c b/mps/code/tract.c index e89424442a2..5e3cad6875f 100644 --- a/mps/code/tract.c +++ b/mps/code/tract.c @@ -159,7 +159,6 @@ Res ChunkInit(Chunk chunk, Arena arena, { Size size; Count pages; - Page pageTable; Shift pageShift; Size pageTableSize; void *p; @@ -199,13 +198,6 @@ Res ChunkInit(Chunk chunk, Arena arena, if (res != ResOK) goto failClassInit; - /* Put the page table as late as possible, as in VM systems we don't want */ - /* to map it. */ - res = BootAlloc(&p, boot, (size_t)pageTableSize, (size_t)pageSize); - if (res != ResOK) - goto failAllocPageTable; - chunk->pageTable = pageTable = p; - /* @@@@ Is BootAllocated always right? */ /* Last thing we BootAlloc'd is pageTable. We requested pageSize */ /* alignment, and pageTableSize is itself pageSize aligned, so */ @@ -221,8 +213,6 @@ Res ChunkInit(Chunk chunk, Arena arena, return ResOK; /* .no-clean: No clean-ups needed for boot, as we will discard the chunk. */ -failAllocPageTable: - (arena->class->chunkFinish)(chunk); failClassInit: failAllocTable: return res;