osalloc.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2016 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. proc roundup(x, v: int): int {.inline.} =
  10. result = (x + (v-1)) and not (v-1)
  11. sysAssert(result >= x, "roundup: result < x")
  12. #return ((-x) and (v-1)) +% x
  13. sysAssert(roundup(14, PageSize) == PageSize, "invalid PageSize")
  14. sysAssert(roundup(15, 8) == 16, "roundup broken")
  15. sysAssert(roundup(65, 8) == 72, "roundup broken 2")
  16. # ------------ platform specific chunk allocation code -----------
  17. # some platforms have really weird unmap behaviour:
  18. # unmap(blockStart, PageSize)
  19. # really frees the whole block. Happens for Linux/PowerPC for example. Amd64
  20. # and x86 are safe though; Windows is special because MEM_RELEASE can only be
  21. # used with a size of 0. We also allow unmapping to be turned off with
  22. # -d:nimAllocNoUnmap:
  23. const doNotUnmap = not (defined(amd64) or defined(i386)) or
  24. defined(windows) or defined(nimAllocNoUnmap)
  25. when defined(emscripten) and not defined(StandaloneHeapSize):
  26. const
  27. PROT_READ = 1 # page can be read
  28. PROT_WRITE = 2 # page can be written
  29. MAP_PRIVATE = 2'i32 # Changes are private
  30. var MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
  31. type
  32. PEmscriptenMMapBlock = ptr EmscriptenMMapBlock
  33. EmscriptenMMapBlock {.pure, inheritable.} = object
  34. realSize: int # size of previous chunk; for coalescing
  35. realPointer: pointer # if < PageSize it is a small chunk
  36. proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
  37. off: int): pointer {.header: "<sys/mman.h>".}
  38. proc munmap(adr: pointer, len: int) {.header: "<sys/mman.h>".}
  39. proc osAllocPages(block_size: int): pointer {.inline.} =
  40. let realSize = block_size + sizeof(EmscriptenMMapBlock) + PageSize + 1
  41. result = mmap(nil, realSize, PROT_READ or PROT_WRITE,
  42. MAP_PRIVATE or MAP_ANONYMOUS, -1, 0)
  43. if result == nil or result == cast[pointer](-1):
  44. raiseOutOfMem()
  45. let realPointer = result
  46. let pos = cast[int](result)
  47. # Convert pointer to PageSize correct one.
  48. var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize))
  49. if (new_pos-pos) < sizeof(EmscriptenMMapBlock):
  50. new_pos = new_pos +% PageSize
  51. result = cast[pointer](new_pos)
  52. var mmapDescrPos = cast[ByteAddress](result) -% sizeof(EmscriptenMMapBlock)
  53. var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
  54. mmapDescr.realSize = realSize
  55. mmapDescr.realPointer = realPointer
  56. #c_fprintf(stdout, "[Alloc] size %d %d realSize:%d realPos:%d\n", block_size, cast[int](result), realSize, cast[int](realPointer))
  57. proc osTryAllocPages(size: int): pointer = osAllocPages(size)
  58. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  59. var mmapDescrPos = cast[ByteAddress](p) -% sizeof(EmscriptenMMapBlock)
  60. var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
  61. munmap(mmapDescr.realPointer, mmapDescr.realSize)
  62. elif defined(genode) and not defined(StandaloneHeapSize):
  63. include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages
  64. elif defined(nintendoswitch) and not defined(StandaloneHeapSize):
  65. import nintendoswitch/switch_memory
  66. type
  67. PSwitchBlock = ptr NSwitchBlock
  68. ## This will hold the heap pointer data in a separate
  69. ## block of memory that is PageSize bytes above
  70. ## the requested memory. It's the only good way
  71. ## to pass around data with heap allocations
  72. NSwitchBlock {.pure, inheritable.} = object
  73. realSize: int
  74. heap: pointer # pointer to main heap alloc
  75. heapMirror: pointer # pointer to virtmem mapped heap
  76. proc alignSize(size: int): int {.inline.} =
  77. ## Align a size integer to be in multiples of PageSize
  78. ## The nintendo switch will not allocate memory that is not
  79. ## aligned to 0x1000 bytes and will just crash.
  80. (size + (PageSize - 1)) and not (PageSize - 1)
  81. proc deallocate(heapMirror: pointer, heap: pointer, size: int) =
  82. # Unmap the allocated memory
  83. discard svcUnmapMemory(heapMirror, heap, size.uint64)
  84. # These should be called (theoretically), but referencing them crashes the switch.
  85. # The above call seems to free all heap memory, so these are not needed.
  86. # virtmemFreeMap(nswitchBlock.heapMirror, nswitchBlock.realSize.csize)
  87. # free(nswitchBlock.heap)
  88. proc freeMem(p: pointer) =
  89. # Retrieve the switch block data from the pointer we set before
  90. # The data is located just sizeof(NSwitchBlock) bytes below
  91. # the top of the pointer to the heap
  92. let
  93. nswitchDescrPos = cast[ByteAddress](p) -% sizeof(NSwitchBlock)
  94. nswitchBlock = cast[PSwitchBlock](nswitchDescrPos)
  95. deallocate(
  96. nswitchBlock.heapMirror, nswitchBlock.heap, nswitchBlock.realSize
  97. )
  98. proc storeHeapData(address, heapMirror, heap: pointer, size: int) {.inline.} =
  99. ## Store data in the heap for deallocation purposes later
  100. # the position of our heap pointer data. Since we allocated PageSize extra
  101. # bytes, we should have a buffer on top of the requested size of at least
  102. # PageSize bytes, which is much larger than sizeof(NSwitchBlock). So we
  103. # decrement the address by sizeof(NSwitchBlock) and use that address
  104. # to store our pointer data
  105. let nswitchBlockPos = cast[ByteAddress](address) -% sizeof(NSwitchBlock)
  106. # We need to store this in a pointer obj (PSwitchBlock) so that the data sticks
  107. # at the address we've chosen. If NSwitchBlock is used here, the data will
  108. # be all 0 when we try to retrieve it later.
  109. var nswitchBlock = cast[PSwitchBlock](nswitchBlockPos)
  110. nswitchBlock.realSize = size
  111. nswitchBlock.heap = heap
  112. nswitchBlock.heapMirror = heapMirror
  113. proc getOriginalHeapPosition(address: pointer, difference: int): pointer {.inline.} =
  114. ## This function sets the heap back to the originally requested
  115. ## size
  116. let
  117. pos = cast[int](address)
  118. newPos = cast[ByteAddress](pos) +% difference
  119. return cast[pointer](newPos)
  120. template allocPages(size: int, outOfMemoryStmt: untyped): untyped =
  121. # This is to ensure we get a block of memory the requested
  122. # size, as well as space to store our structure
  123. let realSize = alignSize(size + sizeof(NSwitchBlock))
  124. let heap = memalign(PageSize, realSize)
  125. if heap.isNil:
  126. outOfMemoryStmt
  127. let heapMirror = virtmemReserveMap(realSize.csize)
  128. result = heapMirror
  129. let rc = svcMapMemory(heapMirror, heap, realSize.uint64)
  130. # Any return code not equal 0 means an error in libnx
  131. if rc.uint32 != 0:
  132. deallocate(heapMirror, heap, realSize)
  133. outOfMemoryStmt
  134. # set result to be the original size requirement
  135. result = getOriginalHeapPosition(result, realSize - size)
  136. storeHeapData(result, heapMirror, heap, realSize)
  137. proc osAllocPages(size: int): pointer {.inline.} =
  138. allocPages(size):
  139. raiseOutOfMem()
  140. proc osTryAllocPages(size: int): pointer =
  141. allocPages(size):
  142. return nil
  143. proc osDeallocPages(p: pointer, size: int) =
  144. # Note that in order for the Switch not to crash, a call to
  145. # deallocHeap(runFinalizers = true, allowGcAfterwards = false)
  146. # must be run before gfxExit(). The Switch requires all memory
  147. # to be deallocated before the graphics application has exited.
  148. #
  149. # gfxExit() can be found in <switch/gfx/gfx.h> in the github
  150. # repo https://github.com/switchbrew/libnx
  151. when reallyOsDealloc:
  152. freeMem(p)
  153. elif defined(posix) and not defined(StandaloneHeapSize):
  154. const
  155. PROT_READ = 1 # page can be read
  156. PROT_WRITE = 2 # page can be written
  157. when defined(netbsd) or defined(openbsd):
  158. # OpenBSD security for setjmp/longjmp coroutines
  159. var MAP_STACK {.importc: "MAP_STACK", header: "<sys/mman.h>".}: cint
  160. else:
  161. const MAP_STACK = 0 # avoid sideeffects
  162. when defined(macosx) or defined(freebsd):
  163. const MAP_ANONYMOUS = 0x1000
  164. const MAP_PRIVATE = 0x02 # Changes are private
  165. elif defined(solaris):
  166. const MAP_ANONYMOUS = 0x100
  167. const MAP_PRIVATE = 0x02 # Changes are private
  168. elif defined(linux) and defined(amd64):
  169. # actually, any architecture using asm-generic, but being conservative here,
  170. # some arches like mips and alpha use different values
  171. const MAP_ANONYMOUS = 0x20
  172. const MAP_PRIVATE = 0x02 # Changes are private
  173. elif defined(haiku):
  174. const MAP_ANONYMOUS = 0x08
  175. const MAP_PRIVATE = 0x02
  176. else: # posix including netbsd or openbsd
  177. var
  178. MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
  179. MAP_PRIVATE {.importc: "MAP_PRIVATE", header: "<sys/mman.h>".}: cint
  180. proc mmap(adr: pointer, len: csize_t, prot, flags, fildes: cint,
  181. off: int): pointer {.header: "<sys/mman.h>".}
  182. proc munmap(adr: pointer, len: csize_t): cint {.header: "<sys/mman.h>".}
  183. proc osAllocPages(size: int): pointer {.inline.} =
  184. result = mmap(nil, cast[csize_t](size), PROT_READ or PROT_WRITE,
  185. MAP_ANONYMOUS or MAP_PRIVATE or MAP_STACK, -1, 0)
  186. if result == nil or result == cast[pointer](-1):
  187. raiseOutOfMem()
  188. proc osTryAllocPages(size: int): pointer {.inline.} =
  189. result = mmap(nil, cast[csize_t](size), PROT_READ or PROT_WRITE,
  190. MAP_ANONYMOUS or MAP_PRIVATE or MAP_STACK, -1, 0)
  191. if result == cast[pointer](-1): result = nil
  192. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  193. when reallyOsDealloc: discard munmap(p, cast[csize_t](size))
  194. elif defined(windows) and not defined(StandaloneHeapSize):
  195. const
  196. MEM_RESERVE = 0x2000
  197. MEM_COMMIT = 0x1000
  198. MEM_TOP_DOWN = 0x100000
  199. PAGE_READWRITE = 0x04
  200. MEM_DECOMMIT = 0x4000
  201. MEM_RELEASE = 0x8000
  202. proc virtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
  203. flProtect: int32): pointer {.
  204. header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
  205. proc virtualFree(lpAddress: pointer, dwSize: int,
  206. dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
  207. importc: "VirtualFree".}
  208. proc osAllocPages(size: int): pointer {.inline.} =
  209. result = virtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT,
  210. PAGE_READWRITE)
  211. if result == nil: raiseOutOfMem()
  212. proc osTryAllocPages(size: int): pointer {.inline.} =
  213. result = virtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT,
  214. PAGE_READWRITE)
  215. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  216. # according to Microsoft, 0 is the only correct value for MEM_RELEASE:
  217. # This means that the OS has some different view over how big the block is
  218. # that we want to free! So, we cannot reliably release the memory back to
  219. # Windows :-(. We have to live with MEM_DECOMMIT instead.
  220. # Well that used to be the case but MEM_DECOMMIT fragments the address
  221. # space heavily, so we now treat Windows as a strange unmap target.
  222. when reallyOsDealloc:
  223. if virtualFree(p, 0, MEM_RELEASE) == 0:
  224. cprintf "virtualFree failing!"
  225. quit 1
  226. #VirtualFree(p, size, MEM_DECOMMIT)
  227. elif hostOS == "standalone" or defined(StandaloneHeapSize):
  228. const StandaloneHeapSize {.intdefine.}: int = 1024 * PageSize
  229. var
  230. theHeap: array[StandaloneHeapSize div sizeof(float64), float64] # 'float64' for alignment
  231. bumpPointer = cast[int](addr theHeap)
  232. proc osAllocPages(size: int): pointer {.inline.} =
  233. if size+bumpPointer < cast[int](addr theHeap) + sizeof(theHeap):
  234. result = cast[pointer](bumpPointer)
  235. inc bumpPointer, size
  236. else:
  237. raiseOutOfMem()
  238. proc osTryAllocPages(size: int): pointer {.inline.} =
  239. if size+bumpPointer < cast[int](addr theHeap) + sizeof(theHeap):
  240. result = cast[pointer](bumpPointer)
  241. inc bumpPointer, size
  242. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  243. if bumpPointer-size == cast[int](p):
  244. dec bumpPointer, size
  245. else:
  246. {.error: "Port memory manager to your platform".}