gc_regions.nim 13 KB

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