osalloc.nim 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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(nimAllocPagesViaMalloc):
  26. when not defined(gcArc) and not defined(gcOrc):
  27. {.error: "-d:nimAllocPagesViaMalloc is only supported with --gc:arc or --gc:orc".}
  28. proc osTryAllocPages(size: int): pointer {.inline.} =
  29. let base = c_malloc(csize_t size + PageSize - 1 + sizeof(uint32))
  30. if base == nil: raiseOutOfMem()
  31. # memory layout: padding + offset (4 bytes) + user_data
  32. # in order to deallocate: read offset at user_data - 4 bytes,
  33. # then deallocate user_data - offset
  34. let offset = PageSize - (cast[int](base) and (PageSize - 1))
  35. cast[ptr uint32](base +! (offset - sizeof(uint32)))[] = uint32(offset)
  36. result = base +! offset
  37. proc osAllocPages(size: int): pointer {.inline.} =
  38. result = osTryAllocPages(size)
  39. if result == nil: raiseOutOfMem()
  40. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  41. # read offset at p - 4 bytes, then deallocate (p - offset) pointer
  42. let offset = cast[ptr uint32](p -! sizeof(uint32))[]
  43. c_free(p -! offset)
  44. elif defined(emscripten) and not defined(StandaloneHeapSize):
  45. const
  46. PROT_READ = 1 # page can be read
  47. PROT_WRITE = 2 # page can be written
  48. MAP_PRIVATE = 2'i32 # Changes are private
  49. var MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
  50. type
  51. PEmscriptenMMapBlock = ptr EmscriptenMMapBlock
  52. EmscriptenMMapBlock {.pure, inheritable.} = object
  53. realSize: int # size of previous chunk; for coalescing
  54. realPointer: pointer # if < PageSize it is a small chunk
  55. proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
  56. off: int): pointer {.header: "<sys/mman.h>".}
  57. proc munmap(adr: pointer, len: int) {.header: "<sys/mman.h>".}
  58. proc osAllocPages(block_size: int): pointer {.inline.} =
  59. let realSize = block_size + sizeof(EmscriptenMMapBlock) + PageSize + 1
  60. result = mmap(nil, realSize, PROT_READ or PROT_WRITE,
  61. MAP_PRIVATE or MAP_ANONYMOUS, -1, 0)
  62. if result == nil or result == cast[pointer](-1):
  63. raiseOutOfMem()
  64. let realPointer = result
  65. let pos = cast[int](result)
  66. # Convert pointer to PageSize correct one.
  67. var new_pos = cast[ByteAddress](pos) +% (PageSize - (pos %% PageSize))
  68. if (new_pos-pos) < sizeof(EmscriptenMMapBlock):
  69. new_pos = new_pos +% PageSize
  70. result = cast[pointer](new_pos)
  71. var mmapDescrPos = cast[ByteAddress](result) -% sizeof(EmscriptenMMapBlock)
  72. var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
  73. mmapDescr.realSize = realSize
  74. mmapDescr.realPointer = realPointer
  75. #c_fprintf(stdout, "[Alloc] size %d %d realSize:%d realPos:%d\n", block_size, cast[int](result), realSize, cast[int](realPointer))
  76. proc osTryAllocPages(size: int): pointer = osAllocPages(size)
  77. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  78. var mmapDescrPos = cast[ByteAddress](p) -% sizeof(EmscriptenMMapBlock)
  79. var mmapDescr = cast[EmscriptenMMapBlock](mmapDescrPos)
  80. munmap(mmapDescr.realPointer, mmapDescr.realSize)
  81. elif defined(genode) and not defined(StandaloneHeapSize):
  82. include genode/alloc # osAllocPages, osTryAllocPages, osDeallocPages
  83. elif defined(posix) and not defined(StandaloneHeapSize):
  84. const
  85. PROT_READ = 1 # page can be read
  86. PROT_WRITE = 2 # page can be written
  87. when defined(netbsd) or defined(openbsd):
  88. # OpenBSD security for setjmp/longjmp coroutines
  89. var MAP_STACK {.importc: "MAP_STACK", header: "<sys/mman.h>".}: cint
  90. else:
  91. const MAP_STACK = 0 # avoid sideeffects
  92. when defined(macosx) or defined(freebsd):
  93. const MAP_ANONYMOUS = 0x1000
  94. const MAP_PRIVATE = 0x02 # Changes are private
  95. elif defined(solaris):
  96. const MAP_ANONYMOUS = 0x100
  97. const MAP_PRIVATE = 0x02 # Changes are private
  98. elif defined(linux) and defined(amd64):
  99. # actually, any architecture using asm-generic, but being conservative here,
  100. # some arches like mips and alpha use different values
  101. const MAP_ANONYMOUS = 0x20
  102. const MAP_PRIVATE = 0x02 # Changes are private
  103. elif defined(haiku):
  104. const MAP_ANONYMOUS = 0x08
  105. const MAP_PRIVATE = 0x02
  106. else: # posix including netbsd or openbsd
  107. var
  108. MAP_ANONYMOUS {.importc: "MAP_ANONYMOUS", header: "<sys/mman.h>".}: cint
  109. MAP_PRIVATE {.importc: "MAP_PRIVATE", header: "<sys/mman.h>".}: cint
  110. proc mmap(adr: pointer, len: csize_t, prot, flags, fildes: cint,
  111. off: int): pointer {.header: "<sys/mman.h>".}
  112. proc munmap(adr: pointer, len: csize_t): cint {.header: "<sys/mman.h>".}
  113. proc osAllocPages(size: int): pointer {.inline.} =
  114. result = mmap(nil, cast[csize_t](size), PROT_READ or PROT_WRITE,
  115. MAP_ANONYMOUS or MAP_PRIVATE or MAP_STACK, -1, 0)
  116. if result == nil or result == cast[pointer](-1):
  117. raiseOutOfMem()
  118. proc osTryAllocPages(size: int): pointer {.inline.} =
  119. result = mmap(nil, cast[csize_t](size), PROT_READ or PROT_WRITE,
  120. MAP_ANONYMOUS or MAP_PRIVATE or MAP_STACK, -1, 0)
  121. if result == cast[pointer](-1): result = nil
  122. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  123. when reallyOsDealloc: discard munmap(p, cast[csize_t](size))
  124. elif defined(windows) and not defined(StandaloneHeapSize):
  125. const
  126. MEM_RESERVE = 0x2000
  127. MEM_COMMIT = 0x1000
  128. MEM_TOP_DOWN = 0x100000
  129. PAGE_READWRITE = 0x04
  130. MEM_DECOMMIT = 0x4000
  131. MEM_RELEASE = 0x8000
  132. proc virtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
  133. flProtect: int32): pointer {.
  134. header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
  135. proc virtualFree(lpAddress: pointer, dwSize: int,
  136. dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
  137. importc: "VirtualFree".}
  138. proc osAllocPages(size: int): pointer {.inline.} =
  139. result = virtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT,
  140. PAGE_READWRITE)
  141. if result == nil: raiseOutOfMem()
  142. proc osTryAllocPages(size: int): pointer {.inline.} =
  143. result = virtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT,
  144. PAGE_READWRITE)
  145. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  146. # according to Microsoft, 0 is the only correct value for MEM_RELEASE:
  147. # This means that the OS has some different view over how big the block is
  148. # that we want to free! So, we cannot reliably release the memory back to
  149. # Windows :-(. We have to live with MEM_DECOMMIT instead.
  150. # Well that used to be the case but MEM_DECOMMIT fragments the address
  151. # space heavily, so we now treat Windows as a strange unmap target.
  152. when reallyOsDealloc:
  153. if virtualFree(p, 0, MEM_RELEASE) == 0:
  154. cprintf "virtualFree failing!"
  155. quit 1
  156. #VirtualFree(p, size, MEM_DECOMMIT)
  157. elif hostOS == "standalone" or defined(StandaloneHeapSize):
  158. const StandaloneHeapSize {.intdefine.}: int = 1024 * PageSize
  159. var
  160. theHeap: array[StandaloneHeapSize div sizeof(float64), float64] # 'float64' for alignment
  161. bumpPointer = cast[int](addr theHeap)
  162. proc osAllocPages(size: int): pointer {.inline.} =
  163. if size+bumpPointer < cast[int](addr theHeap) + sizeof(theHeap):
  164. result = cast[pointer](bumpPointer)
  165. inc bumpPointer, size
  166. else:
  167. raiseOutOfMem()
  168. proc osTryAllocPages(size: int): pointer {.inline.} =
  169. if size+bumpPointer < cast[int](addr theHeap) + sizeof(theHeap):
  170. result = cast[pointer](bumpPointer)
  171. inc bumpPointer, size
  172. proc osDeallocPages(p: pointer, size: int) {.inline.} =
  173. if bumpPointer-size == cast[int](p):
  174. dec bumpPointer, size
  175. else:
  176. {.error: "Port memory manager to your platform".}