gc_regions.nim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. #
  2. # Nim's Runtime Library
  3. # (c) Copyright 2016 Andreas Rumpf
  4. #
  5. # See the file "copying.txt", included in this
  6. # distribution, for details about the copyright.
  7. #
  8. # "Stack GC" for embedded devices or ultra performance requirements.
  9. when defined(memProfiler):
  10. proc nimProfile(requestedSize: int) {.benign.}
  11. when defined(useMalloc):
  12. proc roundup(x, v: int): int {.inline.} =
  13. result = (x + (v-1)) and not (v-1)
  14. proc emalloc(size: int): pointer {.importc: "malloc", header: "<stdlib.h>".}
  15. proc efree(mem: pointer) {.importc: "free", header: "<stdlib.h>".}
  16. proc osAllocPages(size: int): pointer {.inline.} =
  17. emalloc(size)
  18. proc osTryAllocPages(size: int): pointer {.inline.} =
  19. emalloc(size)
  20. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  21. efree(p)
  22. else:
  23. include osalloc
  24. # We manage memory as a thread local stack. Since the allocation pointer
  25. # is detached from the control flow pointer, this model is vastly more
  26. # useful than the traditional programming model while almost as safe.
  27. # Individual objects can also be deleted but no coalescing is performed.
  28. # Stacks can also be moved from one thread to another.
  29. # We also support 'finalizers'.
  30. type
  31. Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.}
  32. # A ref type can have a finalizer that is called before the object's
  33. # storage is freed.
  34. AlignType = BiggestFloat
  35. ObjHeader = object
  36. typ: PNimType
  37. nextFinal: ptr ObjHeader # next object with finalizer
  38. Chunk = ptr BaseChunk
  39. BaseChunk = object
  40. next: Chunk
  41. size: int
  42. head, tail: ptr ObjHeader # first and last object in chunk that
  43. # has a finalizer attached to it
  44. const
  45. MaxSmallObject = 128
  46. type
  47. FreeEntry = ptr object
  48. next: FreeEntry
  49. SizedFreeEntry = ptr object
  50. next: SizedFreeEntry
  51. size: int
  52. StackPtr = object
  53. bump: pointer
  54. remaining: int
  55. current: Chunk
  56. MemRegion* = object
  57. remaining: int
  58. bump: pointer
  59. head, tail: Chunk
  60. nextChunkSize, totalSize: int
  61. when false:
  62. freeLists: array[MaxSmallObject div MemAlign, FreeEntry]
  63. holes: SizedFreeEntry
  64. when hasThreadSupport:
  65. lock: SysLock
  66. SeqHeader = object # minor hack ahead: Since we know that seqs
  67. # and strings cannot have finalizers, we use the field
  68. # instead for a 'region' field so that they can grow
  69. # and shrink safely.
  70. typ: PNimType
  71. region: ptr MemRegion
  72. var
  73. tlRegion {.threadvar.}: MemRegion
  74. # tempStrRegion {.threadvar.}: MemRegion # not yet used
  75. template withRegion*(r: var MemRegion; body: untyped) =
  76. let oldRegion = tlRegion
  77. tlRegion = r
  78. try:
  79. body
  80. finally:
  81. r = tlRegion
  82. tlRegion = oldRegion
  83. template inc(p: pointer, s: int) =
  84. p = cast[pointer](cast[int](p) +% s)
  85. template dec(p: pointer, s: int) =
  86. p = cast[pointer](cast[int](p) -% s)
  87. template `+!`(p: pointer, s: int): pointer =
  88. cast[pointer](cast[int](p) +% s)
  89. template `-!`(p: pointer, s: int): pointer =
  90. cast[pointer](cast[int](p) -% s)
  91. const nimMinHeapPages {.intdefine.} = 4
  92. proc allocSlowPath(r: var MemRegion; size: int) =
  93. # we need to ensure that the underlying linked list
  94. # stays small. Say we want to grab 16GB of RAM with some
  95. # exponential growth function. So we allocate 16KB, then
  96. # 32 KB, 64 KB, 128KB, 256KB, 512KB, 1MB, 2MB, 4MB,
  97. # 8MB, 16MB, 32MB, 64MB, 128MB, 512MB, 1GB, 2GB, 4GB, 8GB,
  98. # 16GB --> list contains only 20 elements! That's reasonable.
  99. if (r.totalSize and 1) == 0:
  100. r.nextChunkSize = if r.totalSize < 64 * 1024: PageSize*nimMinHeapPages
  101. else: r.nextChunkSize*2
  102. var s = roundup(size+sizeof(BaseChunk), PageSize)
  103. var fresh: Chunk
  104. if s > r.nextChunkSize:
  105. fresh = cast[Chunk](osAllocPages(s))
  106. else:
  107. fresh = cast[Chunk](osTryAllocPages(r.nextChunkSize))
  108. if fresh == nil:
  109. fresh = cast[Chunk](osAllocPages(s))
  110. # lowest bit in totalSize is the "don't increase nextChunkSize"
  111. inc r.totalSize
  112. else:
  113. s = r.nextChunkSize
  114. fresh.size = s
  115. fresh.head = nil
  116. fresh.tail = nil
  117. fresh.next = nil
  118. inc r.totalSize, s
  119. let old = r.tail
  120. if old == nil:
  121. r.head = fresh
  122. else:
  123. r.tail.next = fresh
  124. r.bump = fresh +! sizeof(BaseChunk)
  125. r.tail = fresh
  126. r.remaining = s - sizeof(BaseChunk)
  127. proc allocFast(r: var MemRegion; size: int): pointer =
  128. when false:
  129. if size <= MaxSmallObject:
  130. var it = r.freeLists[size div MemAlign]
  131. if it != nil:
  132. r.freeLists[size div MemAlign] = it.next
  133. return pointer(it)
  134. else:
  135. var it = r.holes
  136. var prev: SizedFreeEntry = nil
  137. while it != nil:
  138. if it.size >= size:
  139. if prev != nil: prev.next = it.next
  140. else: r.holes = it.next
  141. return pointer(it)
  142. prev = it
  143. it = it.next
  144. let size = roundup(size, MemAlign)
  145. if size > r.remaining:
  146. allocSlowPath(r, size)
  147. sysAssert(size <= r.remaining, "size <= r.remaining")
  148. dec(r.remaining, size)
  149. result = r.bump
  150. inc r.bump, size
  151. proc runFinalizers(c: Chunk) =
  152. var it = c.head
  153. while it != nil:
  154. # indivually freed objects with finalizer stay in the list, but
  155. # their typ is nil then:
  156. if it.typ != nil and it.typ.finalizer != nil:
  157. (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader))
  158. it = it.nextFinal
  159. proc runFinalizers(c: Chunk; newbump: pointer) =
  160. var it = c.head
  161. var prev: ptr ObjHeader = nil
  162. while it != nil:
  163. let nxt = it.nextFinal
  164. if it >= newbump:
  165. if it.typ != nil and it.typ.finalizer != nil:
  166. (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader))
  167. elif prev != nil:
  168. prev.nextFinal = nil
  169. prev = it
  170. it = nxt
  171. proc dealloc(r: var MemRegion; p: pointer; size: int) =
  172. let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader))
  173. if it.typ != nil and it.typ.finalizer != nil:
  174. (cast[Finalizer](it.typ.finalizer))(p)
  175. it.typ = nil
  176. # it is beneficial to not use the free lists here:
  177. if r.bump -! size == p:
  178. dec r.bump, size
  179. when false:
  180. if size <= MaxSmallObject:
  181. let it = cast[FreeEntry](p)
  182. it.next = r.freeLists[size div MemAlign]
  183. r.freeLists[size div MemAlign] = it
  184. else:
  185. let it = cast[SizedFreeEntry](p)
  186. it.size = size
  187. it.next = r.holes
  188. r.holes = it
  189. proc deallocAll(r: var MemRegion; head: Chunk) =
  190. var it = head
  191. while it != nil:
  192. let nxt = it.next
  193. runFinalizers(it)
  194. dec r.totalSize, it.size
  195. osDeallocPages(it, it.size)
  196. it = nxt
  197. proc deallocAll*(r: var MemRegion) =
  198. deallocAll(r, r.head)
  199. zeroMem(addr r, sizeof r)
  200. proc obstackPtr*(r: MemRegion): StackPtr =
  201. result.bump = r.bump
  202. result.remaining = r.remaining
  203. result.current = r.tail
  204. template computeRemaining(r): untyped =
  205. r.tail.size -% (cast[int](r.bump) -% cast[int](r.tail))
  206. proc setObstackPtr*(r: var MemRegion; sp: StackPtr) =
  207. # free everything after 'sp':
  208. if sp.current != nil and sp.current.next != nil:
  209. deallocAll(r, sp.current.next)
  210. sp.current.next = nil
  211. when false:
  212. # better leak this memory than be sorry:
  213. for i in 0..high(r.freeLists): r.freeLists[i] = nil
  214. r.holes = nil
  215. if r.tail != nil: runFinalizers(r.tail, sp.bump)
  216. r.bump = sp.bump
  217. r.tail = sp.current
  218. r.remaining = sp.remaining
  219. proc obstackPtr*(): StackPtr = tlRegion.obstackPtr()
  220. proc setObstackPtr*(sp: StackPtr) = tlRegion.setObstackPtr(sp)
  221. proc deallocAll*() = tlRegion.deallocAll()
  222. proc deallocOsPages(r: var MemRegion) = r.deallocAll()
  223. when false:
  224. let obs = obstackPtr()
  225. try:
  226. body
  227. finally:
  228. setObstackPtr(obs)
  229. template withScratchRegion*(body: untyped) =
  230. let oldRegion = tlRegion
  231. tlRegion = MemRegion()
  232. try:
  233. body
  234. finally:
  235. deallocAll()
  236. tlRegion = oldRegion
  237. when false:
  238. proc joinRegion*(dest: var MemRegion; src: MemRegion) =
  239. # merging is not hard.
  240. if dest.head.isNil:
  241. dest.head = src.head
  242. else:
  243. dest.tail.next = src.head
  244. dest.tail = src.tail
  245. dest.bump = src.bump
  246. dest.remaining = src.remaining
  247. dest.nextChunkSize = max(dest.nextChunkSize, src.nextChunkSize)
  248. inc dest.totalSize, src.totalSize
  249. proc isOnHeap*(r: MemRegion; p: pointer): bool =
  250. # the tail chunk is the largest, so check it first. It's also special
  251. # in that contains the current bump pointer:
  252. if r.tail >= p and p < r.bump:
  253. return true
  254. var it = r.head
  255. while it != r.tail:
  256. if it >= p and p <= it+!it.size: return true
  257. it = it.next
  258. proc rawNewObj(r: var MemRegion, typ: PNimType, size: int): pointer =
  259. var res = cast[ptr ObjHeader](allocFast(r, size + sizeof(ObjHeader)))
  260. res.typ = typ
  261. if typ.finalizer != nil:
  262. res.nextFinal = r.head.head
  263. r.head.head = res
  264. result = res +! sizeof(ObjHeader)
  265. proc rawNewSeq(r: var MemRegion, typ: PNimType, size: int): pointer =
  266. var res = cast[ptr SeqHeader](allocFast(r, size + sizeof(SeqHeader)))
  267. res.typ = typ
  268. res.region = addr(r)
  269. result = res +! sizeof(SeqHeader)
  270. proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
  271. sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs"
  272. result = rawNewObj(tlRegion, typ, size)
  273. zeroMem(result, size)
  274. when defined(memProfiler): nimProfile(size)
  275. proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} =
  276. sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs"
  277. result = rawNewObj(tlRegion, typ, size)
  278. when defined(memProfiler): nimProfile(size)
  279. {.push overflowChecks: on.}
  280. proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
  281. let size = roundup(align(GenericSeqSize, typ.base.align) + len * typ.base.size, MemAlign)
  282. result = rawNewSeq(tlRegion, typ, size)
  283. zeroMem(result, size)
  284. cast[PGenericSeq](result).len = len
  285. cast[PGenericSeq](result).reserved = len
  286. proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl.} =
  287. let size = roundup(len + GenericSeqSize, MemAlign)
  288. result = rawNewSeq(tlRegion, typ, size)
  289. if init: zeroMem(result, size)
  290. cast[PGenericSeq](result).len = 0
  291. cast[PGenericSeq](result).reserved = len
  292. {.pop.}
  293. proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
  294. result = rawNewObj(tlRegion, typ, size)
  295. zeroMem(result, size)
  296. proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
  297. result = newSeq(typ, len)
  298. proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer =
  299. let sh = cast[ptr SeqHeader](old -! sizeof(SeqHeader))
  300. let typ = sh.typ
  301. result = rawNewSeq(sh.region[], typ,
  302. roundup(newsize, MemAlign))
  303. let elemSize = if typ.kind == tyString: 1 else: typ.base.size
  304. let elemAlign = if typ.kind == tyString: 1 else: typ.base.align
  305. let oldsize = align(GenericSeqSize, elemAlign) + cast[PGenericSeq](old).len*elemSize
  306. zeroMem(result +! oldsize, newsize-oldsize)
  307. copyMem(result, old, oldsize)
  308. dealloc(sh.region[], old, roundup(oldsize, MemAlign))
  309. proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
  310. result = growObj(tlRegion, old, newsize)
  311. proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
  312. dest[] = src
  313. proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
  314. dest[] = src
  315. proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline,
  316. deprecated: "old compiler compat".} = asgnRef(dest, src)
  317. proc allocImpl(size: Natural): pointer =
  318. result = c_malloc(cast[csize_t](size))
  319. if result == nil: raiseOutOfMem()
  320. proc alloc0Impl(size: Natural): pointer =
  321. result = alloc(size)
  322. zeroMem(result, size)
  323. proc reallocImpl(p: pointer, newsize: Natural): pointer =
  324. result = c_realloc(p, cast[csize_t](newsize))
  325. if result == nil: raiseOutOfMem()
  326. proc realloc0Impl(p: pointer, oldsize, newsize: Natural): pointer =
  327. result = c_realloc(p, cast[csize_t](newsize))
  328. if result == nil: raiseOutOfMem()
  329. if newsize > oldsize:
  330. zeroMem(cast[pointer](cast[int](result) + oldsize), newsize - oldsize)
  331. proc deallocImpl(p: pointer) = c_free(p)
  332. proc alloc0(r: var MemRegion; size: Natural): pointer =
  333. # ignore the region. That is correct for the channels module
  334. # but incorrect in general. XXX
  335. result = alloc0(size)
  336. proc alloc(r: var MemRegion; size: Natural): pointer =
  337. # ignore the region. That is correct for the channels module
  338. # but incorrect in general. XXX
  339. result = alloc(size)
  340. proc dealloc(r: var MemRegion; p: pointer) = dealloc(p)
  341. proc allocSharedImpl(size: Natural): pointer =
  342. result = c_malloc(cast[csize_t](size))
  343. if result == nil: raiseOutOfMem()
  344. proc allocShared0Impl(size: Natural): pointer =
  345. result = alloc(size)
  346. zeroMem(result, size)
  347. proc reallocSharedImpl(p: pointer, newsize: Natural): pointer =
  348. result = c_realloc(p, cast[csize_t](newsize))
  349. if result == nil: raiseOutOfMem()
  350. proc reallocShared0Impl(p: pointer, oldsize, newsize: Natural): pointer =
  351. result = c_realloc(p, cast[csize_t](newsize))
  352. if result == nil: raiseOutOfMem()
  353. if newsize > oldsize:
  354. zeroMem(cast[pointer](cast[int](result) + oldsize), newsize - oldsize)
  355. proc deallocSharedImpl(p: pointer) = c_free(p)
  356. when hasThreadSupport:
  357. proc getFreeSharedMem(): int = 0
  358. proc getTotalSharedMem(): int = 0
  359. proc getOccupiedSharedMem(): int = 0
  360. proc GC_disable() = discard
  361. proc GC_enable() = discard
  362. proc GC_fullCollect() = discard
  363. proc GC_setStrategy(strategy: GC_Strategy) = discard
  364. proc GC_enableMarkAndSweep() = discard
  365. proc GC_disableMarkAndSweep() = discard
  366. proc GC_getStatistics(): string = return ""
  367. proc getOccupiedMem(): int =
  368. result = tlRegion.totalSize - tlRegion.remaining
  369. proc getFreeMem(): int = tlRegion.remaining
  370. proc getTotalMem(): int =
  371. result = tlRegion.totalSize
  372. proc getOccupiedMem*(r: MemRegion): int =
  373. result = r.totalSize - r.remaining
  374. proc getFreeMem*(r: MemRegion): int = r.remaining
  375. proc getTotalMem*(r: MemRegion): int =
  376. result = r.totalSize
  377. proc nimGC_setStackBottom(theStackBottom: pointer) = discard
  378. proc nimGCref(x: pointer) {.compilerproc.} = discard
  379. proc nimGCunref(x: pointer) {.compilerproc.} = discard