gc_regions.nim 14 KB

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