| /** | |
| Functions to manipulate metadata in-block. | |
| functionality was moved from rt.lifetime | |
| */ | |
| module core.internal.gc.blockmeta; | |
| import core.memory; | |
| alias BlkInfo = GC.BlkInfo; | |
| alias BlkAttr = GC.BlkAttr; | |
| enum : size_t | |
| { | |
| PAGESIZE = 4096, | |
| BIGLENGTHMASK = ~(PAGESIZE - 1), | |
| SMALLPAD = 1, | |
| MEDPAD = ushort.sizeof, | |
| LARGEPREFIX = 16, // 16 bytes padding at the front of the array | |
| LARGEPAD = LARGEPREFIX + 1, | |
| MAXSMALLSIZE = 256-SMALLPAD, | |
| MAXMEDSIZE = (PAGESIZE / 2) - MEDPAD | |
| } | |
| // size used to store the TypeInfo at the end of an allocation for structs that have a destructor | |
| size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc | |
| { | |
| if (ti && typeid(ti) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast | |
| { | |
| auto sti = cast(TypeInfo_Struct)cast(void*)ti; | |
| if (sti.xdtor) | |
| return size_t.sizeof; | |
| } | |
| return 0; | |
| } | |
| /** | |
| Set the allocated length of the array block. This is called | |
| any time an array is appended to or its length is set. | |
| The allocated block looks like this for blocks < PAGESIZE: | |
| |elem0|elem1|elem2|...|elemN-1|emptyspace|N*elemsize| | |
| The size of the allocated length at the end depends on the block size: | |
| a block of 16 to 256 bytes has an 8-bit length. | |
| a block with 512 to pagesize/2 bytes has a 16-bit length. | |
| For blocks >= pagesize, the length is a size_t and is at the beginning of the | |
| block. The reason we have to do this is because the block can extend into | |
| more pages, so we cannot trust the block length if it sits at the end of the | |
| block, because it might have just been extended. If we can prove in the | |
| future that the block is unshared, we may be able to change this, but I'm not | |
| sure it's important. | |
| In order to do put the length at the front, we have to provide 16 bytes | |
| buffer space in case the block has to be aligned properly. In x86, certain | |
| SSE instructions will only work if the data is 16-byte aligned. In addition, | |
| we need the sentinel byte to prevent accidental pointers to the next block. | |
| Because of the extra overhead, we only do this for page size and above, where | |
| the overhead is minimal compared to the block size. | |
| So for those blocks, it looks like: | |
| |N*elemsize|padding|elem0|elem1|...|elemN-1|emptyspace|sentinelbyte| | |
| where elem0 starts 16 bytes after the first byte. | |
| */ | |
| bool __setArrayAllocLength(ref BlkInfo info, size_t newlength, bool isshared, const TypeInfo tinext, size_t oldlength = size_t.max) pure nothrow | |
| { | |
| __setBlockFinalizerInfo(info, tinext); | |
| size_t typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0; | |
| return __setArrayAllocLengthImpl(info, newlength, isshared, oldlength, typeInfoSize); | |
| } | |
| // the impl function, used both above and in core.internal.array.utils | |
| bool __setArrayAllocLengthImpl(ref BlkInfo info, size_t newlength, bool isshared, size_t oldlength, size_t typeInfoSize) pure nothrow | |
| { | |
| import core.atomic; | |
| if (info.size <= 256) | |
| { | |
| import core.checkedint; | |
| bool overflow; | |
| auto newlength_padded = addu(newlength, | |
| addu(SMALLPAD, typeInfoSize, overflow), | |
| overflow); | |
| if (newlength_padded > info.size || overflow) | |
| // new size does not fit inside block | |
| return false; | |
| auto length = cast(ubyte *)(info.base + info.size - typeInfoSize - SMALLPAD); | |
| if (oldlength != size_t.max) | |
| { | |
| if (isshared) | |
| { | |
| return cas(cast(shared)length, cast(ubyte)oldlength, cast(ubyte)newlength); | |
| } | |
| else | |
| { | |
| if (*length == cast(ubyte)oldlength) | |
| *length = cast(ubyte)newlength; | |
| else | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| // setting the initial length, no cas needed | |
| *length = cast(ubyte)newlength; | |
| } | |
| } | |
| else if (info.size <= PAGESIZE / 2) | |
| { | |
| if (newlength + MEDPAD + typeInfoSize > info.size) | |
| // new size does not fit inside block | |
| return false; | |
| auto length = cast(ushort *)(info.base + info.size - typeInfoSize - MEDPAD); | |
| if (oldlength != size_t.max) | |
| { | |
| if (isshared) | |
| { | |
| return cas(cast(shared)length, cast(ushort)oldlength, cast(ushort)newlength); | |
| } | |
| else | |
| { | |
| if (*length == oldlength) | |
| *length = cast(ushort)newlength; | |
| else | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| // setting the initial length, no cas needed | |
| *length = cast(ushort)newlength; | |
| } | |
| } | |
| else | |
| { | |
| if (newlength + LARGEPAD > info.size) | |
| // new size does not fit inside block | |
| return false; | |
| auto length = cast(size_t *)(info.base); | |
| if (oldlength != size_t.max) | |
| { | |
| if (isshared) | |
| { | |
| return cas(cast(shared)length, cast(size_t)oldlength, cast(size_t)newlength); | |
| } | |
| else | |
| { | |
| if (*length == oldlength) | |
| *length = newlength; | |
| else | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| // setting the initial length, no cas needed | |
| *length = newlength; | |
| } | |
| } | |
| return true; // resize succeeded | |
| } | |
| /** | |
| The block finalizer info is set separately from the array length, as that is | |
| only needed on the initial setup of the block. No shared is needed, since | |
| this should only happen when the block is new. | |
| If the STRUCTFINAL bit is not set, no finalizer is stored (but if needed the | |
| slot is zeroed) | |
| */ | |
| void __setBlockFinalizerInfo(ref BlkInfo info, const TypeInfo ti) pure nothrow | |
| { | |
| if ((info.attr & BlkAttr.APPENDABLE) && info.size > PAGESIZE / 2) | |
| { | |
| // if the structfinal bit is not set, we don't have a finalizer. But we | |
| // should still zero out the finalizer slot. | |
| auto context = (info.attr & BlkAttr.STRUCTFINAL) ? cast(void*)ti : null; | |
| // array used size goes at the beginning. We can stuff the typeinfo | |
| // right after it, as we need to use 16 bytes anyway. | |
| // | |
| auto typeInfo = cast(void**)info.base + 1; | |
| *typeInfo = context; | |
| version (D_LP64) {} else | |
| { | |
| // zero out the extra padding | |
| (cast(size_t*)info.base)[2 .. 4] = 0; | |
| } | |
| } | |
| else if(info.attr & BlkAttr.STRUCTFINAL) | |
| { | |
| // all other cases the typeinfo gets put at the end of the block | |
| auto typeInfo = cast(void**)(info.base + info.size) - 1; | |
| *typeInfo = cast(void*) ti; | |
| } | |
| } | |
| /** | |
| Get the finalizer info from the block (typeinfo). | |
| If called on a block, without STRUCTFINAL set, returns null. | |
| */ | |
| const(TypeInfo) __getBlockFinalizerInfo(ref BlkInfo info) pure nothrow | |
| { | |
| return __getBlockFinalizerInfo(info.base, info.size, info.attr); | |
| } | |
| /// ditto | |
| const(TypeInfo) __getBlockFinalizerInfo(void* base, size_t size, uint attr) pure nothrow | |
| { | |
| if (!(attr & BlkAttr.STRUCTFINAL)) | |
| return null; | |
| bool isLargeArray = (attr & BlkAttr.APPENDABLE) && size > PAGESIZE / 2; | |
| auto typeInfo = isLargeArray ? | |
| base + size_t.sizeof : | |
| base + size - size_t.sizeof; | |
| assert(*cast(size_t*)typeInfo != 0); | |
| return *cast(TypeInfo*)typeInfo; | |
| } | |
| /** | |
| get the used size of the array for the given block | |
| */ | |
| size_t __arrayAllocLength(ref BlkInfo info) pure nothrow | |
| in(info.attr & BlkAttr.APPENDABLE) | |
| { | |
| auto typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0; | |
| if (info.size <= 256) | |
| return *cast(ubyte *)(info.base + info.size - typeInfoSize - SMALLPAD); | |
| if (info.size <= PAGESIZE / 2) | |
| return *cast(ushort *)(info.base + info.size - typeInfoSize - MEDPAD); | |
| return *cast(size_t *)(info.base); | |
| } | |
| /** | |
| Atomically get the used size of the array for the given block | |
| */ | |
| size_t __arrayAllocLengthAtomic(ref BlkInfo info) pure nothrow | |
| in(info.attr & BlkAttr.APPENDABLE) | |
| { | |
| import core.atomic; | |
| auto typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0; | |
| if (info.size <= 256) | |
| return atomicLoad(*cast(shared(ubyte)*)(info.base + info.size - typeInfoSize - SMALLPAD)); | |
| if (info.size <= PAGESIZE / 2) | |
| return atomicLoad(*cast(shared(ushort)*)(info.base + info.size - typeInfoSize - MEDPAD)); | |
| return atomicLoad(*cast(shared(size_t)*)(info.base)); | |
| } | |
| /** | |
| Get the maximum bytes that can be stored in the given block. | |
| */ | |
| size_t __arrayAllocCapacity(ref BlkInfo info) pure nothrow | |
| in(info.attr & BlkAttr.APPENDABLE) | |
| { | |
| // Capacity is a calculation based solely on the block info. | |
| if (info.size > PAGESIZE / 2) | |
| return info.size - LARGEPAD; | |
| auto typeInfoSize = (info.attr & BlkAttr.STRUCTFINAL) ? size_t.sizeof : 0; | |
| auto padsize = info.size <= 256 ? SMALLPAD : MEDPAD; | |
| return info.size - typeInfoSize - padsize; | |
| } | |
| /** | |
| get the padding required to allocate size bytes. Note that the padding is | |
| NOT included in the passed in size. Therefore, do NOT call this function | |
| with the size of an allocated block. | |
| */ | |
| size_t __arrayPad(size_t size, const TypeInfo tinext) nothrow pure @trusted | |
| { | |
| return size > MAXMEDSIZE ? LARGEPAD : ((size > MAXSMALLSIZE ? MEDPAD : SMALLPAD) + structTypeInfoSize(tinext)); | |
| } | |
| /** | |
| get the padding required to allocate size bytes, use the bits to determine | |
| which metadata must be stored. | |
| */ | |
| size_t __allocPad(size_t size, uint bits) nothrow pure @trusted | |
| { | |
| auto finalizerSize = (bits & BlkAttr.STRUCTFINAL) ? (void*).sizeof : 0; | |
| if (bits & BlkAttr.APPENDABLE) | |
| { | |
| if (size > MAXMEDSIZE - finalizerSize) | |
| return LARGEPAD; | |
| auto pad = (size > MAXSMALLSIZE - finalizerSize) ? MEDPAD : SMALLPAD; | |
| return pad + finalizerSize; | |
| } | |
| return finalizerSize; | |
| } | |
| /** | |
| * Get the start of the array for the given block. | |
| * | |
| * Params: | |
| * info = array metadata | |
| * base = pointer to base of memory block | |
| * size = size of memory block. | |
| * Returns: | |
| * pointer to the start of the array | |
| */ | |
| void *__arrayStart()(return scope BlkInfo info) nothrow pure | |
| { | |
| return __arrayStart(info.base, info.size); | |
| } | |
| /// Ditto | |
| void *__arrayStart()(return scope void* base, size_t size) nothrow pure | |
| { | |
| return base + ((size & BIGLENGTHMASK) ? LARGEPREFIX : 0); | |
| } | |
| /** | |
| * Trim a block's extents to the known valid data that is not metadata. This | |
| * takes into account the finalizer and array metadata. | |
| */ | |
| void __trimExtents(ref scope void* base, ref size_t blockSize, uint attr) nothrow pure @nogc | |
| { | |
| if (attr & BlkAttr.APPENDABLE) | |
| { | |
| if (blockSize > PAGESIZE / 2) | |
| { | |
| // large block, it's always LARGEPREFIX bytes at the front. | |
| blockSize = *(cast(size_t*)base); | |
| base += LARGEPREFIX; | |
| return; | |
| } | |
| void *pend = base + blockSize; | |
| if (attr & BlkAttr.STRUCTFINAL) | |
| // skip the finalizer | |
| pend -= (void*).sizeof; | |
| // get the actual length | |
| if (blockSize <= 256) | |
| blockSize = *(cast(ubyte*)pend - 1); | |
| else // medium array block | |
| blockSize = *(cast(ushort*)pend - 1); | |
| } | |
| else if (attr & BlkAttr.STRUCTFINAL) | |
| { | |
| // not appendable, but has a finalizer in the block. This is always | |
| // stored at the end. | |
| blockSize -= (void*).sizeof; | |
| } | |
| } |