alloc.nim 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. # Low level allocator for Nim. Has been designed to support the GC.
  10. {.push profiler:off.}
  11. include osalloc
  12. import std/private/syslocks
  13. import std/sysatomics
  14. template track(op, address, size) =
  15. when defined(memTracker):
  16. memTrackerOp(op, address, size)
  17. # We manage *chunks* of memory. Each chunk is a multiple of the page size.
  18. # Each chunk starts at an address that is divisible by the page size.
  19. # Small chunks may be divided into smaller cells of reusable pointers to reduce the number of page allocations.
  20. # An allocation of a small pointer looks approximately like this
  21. #[
  22. alloc -> rawAlloc -> No free chunk available > Request a new page from tslf -> result = chunk.data -------------+
  23. | |
  24. v |
  25. Free chunk available |
  26. | |
  27. v v
  28. Fetch shared cells -> No free cells available -> Advance acc -> result = chunk.data + chunk.acc -------> return
  29. (may not add new cells) ^
  30. | |
  31. v |
  32. Free cells available -> result = chunk.freeList -> Advance chunk.freeList -----------------------------------+
  33. ]#
  34. # so it is split into 3 paths, where the last path is preferred to prevent unnecessary allocations.
  35. #
  36. #
  37. # A deallocation of a small pointer then looks like this
  38. #[
  39. dealloc -> rawDealloc -> chunk.owner == addr(a) --------------> This thread owns the chunk ------> The current chunk is active -> Chunk is completely unused -----> Chunk references no foreign cells
  40. | | (Add cell into the current chunk) | Return the current chunk back to tlsf
  41. | | | |
  42. v v v v
  43. A different thread owns this chunk. The current chunk is not active. chunk.free was < size Chunk references foreign cells, noop
  44. Add the cell to a.sharedFreeLists Add the cell into the active chunk Activate the chunk (end)
  45. (end) (end) (end)
  46. ]#
  47. # So "true" deallocation is delayed for as long as possible in favor of reusing cells.
  48. const
  49. nimMinHeapPages {.intdefine.} = 128 # 0.5 MB
  50. SmallChunkSize = PageSize
  51. MaxFli = when sizeof(int) > 2: 30 else: 14
  52. MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32'
  53. # everywhere!
  54. MaxSli = 1 shl MaxLog2Sli
  55. FliOffset = 6
  56. RealFli = MaxFli - FliOffset
  57. # size of chunks in last matrix bin
  58. MaxBigChunkSize = int(1'i32 shl MaxFli - 1'i32 shl (MaxFli-MaxLog2Sli-1))
  59. HugeChunkSize = MaxBigChunkSize + 1
  60. type
  61. PTrunk = ptr Trunk
  62. Trunk = object
  63. next: PTrunk # all nodes are connected with this pointer
  64. key: int # start address at bit 0
  65. bits: array[0..IntsPerTrunk-1, uint] # a bit vector
  66. TrunkBuckets = array[0..255, PTrunk]
  67. IntSet = object
  68. data: TrunkBuckets
  69. # ------------- chunk table ---------------------------------------------------
  70. # We use a PtrSet of chunk starts and a table[Page, chunksize] for chunk
  71. # endings of big chunks. This is needed by the merging operation. The only
  72. # remaining operation is best-fit for big chunks. Since there is a size-limit
  73. # for big chunks (because greater than the limit means they are returned back
  74. # to the OS), a fixed size array can be used.
  75. type
  76. PLLChunk = ptr LLChunk
  77. LLChunk = object ## *low-level* chunk
  78. size: int # remaining size
  79. acc: int # accumulator
  80. next: PLLChunk # next low-level chunk; only needed for dealloc
  81. PAvlNode = ptr AvlNode
  82. AvlNode = object
  83. link: array[0..1, PAvlNode] # Left (0) and right (1) links
  84. key, upperBound: int
  85. level: int
  86. const
  87. RegionHasLock = false # hasThreadSupport and defined(gcDestructors)
  88. type
  89. FreeCell {.final, pure.} = object
  90. # A free cell is a pointer that has been freed, meaning it became available for reuse.
  91. # It may become foreign if it is lent to a chunk that did not create it, doing so reduces the amount of needed pages.
  92. next: ptr FreeCell # next free cell in chunk (overlaid with refcount)
  93. when not defined(gcDestructors):
  94. zeroField: int # 0 means cell is not used (overlaid with typ field)
  95. # 1 means cell is manually managed pointer
  96. # otherwise a PNimType is stored in there
  97. else:
  98. alignment: int
  99. PChunk = ptr BaseChunk
  100. PBigChunk = ptr BigChunk
  101. PSmallChunk = ptr SmallChunk
  102. BaseChunk {.pure, inheritable.} = object
  103. prevSize: int # size of previous chunk; for coalescing
  104. # 0th bit == 1 if 'used
  105. size: int # if < PageSize it is a small chunk
  106. owner: ptr MemRegion
  107. SmallChunk = object of BaseChunk
  108. next, prev: PSmallChunk # chunks of the same size
  109. freeList: ptr FreeCell # Singly linked list of cells. They may be from foreign chunks or from the current chunk.
  110. # Should be `nil` when the chunk isn't active in `a.freeSmallChunks`.
  111. free: int32 # Bytes this chunk is able to provide using both the accumulator and free cells.
  112. # When a cell is considered foreign, its source chunk's free field is NOT adjusted until it
  113. # reaches dealloc while the source chunk is active.
  114. # Instead, the receiving chunk gains the capacity and thus reserves space in the foreign chunk.
  115. acc: uint32 # Offset from data, used when there are no free cells available but the chunk is considered free.
  116. foreignCells: int # When a free cell is given to a chunk that is not its origin,
  117. # both the cell and the source chunk are considered foreign.
  118. # Receiving a foreign cell can happen both when deallocating from another thread or when
  119. # the active chunk in `a.freeSmallChunks` is not the current chunk.
  120. # Freeing a chunk while `foreignCells > 0` leaks memory as all references to it become lost.
  121. data {.align: MemAlign.}: UncheckedArray[byte] # start of usable memory
  122. BigChunk = object of BaseChunk # not necessarily > PageSize!
  123. next, prev: PBigChunk # chunks of the same (or bigger) size
  124. data {.align: MemAlign.}: UncheckedArray[byte] # start of usable memory
  125. HeapLinks = object
  126. len: int
  127. chunks: array[30, (PBigChunk, int)]
  128. next: ptr HeapLinks
  129. MemRegion = object
  130. when not defined(gcDestructors):
  131. minLargeObj, maxLargeObj: int
  132. freeSmallChunks: array[0..max(1, SmallChunkSize div MemAlign-1), PSmallChunk]
  133. # List of available chunks per size class. Only one is expected to be active per class.
  134. when defined(gcDestructors):
  135. sharedFreeLists: array[0..max(1, SmallChunkSize div MemAlign-1), ptr FreeCell]
  136. # When a thread frees a pointer it did not create, it must not adjust the counters.
  137. # Instead, the cell is placed here and deferred until the next allocation.
  138. flBitmap: uint32
  139. slBitmap: array[RealFli, uint32]
  140. matrix: array[RealFli, array[MaxSli, PBigChunk]]
  141. llmem: PLLChunk
  142. currMem, maxMem, freeMem, occ: int # memory sizes (allocated from OS)
  143. lastSize: int # needed for the case that OS gives us pages linearly
  144. when RegionHasLock:
  145. lock: SysLock
  146. when defined(gcDestructors):
  147. sharedFreeListBigChunks: PBigChunk # make no attempt at avoiding false sharing for now for this object field
  148. chunkStarts: IntSet
  149. when not defined(gcDestructors):
  150. root, deleted, last, freeAvlNodes: PAvlNode
  151. lockActive, locked, blockChunkSizeIncrease: bool # if locked, we cannot free pages.
  152. nextChunkSize: int
  153. when not defined(gcDestructors):
  154. bottomData: AvlNode
  155. heapLinks: HeapLinks
  156. when defined(nimTypeNames):
  157. allocCounter, deallocCounter: int
  158. template smallChunkOverhead(): untyped = sizeof(SmallChunk)
  159. template bigChunkOverhead(): untyped = sizeof(BigChunk)
  160. when hasThreadSupport:
  161. template loada(x: untyped): untyped = atomicLoadN(unsafeAddr x, ATOMIC_RELAXED)
  162. template storea(x, y: untyped) = atomicStoreN(unsafeAddr x, y, ATOMIC_RELAXED)
  163. when false:
  164. # not yet required
  165. template atomicStatDec(x, diff: untyped) = discard atomicSubFetch(unsafeAddr x, diff, ATOMIC_RELAXED)
  166. template atomicStatInc(x, diff: untyped) = discard atomicAddFetch(unsafeAddr x, diff, ATOMIC_RELAXED)
  167. else:
  168. template loada(x: untyped): untyped = x
  169. template storea(x, y: untyped) = x = y
  170. template atomicStatDec(x, diff: untyped) = dec x, diff
  171. template atomicStatInc(x, diff: untyped) = inc x, diff
  172. const
  173. fsLookupTable: array[byte, int8] = [
  174. -1'i8, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
  175. 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
  176. 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
  177. 5, 5, 5, 5, 5, 5, 5, 5,
  178. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
  179. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
  180. 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
  181. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  182. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  183. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  184. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  185. 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
  186. 7, 7, 7, 7, 7, 7, 7, 7
  187. ]
  188. proc msbit(x: uint32): int {.inline.} =
  189. let a = if x <= 0xff_ff'u32:
  190. (if x <= 0xff: 0 else: 8)
  191. else:
  192. (if x <= 0xff_ff_ff'u32: 16 else: 24)
  193. result = int(fsLookupTable[byte(x shr a)]) + a
  194. proc lsbit(x: uint32): int {.inline.} =
  195. msbit(x and ((not x) + 1))
  196. proc setBit(nr: int; dest: var uint32) {.inline.} =
  197. dest = dest or (1u32 shl (nr and 0x1f))
  198. proc clearBit(nr: int; dest: var uint32) {.inline.} =
  199. dest = dest and not (1u32 shl (nr and 0x1f))
  200. proc mappingSearch(r, fl, sl: var int) {.inline.} =
  201. #let t = (1 shl (msbit(uint32 r) - MaxLog2Sli)) - 1
  202. # This diverges from the standard TLSF algorithm because we need to ensure
  203. # PageSize alignment:
  204. let t = roundup((1 shl (msbit(uint32 r) - MaxLog2Sli)), PageSize) - 1
  205. r = r + t
  206. r = r and not t
  207. r = min(r, MaxBigChunkSize).int
  208. fl = msbit(uint32 r)
  209. sl = (r shr (fl - MaxLog2Sli)) - MaxSli
  210. dec fl, FliOffset
  211. sysAssert((r and PageMask) == 0, "mappingSearch: still not aligned")
  212. # See http://www.gii.upv.es/tlsf/files/papers/tlsf_desc.pdf for details of
  213. # this algorithm.
  214. proc mappingInsert(r: int): tuple[fl, sl: int] {.inline.} =
  215. sysAssert((r and PageMask) == 0, "mappingInsert: still not aligned")
  216. result.fl = msbit(uint32 r)
  217. result.sl = (r shr (result.fl - MaxLog2Sli)) - MaxSli
  218. dec result.fl, FliOffset
  219. template mat(): untyped = a.matrix[fl][sl]
  220. proc findSuitableBlock(a: MemRegion; fl, sl: var int): PBigChunk {.inline.} =
  221. let tmp = a.slBitmap[fl] and (not 0u32 shl sl)
  222. result = nil
  223. if tmp != 0:
  224. sl = lsbit(tmp)
  225. result = mat()
  226. else:
  227. fl = lsbit(a.flBitmap and (not 0u32 shl (fl + 1)))
  228. if fl > 0:
  229. sl = lsbit(a.slBitmap[fl])
  230. result = mat()
  231. template clearBits(sl, fl) =
  232. clearBit(sl, a.slBitmap[fl])
  233. if a.slBitmap[fl] == 0u32:
  234. # do not forget to cascade:
  235. clearBit(fl, a.flBitmap)
  236. proc removeChunkFromMatrix(a: var MemRegion; b: PBigChunk) =
  237. let (fl, sl) = mappingInsert(b.size)
  238. if b.next != nil: b.next.prev = b.prev
  239. if b.prev != nil: b.prev.next = b.next
  240. if mat() == b:
  241. mat() = b.next
  242. if mat() == nil:
  243. clearBits(sl, fl)
  244. b.prev = nil
  245. b.next = nil
  246. proc removeChunkFromMatrix2(a: var MemRegion; b: PBigChunk; fl, sl: int) =
  247. mat() = b.next
  248. if mat() != nil:
  249. mat().prev = nil
  250. else:
  251. clearBits(sl, fl)
  252. b.prev = nil
  253. b.next = nil
  254. proc addChunkToMatrix(a: var MemRegion; b: PBigChunk) =
  255. let (fl, sl) = mappingInsert(b.size)
  256. b.prev = nil
  257. b.next = mat()
  258. if mat() != nil:
  259. mat().prev = b
  260. mat() = b
  261. setBit(sl, a.slBitmap[fl])
  262. setBit(fl, a.flBitmap)
  263. proc incCurrMem(a: var MemRegion, bytes: int) {.inline.} =
  264. atomicStatInc(a.currMem, bytes)
  265. proc decCurrMem(a: var MemRegion, bytes: int) {.inline.} =
  266. a.maxMem = max(a.maxMem, a.currMem)
  267. atomicStatDec(a.currMem, bytes)
  268. proc getMaxMem(a: var MemRegion): int =
  269. # Since we update maxPagesCount only when freeing pages,
  270. # maxPagesCount may not be up to date. Thus we use the
  271. # maximum of these both values here:
  272. result = max(a.currMem, a.maxMem)
  273. const nimMaxHeap {.intdefine.} = 0
  274. proc allocPages(a: var MemRegion, size: int): pointer =
  275. when nimMaxHeap != 0:
  276. if a.occ + size > nimMaxHeap * 1024 * 1024:
  277. raiseOutOfMem()
  278. osAllocPages(size)
  279. proc tryAllocPages(a: var MemRegion, size: int): pointer =
  280. when nimMaxHeap != 0:
  281. if a.occ + size > nimMaxHeap * 1024 * 1024:
  282. raiseOutOfMem()
  283. osTryAllocPages(size)
  284. proc llAlloc(a: var MemRegion, size: int): pointer =
  285. # *low-level* alloc for the memory managers data structures. Deallocation
  286. # is done at the end of the allocator's life time.
  287. if a.llmem == nil or size > a.llmem.size:
  288. # the requested size is ``roundup(size+sizeof(LLChunk), PageSize)``, but
  289. # since we know ``size`` is a (small) constant, we know the requested size
  290. # is one page:
  291. sysAssert roundup(size+sizeof(LLChunk), PageSize) == PageSize, "roundup 6"
  292. var old = a.llmem # can be nil and is correct with nil
  293. a.llmem = cast[PLLChunk](allocPages(a, PageSize))
  294. when defined(nimAvlcorruption):
  295. trackLocation(a.llmem, PageSize)
  296. incCurrMem(a, PageSize)
  297. a.llmem.size = PageSize - sizeof(LLChunk)
  298. a.llmem.acc = sizeof(LLChunk)
  299. a.llmem.next = old
  300. result = cast[pointer](cast[int](a.llmem) + a.llmem.acc)
  301. dec(a.llmem.size, size)
  302. inc(a.llmem.acc, size)
  303. zeroMem(result, size)
  304. when not defined(gcDestructors):
  305. proc getBottom(a: var MemRegion): PAvlNode =
  306. result = addr(a.bottomData)
  307. if result.link[0] == nil:
  308. result.link[0] = result
  309. result.link[1] = result
  310. proc allocAvlNode(a: var MemRegion, key, upperBound: int): PAvlNode =
  311. if a.freeAvlNodes != nil:
  312. result = a.freeAvlNodes
  313. a.freeAvlNodes = a.freeAvlNodes.link[0]
  314. else:
  315. result = cast[PAvlNode](llAlloc(a, sizeof(AvlNode)))
  316. when defined(nimAvlcorruption):
  317. cprintf("tracking location: %p\n", result)
  318. result.key = key
  319. result.upperBound = upperBound
  320. let bottom = getBottom(a)
  321. result.link[0] = bottom
  322. result.link[1] = bottom
  323. result.level = 1
  324. #when defined(nimAvlcorruption):
  325. # track("allocAvlNode", result, sizeof(AvlNode))
  326. sysAssert(bottom == addr(a.bottomData), "bottom data")
  327. sysAssert(bottom.link[0] == bottom, "bottom link[0]")
  328. sysAssert(bottom.link[1] == bottom, "bottom link[1]")
  329. proc deallocAvlNode(a: var MemRegion, n: PAvlNode) {.inline.} =
  330. n.link[0] = a.freeAvlNodes
  331. a.freeAvlNodes = n
  332. proc addHeapLink(a: var MemRegion; p: PBigChunk, size: int): ptr HeapLinks =
  333. var it = addr(a.heapLinks)
  334. while it != nil and it.len >= it.chunks.len: it = it.next
  335. if it == nil:
  336. var n = cast[ptr HeapLinks](llAlloc(a, sizeof(HeapLinks)))
  337. n.next = a.heapLinks.next
  338. a.heapLinks.next = n
  339. n.chunks[0] = (p, size)
  340. n.len = 1
  341. result = n
  342. else:
  343. let L = it.len
  344. it.chunks[L] = (p, size)
  345. inc it.len
  346. result = it
  347. when not defined(gcDestructors):
  348. include "system/avltree"
  349. proc llDeallocAll(a: var MemRegion) =
  350. var it = a.llmem
  351. while it != nil:
  352. # we know each block in the list has the size of 1 page:
  353. var next = it.next
  354. osDeallocPages(it, PageSize)
  355. it = next
  356. a.llmem = nil
  357. proc intSetGet(t: IntSet, key: int): PTrunk =
  358. var it = t.data[key and high(t.data)]
  359. while it != nil:
  360. if it.key == key: return it
  361. it = it.next
  362. result = nil
  363. proc intSetPut(a: var MemRegion, t: var IntSet, key: int): PTrunk =
  364. result = intSetGet(t, key)
  365. if result == nil:
  366. result = cast[PTrunk](llAlloc(a, sizeof(result[])))
  367. result.next = t.data[key and high(t.data)]
  368. t.data[key and high(t.data)] = result
  369. result.key = key
  370. proc contains(s: IntSet, key: int): bool =
  371. var t = intSetGet(s, key shr TrunkShift)
  372. if t != nil:
  373. var u = key and TrunkMask
  374. result = (t.bits[u shr IntShift] and (uint(1) shl (u and IntMask))) != 0
  375. else:
  376. result = false
  377. proc incl(a: var MemRegion, s: var IntSet, key: int) =
  378. var t = intSetPut(a, s, key shr TrunkShift)
  379. var u = key and TrunkMask
  380. t.bits[u shr IntShift] = t.bits[u shr IntShift] or (uint(1) shl (u and IntMask))
  381. proc excl(s: var IntSet, key: int) =
  382. var t = intSetGet(s, key shr TrunkShift)
  383. if t != nil:
  384. var u = key and TrunkMask
  385. t.bits[u shr IntShift] = t.bits[u shr IntShift] and not
  386. (uint(1) shl (u and IntMask))
  387. iterator elements(t: IntSet): int {.inline.} =
  388. # while traversing it is forbidden to change the set!
  389. for h in 0..high(t.data):
  390. var r = t.data[h]
  391. while r != nil:
  392. var i = 0
  393. while i <= high(r.bits):
  394. var w = r.bits[i] # taking a copy of r.bits[i] here is correct, because
  395. # modifying operations are not allowed during traversation
  396. var j = 0
  397. while w != 0: # test all remaining bits for zero
  398. if (w and 1) != 0: # the bit is set!
  399. yield (r.key shl TrunkShift) or (i shl IntShift +% j)
  400. inc(j)
  401. w = w shr 1
  402. inc(i)
  403. r = r.next
  404. proc isSmallChunk(c: PChunk): bool {.inline.} =
  405. result = c.size <= SmallChunkSize-smallChunkOverhead()
  406. proc chunkUnused(c: PChunk): bool {.inline.} =
  407. result = (c.prevSize and 1) == 0
  408. iterator allObjects(m: var MemRegion): pointer {.inline.} =
  409. m.locked = true
  410. for s in elements(m.chunkStarts):
  411. # we need to check here again as it could have been modified:
  412. if s in m.chunkStarts:
  413. let c = cast[PChunk](s shl PageShift)
  414. if not chunkUnused(c):
  415. if isSmallChunk(c):
  416. var c = cast[PSmallChunk](c)
  417. let size = c.size
  418. var a = cast[int](addr(c.data))
  419. let limit = a + c.acc.int
  420. while a <% limit:
  421. yield cast[pointer](a)
  422. a = a +% size
  423. else:
  424. let c = cast[PBigChunk](c)
  425. yield addr(c.data)
  426. m.locked = false
  427. proc iterToProc*(iter: typed, envType: typedesc; procName: untyped) {.
  428. magic: "Plugin", compileTime.}
  429. when not defined(gcDestructors):
  430. proc isCell(p: pointer): bool {.inline.} =
  431. result = cast[ptr FreeCell](p).zeroField >% 1
  432. # ------------- chunk management ----------------------------------------------
  433. proc pageIndex(c: PChunk): int {.inline.} =
  434. result = cast[int](c) shr PageShift
  435. proc pageIndex(p: pointer): int {.inline.} =
  436. result = cast[int](p) shr PageShift
  437. proc pageAddr(p: pointer): PChunk {.inline.} =
  438. result = cast[PChunk](cast[int](p) and not PageMask)
  439. #sysAssert(Contains(allocator.chunkStarts, pageIndex(result)))
  440. when false:
  441. proc writeFreeList(a: MemRegion) =
  442. var it = a.freeChunksList
  443. c_fprintf(stdout, "freeChunksList: %p\n", it)
  444. while it != nil:
  445. c_fprintf(stdout, "it: %p, next: %p, prev: %p, size: %ld\n",
  446. it, it.next, it.prev, it.size)
  447. it = it.next
  448. proc requestOsChunks(a: var MemRegion, size: int): PBigChunk =
  449. when not defined(emscripten):
  450. if not a.blockChunkSizeIncrease:
  451. let usedMem = a.occ #a.currMem # - a.freeMem
  452. if usedMem < 64 * 1024:
  453. a.nextChunkSize = PageSize*4
  454. else:
  455. a.nextChunkSize = min(roundup(usedMem shr 2, PageSize), a.nextChunkSize * 2)
  456. a.nextChunkSize = min(a.nextChunkSize, MaxBigChunkSize).int
  457. var size = size
  458. if size > a.nextChunkSize:
  459. result = cast[PBigChunk](allocPages(a, size))
  460. else:
  461. result = cast[PBigChunk](tryAllocPages(a, a.nextChunkSize))
  462. if result == nil:
  463. result = cast[PBigChunk](allocPages(a, size))
  464. a.blockChunkSizeIncrease = true
  465. else:
  466. size = a.nextChunkSize
  467. incCurrMem(a, size)
  468. inc(a.freeMem, size)
  469. let heapLink = a.addHeapLink(result, size)
  470. when defined(debugHeapLinks):
  471. cprintf("owner: %p; result: %p; next pointer %p; size: %ld\n", addr(a),
  472. result, heapLink, size)
  473. when defined(memtracker):
  474. trackLocation(addr result.size, sizeof(int))
  475. sysAssert((cast[int](result) and PageMask) == 0, "requestOsChunks 1")
  476. #zeroMem(result, size)
  477. result.next = nil
  478. result.prev = nil
  479. result.size = size
  480. # update next.prevSize:
  481. var nxt = cast[int](result) +% size
  482. sysAssert((nxt and PageMask) == 0, "requestOsChunks 2")
  483. var next = cast[PChunk](nxt)
  484. if pageIndex(next) in a.chunkStarts:
  485. #echo("Next already allocated!")
  486. next.prevSize = size or (next.prevSize and 1)
  487. # set result.prevSize:
  488. var lastSize = if a.lastSize != 0: a.lastSize else: PageSize
  489. var prv = cast[int](result) -% lastSize
  490. sysAssert((nxt and PageMask) == 0, "requestOsChunks 3")
  491. var prev = cast[PChunk](prv)
  492. if pageIndex(prev) in a.chunkStarts and prev.size == lastSize:
  493. #echo("Prev already allocated!")
  494. result.prevSize = lastSize or (result.prevSize and 1)
  495. else:
  496. result.prevSize = 0 or (result.prevSize and 1) # unknown
  497. # but do not overwrite 'used' field
  498. a.lastSize = size # for next request
  499. sysAssert((cast[int](result) and PageMask) == 0, "requestOschunks: unaligned chunk")
  500. proc isAccessible(a: MemRegion, p: pointer): bool {.inline.} =
  501. result = contains(a.chunkStarts, pageIndex(p))
  502. proc contains[T](list, x: T): bool =
  503. result = false
  504. var it = list
  505. while it != nil:
  506. if it == x: return true
  507. it = it.next
  508. proc listAdd[T](head: var T, c: T) {.inline.} =
  509. sysAssert(c notin head, "listAdd 1")
  510. sysAssert c.prev == nil, "listAdd 2"
  511. sysAssert c.next == nil, "listAdd 3"
  512. c.next = head
  513. if head != nil:
  514. sysAssert head.prev == nil, "listAdd 4"
  515. head.prev = c
  516. head = c
  517. proc listRemove[T](head: var T, c: T) {.inline.} =
  518. sysAssert(c in head, "listRemove")
  519. if c == head:
  520. head = c.next
  521. sysAssert c.prev == nil, "listRemove 2"
  522. if head != nil: head.prev = nil
  523. else:
  524. sysAssert c.prev != nil, "listRemove 3"
  525. c.prev.next = c.next
  526. if c.next != nil: c.next.prev = c.prev
  527. c.next = nil
  528. c.prev = nil
  529. proc updatePrevSize(a: var MemRegion, c: PBigChunk,
  530. prevSize: int) {.inline.} =
  531. var ri = cast[PChunk](cast[int](c) +% c.size)
  532. sysAssert((cast[int](ri) and PageMask) == 0, "updatePrevSize")
  533. if isAccessible(a, ri):
  534. ri.prevSize = prevSize or (ri.prevSize and 1)
  535. proc splitChunk2(a: var MemRegion, c: PBigChunk, size: int): PBigChunk =
  536. result = cast[PBigChunk](cast[int](c) +% size)
  537. result.size = c.size - size
  538. track("result.size", addr result.size, sizeof(int))
  539. when not defined(nimOptimizedSplitChunk):
  540. # still active because of weird codegen issue on some of our CIs:
  541. result.next = nil
  542. result.prev = nil
  543. # size and not used:
  544. result.prevSize = size
  545. result.owner = addr a
  546. sysAssert((size and 1) == 0, "splitChunk 2")
  547. sysAssert((size and PageMask) == 0,
  548. "splitChunk: size is not a multiple of the PageSize")
  549. updatePrevSize(a, c, result.size)
  550. c.size = size
  551. incl(a, a.chunkStarts, pageIndex(result))
  552. proc splitChunk(a: var MemRegion, c: PBigChunk, size: int) =
  553. let rest = splitChunk2(a, c, size)
  554. addChunkToMatrix(a, rest)
  555. proc freeBigChunk(a: var MemRegion, c: PBigChunk) =
  556. var c = c
  557. sysAssert(c.size >= PageSize, "freeBigChunk")
  558. inc(a.freeMem, c.size)
  559. c.prevSize = c.prevSize and not 1 # set 'used' to false
  560. when coalescLeft:
  561. let prevSize = c.prevSize
  562. if prevSize != 0:
  563. var le = cast[PChunk](cast[int](c) -% prevSize)
  564. sysAssert((cast[int](le) and PageMask) == 0, "freeBigChunk 4")
  565. if isAccessible(a, le) and chunkUnused(le):
  566. sysAssert(not isSmallChunk(le), "freeBigChunk 5")
  567. if not isSmallChunk(le) and le.size < MaxBigChunkSize:
  568. removeChunkFromMatrix(a, cast[PBigChunk](le))
  569. inc(le.size, c.size)
  570. excl(a.chunkStarts, pageIndex(c))
  571. c = cast[PBigChunk](le)
  572. if c.size > MaxBigChunkSize:
  573. let rest = splitChunk2(a, c, MaxBigChunkSize)
  574. when defined(nimOptimizedSplitChunk):
  575. rest.next = nil
  576. rest.prev = nil
  577. addChunkToMatrix(a, c)
  578. c = rest
  579. when coalescRight:
  580. var ri = cast[PChunk](cast[int](c) +% c.size)
  581. sysAssert((cast[int](ri) and PageMask) == 0, "freeBigChunk 2")
  582. if isAccessible(a, ri) and chunkUnused(ri):
  583. sysAssert(not isSmallChunk(ri), "freeBigChunk 3")
  584. if not isSmallChunk(ri) and c.size < MaxBigChunkSize:
  585. removeChunkFromMatrix(a, cast[PBigChunk](ri))
  586. inc(c.size, ri.size)
  587. excl(a.chunkStarts, pageIndex(ri))
  588. if c.size > MaxBigChunkSize:
  589. let rest = splitChunk2(a, c, MaxBigChunkSize)
  590. addChunkToMatrix(a, rest)
  591. addChunkToMatrix(a, c)
  592. proc getBigChunk(a: var MemRegion, size: int): PBigChunk =
  593. sysAssert(size > 0, "getBigChunk 2")
  594. var size = size # roundup(size, PageSize)
  595. var fl = 0
  596. var sl = 0
  597. mappingSearch(size, fl, sl)
  598. sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk")
  599. result = findSuitableBlock(a, fl, sl)
  600. when RegionHasLock:
  601. if not a.lockActive:
  602. a.lockActive = true
  603. initSysLock(a.lock)
  604. acquireSys a.lock
  605. if result == nil:
  606. if size < nimMinHeapPages * PageSize:
  607. result = requestOsChunks(a, nimMinHeapPages * PageSize)
  608. splitChunk(a, result, size)
  609. else:
  610. result = requestOsChunks(a, size)
  611. # if we over allocated split the chunk:
  612. if result.size > size:
  613. splitChunk(a, result, size)
  614. result.owner = addr a
  615. else:
  616. removeChunkFromMatrix2(a, result, fl, sl)
  617. if result.size >= size + PageSize:
  618. splitChunk(a, result, size)
  619. # set 'used' to to true:
  620. result.prevSize = 1
  621. track("setUsedToFalse", addr result.size, sizeof(int))
  622. sysAssert result.owner == addr a, "getBigChunk: No owner set!"
  623. incl(a, a.chunkStarts, pageIndex(result))
  624. dec(a.freeMem, size)
  625. when RegionHasLock:
  626. releaseSys a.lock
  627. proc getHugeChunk(a: var MemRegion; size: int): PBigChunk =
  628. result = cast[PBigChunk](allocPages(a, size))
  629. when RegionHasLock:
  630. if not a.lockActive:
  631. a.lockActive = true
  632. initSysLock(a.lock)
  633. acquireSys a.lock
  634. incCurrMem(a, size)
  635. # XXX add this to the heap links. But also remove it from it later.
  636. when false: a.addHeapLink(result, size)
  637. sysAssert((cast[int](result) and PageMask) == 0, "getHugeChunk")
  638. result.next = nil
  639. result.prev = nil
  640. result.size = size
  641. # set 'used' to to true:
  642. result.prevSize = 1
  643. result.owner = addr a
  644. incl(a, a.chunkStarts, pageIndex(result))
  645. when RegionHasLock:
  646. releaseSys a.lock
  647. proc freeHugeChunk(a: var MemRegion; c: PBigChunk) =
  648. let size = c.size
  649. sysAssert(size >= HugeChunkSize, "freeHugeChunk: invalid size")
  650. excl(a.chunkStarts, pageIndex(c))
  651. decCurrMem(a, size)
  652. osDeallocPages(c, size)
  653. proc getSmallChunk(a: var MemRegion): PSmallChunk =
  654. var res = getBigChunk(a, PageSize)
  655. sysAssert res.prev == nil, "getSmallChunk 1"
  656. sysAssert res.next == nil, "getSmallChunk 2"
  657. result = cast[PSmallChunk](res)
  658. # -----------------------------------------------------------------------------
  659. when not defined(gcDestructors):
  660. proc isAllocatedPtr(a: MemRegion, p: pointer): bool {.benign.}
  661. when true:
  662. template allocInv(a: MemRegion): bool = true
  663. else:
  664. proc allocInv(a: MemRegion): bool =
  665. ## checks some (not all yet) invariants of the allocator's data structures.
  666. for s in low(a.freeSmallChunks)..high(a.freeSmallChunks):
  667. var c = a.freeSmallChunks[s]
  668. while not (c == nil):
  669. if c.next == c:
  670. echo "[SYSASSERT] c.next == c"
  671. return false
  672. if not (c.size == s * MemAlign):
  673. echo "[SYSASSERT] c.size != s * MemAlign"
  674. return false
  675. var it = c.freeList
  676. while not (it == nil):
  677. if not (it.zeroField == 0):
  678. echo "[SYSASSERT] it.zeroField != 0"
  679. c_printf("%ld %p\n", it.zeroField, it)
  680. return false
  681. it = it.next
  682. c = c.next
  683. result = true
  684. when false:
  685. var
  686. rsizes: array[50_000, int]
  687. rsizesLen: int
  688. proc trackSize(size: int) =
  689. rsizes[rsizesLen] = size
  690. inc rsizesLen
  691. proc untrackSize(size: int) =
  692. for i in 0 .. rsizesLen-1:
  693. if rsizes[i] == size:
  694. rsizes[i] = rsizes[rsizesLen-1]
  695. dec rsizesLen
  696. return
  697. c_fprintf(stdout, "%ld\n", size)
  698. sysAssert(false, "untracked size!")
  699. else:
  700. template trackSize(x) = discard
  701. template untrackSize(x) = discard
  702. proc deallocBigChunk(a: var MemRegion, c: PBigChunk) =
  703. when RegionHasLock:
  704. acquireSys a.lock
  705. dec a.occ, c.size
  706. untrackSize(c.size)
  707. sysAssert a.occ >= 0, "rawDealloc: negative occupied memory (case B)"
  708. when not defined(gcDestructors):
  709. a.deleted = getBottom(a)
  710. del(a, a.root, cast[int](addr(c.data)))
  711. if c.size >= HugeChunkSize: freeHugeChunk(a, c)
  712. else: freeBigChunk(a, c)
  713. when RegionHasLock:
  714. releaseSys a.lock
  715. when defined(gcDestructors):
  716. template atomicPrepend(head, elem: untyped) =
  717. # see also https://en.cppreference.com/w/cpp/atomic/atomic_compare_exchange
  718. when hasThreadSupport:
  719. while true:
  720. elem.next.storea head.loada
  721. if atomicCompareExchangeN(addr head, addr elem.next, elem, weak = true, ATOMIC_RELEASE, ATOMIC_RELAXED):
  722. break
  723. else:
  724. elem.next.storea head.loada
  725. head.storea elem
  726. proc addToSharedFreeListBigChunks(a: var MemRegion; c: PBigChunk) {.inline.} =
  727. sysAssert c.next == nil, "c.next pointer must be nil"
  728. atomicPrepend a.sharedFreeListBigChunks, c
  729. proc addToSharedFreeList(c: PSmallChunk; f: ptr FreeCell; size: int) {.inline.} =
  730. atomicPrepend c.owner.sharedFreeLists[size], f
  731. const MaxSteps = 20
  732. proc compensateCounters(a: var MemRegion; c: PSmallChunk; size: int) =
  733. # rawDealloc did NOT do the usual:
  734. # `inc(c.free, size); dec(a.occ, size)` because it wasn't the owner of these
  735. # memory locations. We have to compensate here for these for the entire list.
  736. var it = c.freeList
  737. var total = 0
  738. while it != nil:
  739. inc total, size
  740. let chunk = cast[PSmallChunk](pageAddr(it))
  741. if c != chunk:
  742. # The cell is foreign, potentially even from a foreign thread.
  743. # It must block the current chunk from being freed, as doing so would leak memory.
  744. inc c.foreignCells
  745. it = it.next
  746. # By not adjusting the foreign chunk we reserve space in it to prevent deallocation
  747. inc(c.free, total)
  748. dec(a.occ, total)
  749. proc freeDeferredObjects(a: var MemRegion; root: PBigChunk) =
  750. var it = root
  751. var maxIters = MaxSteps # make it time-bounded
  752. while true:
  753. let rest = it.next.loada
  754. it.next.storea nil
  755. deallocBigChunk(a, cast[PBigChunk](it))
  756. if maxIters == 0:
  757. if rest != nil:
  758. addToSharedFreeListBigChunks(a, rest)
  759. sysAssert a.sharedFreeListBigChunks != nil, "re-enqueing failed"
  760. break
  761. it = rest
  762. dec maxIters
  763. if it == nil: break
  764. proc rawAlloc(a: var MemRegion, requestedSize: int): pointer =
  765. when defined(nimTypeNames):
  766. inc(a.allocCounter)
  767. sysAssert(allocInv(a), "rawAlloc: begin")
  768. sysAssert(roundup(65, 8) == 72, "rawAlloc: roundup broken")
  769. var size = roundup(requestedSize, MemAlign)
  770. sysAssert(size >= sizeof(FreeCell), "rawAlloc: requested size too small")
  771. sysAssert(size >= requestedSize, "insufficient allocated size!")
  772. #c_fprintf(stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
  773. if size <= SmallChunkSize-smallChunkOverhead():
  774. template fetchSharedCells(tc: PSmallChunk) =
  775. # Consumes cells from (potentially) foreign threads from `a.sharedFreeLists[s]`
  776. when defined(gcDestructors):
  777. if tc.freeList == nil:
  778. when hasThreadSupport:
  779. # Steal the entire list from `sharedFreeList`:
  780. tc.freeList = atomicExchangeN(addr a.sharedFreeLists[s], nil, ATOMIC_RELAXED)
  781. else:
  782. tc.freeList = a.sharedFreeLists[s]
  783. a.sharedFreeLists[s] = nil
  784. # if `tc.freeList` isn't nil, `tc` will gain capacity.
  785. # We must calculate how much it gained and how many foreign cells are included.
  786. compensateCounters(a, tc, size)
  787. # allocate a small block: for small chunks, we use only its next pointer
  788. let s = size div MemAlign
  789. var c = a.freeSmallChunks[s]
  790. if c == nil:
  791. # There is no free chunk of the requested size available, we need a new one.
  792. c = getSmallChunk(a)
  793. # init all fields in case memory didn't get zeroed
  794. c.freeList = nil
  795. c.foreignCells = 0
  796. sysAssert c.size == PageSize, "rawAlloc 3"
  797. c.size = size
  798. c.acc = size.uint32
  799. c.free = SmallChunkSize - smallChunkOverhead() - size.int32
  800. sysAssert c.owner == addr(a), "rawAlloc: No owner set!"
  801. c.next = nil
  802. c.prev = nil
  803. # Shared cells are fetched here in case `c.size * 2 >= SmallChunkSize - smallChunkOverhead()`.
  804. # For those single cell chunks, we would otherwise have to allocate a new one almost every time.
  805. fetchSharedCells(c)
  806. if c.free >= size:
  807. # Because removals from `a.freeSmallChunks[s]` only happen in the other alloc branch and during dealloc,
  808. # we must not add it to the list if it cannot be used the next time a pointer of `size` bytes is needed.
  809. listAdd(a.freeSmallChunks[s], c)
  810. result = addr(c.data)
  811. sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 4")
  812. else:
  813. # There is a free chunk of the requested size available, use it.
  814. sysAssert(allocInv(a), "rawAlloc: begin c != nil")
  815. sysAssert c.next != c, "rawAlloc 5"
  816. #if c.size != size:
  817. # c_fprintf(stdout, "csize: %lld; size %lld\n", c.size, size)
  818. sysAssert c.size == size, "rawAlloc 6"
  819. if c.freeList == nil:
  820. sysAssert(c.acc.int + smallChunkOverhead() + size <= SmallChunkSize,
  821. "rawAlloc 7")
  822. result = cast[pointer](cast[int](addr(c.data)) +% c.acc.int)
  823. inc(c.acc, size)
  824. else:
  825. # There are free cells available, prefer them over the accumulator
  826. result = c.freeList
  827. when not defined(gcDestructors):
  828. sysAssert(c.freeList.zeroField == 0, "rawAlloc 8")
  829. c.freeList = c.freeList.next
  830. if cast[PSmallChunk](pageAddr(result)) != c:
  831. # This cell isn't a blocker for the current chunk's deallocation anymore
  832. dec(c.foreignCells)
  833. else:
  834. sysAssert(c == cast[PSmallChunk](pageAddr(result)), "rawAlloc: Bad cell")
  835. # Even if the cell we return is foreign, the local chunk's capacity decreases.
  836. # The capacity was previously reserved in the source chunk (when it first got allocated),
  837. # then added into the current chunk during dealloc,
  838. # so the source chunk will not be freed or leak memory because of this.
  839. dec(c.free, size)
  840. sysAssert((cast[int](result) and (MemAlign-1)) == 0, "rawAlloc 9")
  841. sysAssert(allocInv(a), "rawAlloc: end c != nil")
  842. # We fetch deferred cells *after* advancing `c.freeList`/`acc` to adjust `c.free`.
  843. # If after the adjustment it turns out there's free cells available,
  844. # the chunk stays in `a.freeSmallChunks[s]` and the need for a new chunk is delayed.
  845. fetchSharedCells(c)
  846. sysAssert(allocInv(a), "rawAlloc: before c.free < size")
  847. if c.free < size:
  848. # Even after fetching shared cells the chunk has no usable memory left. It is no longer the active chunk
  849. sysAssert(allocInv(a), "rawAlloc: before listRemove test")
  850. listRemove(a.freeSmallChunks[s], c)
  851. sysAssert(allocInv(a), "rawAlloc: end listRemove test")
  852. sysAssert(((cast[int](result) and PageMask) - smallChunkOverhead()) %%
  853. size == 0, "rawAlloc 21")
  854. sysAssert(allocInv(a), "rawAlloc: end small size")
  855. inc a.occ, size
  856. trackSize(c.size)
  857. else:
  858. when defined(gcDestructors):
  859. when hasThreadSupport:
  860. let deferredFrees = atomicExchangeN(addr a.sharedFreeListBigChunks, nil, ATOMIC_RELAXED)
  861. else:
  862. let deferredFrees = a.sharedFreeListBigChunks
  863. a.sharedFreeListBigChunks = nil
  864. if deferredFrees != nil:
  865. freeDeferredObjects(a, deferredFrees)
  866. size = requestedSize + bigChunkOverhead() # roundup(requestedSize+bigChunkOverhead(), PageSize)
  867. # allocate a large block
  868. var c = if size >= HugeChunkSize: getHugeChunk(a, size)
  869. else: getBigChunk(a, size)
  870. sysAssert c.prev == nil, "rawAlloc 10"
  871. sysAssert c.next == nil, "rawAlloc 11"
  872. result = addr(c.data)
  873. sysAssert((cast[int](c) and (MemAlign-1)) == 0, "rawAlloc 13")
  874. sysAssert((cast[int](c) and PageMask) == 0, "rawAlloc: Not aligned on a page boundary")
  875. when not defined(gcDestructors):
  876. if a.root == nil: a.root = getBottom(a)
  877. add(a, a.root, cast[int](result), cast[int](result)+%size)
  878. inc a.occ, c.size
  879. trackSize(c.size)
  880. sysAssert(isAccessible(a, result), "rawAlloc 14")
  881. sysAssert(allocInv(a), "rawAlloc: end")
  882. when logAlloc: cprintf("var pointer_%p = alloc(%ld) # %p\n", result, requestedSize, addr a)
  883. proc rawAlloc0(a: var MemRegion, requestedSize: int): pointer =
  884. result = rawAlloc(a, requestedSize)
  885. zeroMem(result, requestedSize)
  886. proc rawDealloc(a: var MemRegion, p: pointer) =
  887. when defined(nimTypeNames):
  888. inc(a.deallocCounter)
  889. #sysAssert(isAllocatedPtr(a, p), "rawDealloc: no allocated pointer")
  890. sysAssert(allocInv(a), "rawDealloc: begin")
  891. var c = pageAddr(p)
  892. sysAssert(c != nil, "rawDealloc: begin")
  893. if isSmallChunk(c):
  894. # `p` is within a small chunk:
  895. var c = cast[PSmallChunk](c)
  896. let s = c.size
  897. # ^ We might access thread foreign storage here.
  898. # The other thread cannot possibly free this block as it's still alive.
  899. var f = cast[ptr FreeCell](p)
  900. if c.owner == addr(a):
  901. # We own the block, there is no foreign thread involved.
  902. dec a.occ, s
  903. untrackSize(s)
  904. sysAssert a.occ >= 0, "rawDealloc: negative occupied memory (case A)"
  905. sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
  906. s == 0, "rawDealloc 3")
  907. when not defined(gcDestructors):
  908. #echo("setting to nil: ", $cast[int](addr(f.zeroField)))
  909. sysAssert(f.zeroField != 0, "rawDealloc 1")
  910. f.zeroField = 0
  911. when overwriteFree:
  912. # set to 0xff to check for usage after free bugs:
  913. nimSetMem(cast[pointer](cast[int](p) +% sizeof(FreeCell)), -1'i32,
  914. s -% sizeof(FreeCell))
  915. let activeChunk = a.freeSmallChunks[s div MemAlign]
  916. if activeChunk != nil and c != activeChunk:
  917. # This pointer is not part of the active chunk, lend it out
  918. # and do not adjust the current chunk (same logic as compensateCounters.)
  919. # Put the cell into the active chunk,
  920. # may prevent a queue of available chunks from forming in a.freeSmallChunks[s div MemAlign].
  921. # This queue would otherwise waste memory in the form of free cells until we return to those chunks.
  922. f.next = activeChunk.freeList
  923. activeChunk.freeList = f # lend the cell
  924. inc(activeChunk.free, s) # By not adjusting the current chunk's capacity it is prevented from being freed
  925. inc(activeChunk.foreignCells) # The cell is now considered foreign from the perspective of the active chunk
  926. else:
  927. f.next = c.freeList
  928. c.freeList = f
  929. if c.free < s:
  930. # The chunk could not have been active as it didn't have enough space to give
  931. listAdd(a.freeSmallChunks[s div MemAlign], c)
  932. inc(c.free, s)
  933. else:
  934. inc(c.free, s)
  935. # Free only if the entire chunk is unused and there are no borrowed cells.
  936. # If the chunk were to be freed while it references foreign cells,
  937. # the foreign chunks will leak memory and can never be freed.
  938. if c.free == SmallChunkSize-smallChunkOverhead() and c.foreignCells == 0:
  939. listRemove(a.freeSmallChunks[s div MemAlign], c)
  940. c.size = SmallChunkSize
  941. freeBigChunk(a, cast[PBigChunk](c))
  942. else:
  943. when logAlloc: cprintf("dealloc(pointer_%p) # SMALL FROM %p CALLER %p\n", p, c.owner, addr(a))
  944. when defined(gcDestructors):
  945. addToSharedFreeList(c, f, s div MemAlign)
  946. sysAssert(((cast[int](p) and PageMask) - smallChunkOverhead()) %%
  947. s == 0, "rawDealloc 2")
  948. else:
  949. # set to 0xff to check for usage after free bugs:
  950. when overwriteFree: nimSetMem(p, -1'i32, c.size -% bigChunkOverhead())
  951. when logAlloc: cprintf("dealloc(pointer_%p) # BIG %p\n", p, c.owner)
  952. when defined(gcDestructors):
  953. if c.owner == addr(a):
  954. deallocBigChunk(a, cast[PBigChunk](c))
  955. else:
  956. addToSharedFreeListBigChunks(c.owner[], cast[PBigChunk](c))
  957. else:
  958. deallocBigChunk(a, cast[PBigChunk](c))
  959. sysAssert(allocInv(a), "rawDealloc: end")
  960. #when logAlloc: cprintf("dealloc(pointer_%p)\n", p)
  961. when not defined(gcDestructors):
  962. proc isAllocatedPtr(a: MemRegion, p: pointer): bool =
  963. if isAccessible(a, p):
  964. var c = pageAddr(p)
  965. if not chunkUnused(c):
  966. if isSmallChunk(c):
  967. var c = cast[PSmallChunk](c)
  968. var offset = (cast[int](p) and (PageSize-1)) -%
  969. smallChunkOverhead()
  970. result = (c.acc.int >% offset) and (offset %% c.size == 0) and
  971. (cast[ptr FreeCell](p).zeroField >% 1)
  972. else:
  973. var c = cast[PBigChunk](c)
  974. result = p == addr(c.data) and cast[ptr FreeCell](p).zeroField >% 1
  975. proc prepareForInteriorPointerChecking(a: var MemRegion) {.inline.} =
  976. a.minLargeObj = lowGauge(a.root)
  977. a.maxLargeObj = highGauge(a.root)
  978. proc interiorAllocatedPtr(a: MemRegion, p: pointer): pointer =
  979. if isAccessible(a, p):
  980. var c = pageAddr(p)
  981. if not chunkUnused(c):
  982. if isSmallChunk(c):
  983. var c = cast[PSmallChunk](c)
  984. var offset = (cast[int](p) and (PageSize-1)) -%
  985. smallChunkOverhead()
  986. if c.acc.int >% offset:
  987. sysAssert(cast[int](addr(c.data)) +% offset ==
  988. cast[int](p), "offset is not what you think it is")
  989. var d = cast[ptr FreeCell](cast[int](addr(c.data)) +%
  990. offset -% (offset %% c.size))
  991. if d.zeroField >% 1:
  992. result = d
  993. sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
  994. else:
  995. var c = cast[PBigChunk](c)
  996. var d = addr(c.data)
  997. if p >= d and cast[ptr FreeCell](d).zeroField >% 1:
  998. result = d
  999. sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
  1000. else:
  1001. var q = cast[int](p)
  1002. if q >=% a.minLargeObj and q <=% a.maxLargeObj:
  1003. # this check is highly effective! Test fails for 99,96% of all checks on
  1004. # an x86-64.
  1005. var avlNode = inRange(a.root, q)
  1006. if avlNode != nil:
  1007. var k = cast[pointer](avlNode.key)
  1008. var c = cast[PBigChunk](pageAddr(k))
  1009. sysAssert(addr(c.data) == k, " k is not the same as addr(c.data)!")
  1010. if cast[ptr FreeCell](k).zeroField >% 1:
  1011. result = k
  1012. sysAssert isAllocatedPtr(a, result), " result wrong pointer!"
  1013. proc ptrSize(p: pointer): int =
  1014. when not defined(gcDestructors):
  1015. var x = cast[pointer](cast[int](p) -% sizeof(FreeCell))
  1016. var c = pageAddr(p)
  1017. sysAssert(not chunkUnused(c), "ptrSize")
  1018. result = c.size -% sizeof(FreeCell)
  1019. if not isSmallChunk(c):
  1020. dec result, bigChunkOverhead()
  1021. else:
  1022. var c = pageAddr(p)
  1023. sysAssert(not chunkUnused(c), "ptrSize")
  1024. result = c.size
  1025. if not isSmallChunk(c):
  1026. dec result, bigChunkOverhead()
  1027. proc alloc(allocator: var MemRegion, size: Natural): pointer {.gcsafe.} =
  1028. when not defined(gcDestructors):
  1029. result = rawAlloc(allocator, size+sizeof(FreeCell))
  1030. cast[ptr FreeCell](result).zeroField = 1 # mark it as used
  1031. sysAssert(not isAllocatedPtr(allocator, result), "alloc")
  1032. result = cast[pointer](cast[int](result) +% sizeof(FreeCell))
  1033. track("alloc", result, size)
  1034. else:
  1035. result = rawAlloc(allocator, size)
  1036. proc alloc0(allocator: var MemRegion, size: Natural): pointer =
  1037. result = alloc(allocator, size)
  1038. zeroMem(result, size)
  1039. proc dealloc(allocator: var MemRegion, p: pointer) =
  1040. when not defined(gcDestructors):
  1041. sysAssert(p != nil, "dealloc: p is nil")
  1042. var x = cast[pointer](cast[int](p) -% sizeof(FreeCell))
  1043. sysAssert(x != nil, "dealloc: x is nil")
  1044. sysAssert(isAccessible(allocator, x), "is not accessible")
  1045. sysAssert(cast[ptr FreeCell](x).zeroField == 1, "dealloc: object header corrupted")
  1046. rawDealloc(allocator, x)
  1047. sysAssert(not isAllocatedPtr(allocator, x), "dealloc: object still accessible")
  1048. track("dealloc", p, 0)
  1049. else:
  1050. rawDealloc(allocator, p)
  1051. proc realloc(allocator: var MemRegion, p: pointer, newsize: Natural): pointer =
  1052. result = nil
  1053. if newsize > 0:
  1054. result = alloc(allocator, newsize)
  1055. if p != nil:
  1056. copyMem(result, p, min(ptrSize(p), newsize))
  1057. dealloc(allocator, p)
  1058. elif p != nil:
  1059. dealloc(allocator, p)
  1060. proc realloc0(allocator: var MemRegion, p: pointer, oldsize, newsize: Natural): pointer =
  1061. result = realloc(allocator, p, newsize)
  1062. if newsize > oldsize:
  1063. zeroMem(cast[pointer](cast[uint](result) + uint(oldsize)), newsize - oldsize)
  1064. proc deallocOsPages(a: var MemRegion) =
  1065. # we free every 'ordinarily' allocated page by iterating over the page bits:
  1066. var it = addr(a.heapLinks)
  1067. while true:
  1068. let next = it.next
  1069. for i in 0..it.len-1:
  1070. let (p, size) = it.chunks[i]
  1071. when defined(debugHeapLinks):
  1072. cprintf("owner %p; dealloc A: %p size: %ld; next: %p\n", addr(a),
  1073. it, size, next)
  1074. sysAssert size >= PageSize, "origSize too small"
  1075. osDeallocPages(p, size)
  1076. it = next
  1077. if it == nil: break
  1078. # And then we free the pages that are in use for the page bits:
  1079. llDeallocAll(a)
  1080. proc getFreeMem(a: MemRegion): int {.inline.} = result = a.freeMem
  1081. proc getTotalMem(a: MemRegion): int {.inline.} = result = a.currMem
  1082. proc getOccupiedMem(a: MemRegion): int {.inline.} =
  1083. result = a.occ
  1084. # a.currMem - a.freeMem
  1085. when defined(nimTypeNames):
  1086. proc getMemCounters(a: MemRegion): (int, int) {.inline.} =
  1087. (a.allocCounter, a.deallocCounter)
  1088. # ---------------------- thread memory region -------------------------------
  1089. template instantiateForRegion(allocator: untyped) {.dirty.} =
  1090. {.push stackTrace: off.}
  1091. when defined(nimFulldebug):
  1092. proc interiorAllocatedPtr*(p: pointer): pointer =
  1093. result = interiorAllocatedPtr(allocator, p)
  1094. proc isAllocatedPtr*(p: pointer): bool =
  1095. let p = cast[pointer](cast[int](p)-%ByteAddress(sizeof(Cell)))
  1096. result = isAllocatedPtr(allocator, p)
  1097. proc deallocOsPages = deallocOsPages(allocator)
  1098. proc allocImpl(size: Natural): pointer =
  1099. result = alloc(allocator, size)
  1100. proc alloc0Impl(size: Natural): pointer =
  1101. result = alloc0(allocator, size)
  1102. proc deallocImpl(p: pointer) =
  1103. dealloc(allocator, p)
  1104. proc reallocImpl(p: pointer, newSize: Natural): pointer =
  1105. result = realloc(allocator, p, newSize)
  1106. proc realloc0Impl(p: pointer, oldSize, newSize: Natural): pointer =
  1107. result = realloc(allocator, p, newSize)
  1108. if newSize > oldSize:
  1109. zeroMem(cast[pointer](cast[uint](result) + uint(oldSize)), newSize - oldSize)
  1110. when false:
  1111. proc countFreeMem(): int =
  1112. # only used for assertions
  1113. var it = allocator.freeChunksList
  1114. while it != nil:
  1115. inc(result, it.size)
  1116. it = it.next
  1117. when hasThreadSupport and not defined(gcDestructors):
  1118. proc addSysExitProc(quitProc: proc() {.noconv.}) {.importc: "atexit", header: "<stdlib.h>".}
  1119. var sharedHeap: MemRegion
  1120. var heapLock: SysLock
  1121. initSysLock(heapLock)
  1122. addSysExitProc(proc() {.noconv.} = deinitSys(heapLock))
  1123. proc getFreeMem(): int =
  1124. #sysAssert(result == countFreeMem())
  1125. result = allocator.freeMem
  1126. proc getTotalMem(): int =
  1127. result = allocator.currMem
  1128. proc getOccupiedMem(): int =
  1129. result = allocator.occ #getTotalMem() - getFreeMem()
  1130. proc getMaxMem*(): int =
  1131. result = getMaxMem(allocator)
  1132. when defined(nimTypeNames):
  1133. proc getMemCounters*(): (int, int) = getMemCounters(allocator)
  1134. # -------------------- shared heap region ----------------------------------
  1135. proc allocSharedImpl(size: Natural): pointer =
  1136. when hasThreadSupport and not defined(gcDestructors):
  1137. acquireSys(heapLock)
  1138. result = alloc(sharedHeap, size)
  1139. releaseSys(heapLock)
  1140. else:
  1141. result = allocImpl(size)
  1142. proc allocShared0Impl(size: Natural): pointer =
  1143. result = allocSharedImpl(size)
  1144. zeroMem(result, size)
  1145. proc deallocSharedImpl(p: pointer) =
  1146. when hasThreadSupport and not defined(gcDestructors):
  1147. acquireSys(heapLock)
  1148. dealloc(sharedHeap, p)
  1149. releaseSys(heapLock)
  1150. else:
  1151. deallocImpl(p)
  1152. proc reallocSharedImpl(p: pointer, newSize: Natural): pointer =
  1153. when hasThreadSupport and not defined(gcDestructors):
  1154. acquireSys(heapLock)
  1155. result = realloc(sharedHeap, p, newSize)
  1156. releaseSys(heapLock)
  1157. else:
  1158. result = reallocImpl(p, newSize)
  1159. proc reallocShared0Impl(p: pointer, oldSize, newSize: Natural): pointer =
  1160. when hasThreadSupport and not defined(gcDestructors):
  1161. acquireSys(heapLock)
  1162. result = realloc0(sharedHeap, p, oldSize, newSize)
  1163. releaseSys(heapLock)
  1164. else:
  1165. result = realloc0Impl(p, oldSize, newSize)
  1166. when hasThreadSupport:
  1167. when defined(gcDestructors):
  1168. proc getFreeSharedMem(): int =
  1169. allocator.freeMem
  1170. proc getTotalSharedMem(): int =
  1171. allocator.currMem
  1172. proc getOccupiedSharedMem(): int =
  1173. allocator.occ
  1174. else:
  1175. template sharedMemStatsShared(v: int) =
  1176. acquireSys(heapLock)
  1177. result = v
  1178. releaseSys(heapLock)
  1179. proc getFreeSharedMem(): int =
  1180. sharedMemStatsShared(sharedHeap.freeMem)
  1181. proc getTotalSharedMem(): int =
  1182. sharedMemStatsShared(sharedHeap.currMem)
  1183. proc getOccupiedSharedMem(): int =
  1184. sharedMemStatsShared(sharedHeap.occ)
  1185. #sharedMemStatsShared(sharedHeap.currMem - sharedHeap.freeMem)
  1186. {.pop.}
  1187. {.pop.}