reservedmem.nim 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Nim Contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## :Authors: Zahary Karadjov
  10. ##
  11. ## This module provides utilities for reserving a portions of the
  12. ## address space of a program without consuming physical memory.
  13. ## It can be used to implement a dynamically resizable buffer that
  14. ## is guaranteed to remain in the same memory location. The buffer
  15. ## will be able to grow up to the size of the initially reserved
  16. ## portion of the address space.
  17. ##
  18. ## Unstable API.
  19. from ospaths import raiseOSError, osLastError
  20. template distance*(lhs, rhs: pointer): int =
  21. cast[int](rhs) - cast[int](lhs)
  22. template shift*(p: pointer, distance: int): pointer =
  23. cast[pointer](cast[int](p) + distance)
  24. type
  25. MemAccessFlags* = int
  26. ReservedMem* = object
  27. memStart: pointer
  28. usedMemEnd: pointer
  29. committedMemEnd: pointer
  30. memEnd: pointer
  31. maxCommittedAndUnusedPages: int
  32. accessFlags: MemAccessFlags
  33. ReservedMemSeq*[T] = object
  34. mem: ReservedMem
  35. when defined(windows):
  36. import winlean
  37. type
  38. SYSTEM_INFO {.final, pure.} = object
  39. u1: uint32
  40. dwPageSize: uint32
  41. lpMinimumApplicationAddress: pointer
  42. lpMaximumApplicationAddress: pointer
  43. dwActiveProcessorMask: ptr uint32
  44. dwNumberOfProcessors: uint32
  45. dwProcessorType: uint32
  46. dwAllocationGranularity: uint32
  47. wProcessorLevel: uint16
  48. wProcessorRevision: uint16
  49. proc getSystemInfo(lpSystemInfo: ptr SYSTEM_INFO) {.stdcall,
  50. dynlib: "kernel32", importc: "GetSystemInfo".}
  51. proc getAllocationGranularity: uint =
  52. var sysInfo: SYSTEM_INFO
  53. getSystemInfo(addr sysInfo)
  54. return uint(sysInfo.dwAllocationGranularity)
  55. let allocationGranularity = getAllocationGranularity().int
  56. const
  57. memNoAccess = MemAccessFlags(PAGE_NOACCESS)
  58. memExec* = MemAccessFlags(PAGE_EXECUTE)
  59. memExecRead* = MemAccessFlags(PAGE_EXECUTE_READ)
  60. memExecReadWrite* = MemAccessFlags(PAGE_EXECUTE_READWRITE)
  61. memRead* = MemAccessFlags(PAGE_READONLY)
  62. memReadWrite* = MemAccessFlags(PAGE_READWRITE)
  63. template check(expr) =
  64. let r = expr
  65. if r == cast[type(r)](0):
  66. raiseOSError(osLastError())
  67. else:
  68. import posix
  69. let allocationGranularity = sysconf(SC_PAGESIZE)
  70. let
  71. memNoAccess = MemAccessFlags(PROT_NONE)
  72. memExec* = MemAccessFlags(PROT_EXEC)
  73. memExecRead* = MemAccessFlags(PROT_EXEC or PROT_READ)
  74. memExecReadWrite* = MemAccessFlags(PROT_EXEC or PROT_READ or PROT_WRITE)
  75. memRead* = MemAccessFlags(PROT_READ)
  76. memReadWrite* = MemAccessFlags(PROT_READ or PROT_WRITE)
  77. template check(expr) =
  78. if not expr:
  79. raiseOSError(osLastError())
  80. func nextAlignedOffset(n, alignment: int): int =
  81. result = n
  82. let m = n mod alignment
  83. if m != 0: result += alignment - m
  84. when defined(windows):
  85. const
  86. MEM_DECOMMIT = 0x4000
  87. MEM_RESERVE = 0x2000
  88. MEM_COMMIT = 0x1000
  89. proc virtualFree(lpAddress: pointer, dwSize: int,
  90. dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
  91. importc: "VirtualFree".}
  92. proc virtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
  93. flProtect: int32): pointer {.
  94. header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
  95. proc init*(T: type ReservedMem,
  96. maxLen: Natural,
  97. initLen: Natural = 0,
  98. initCommitLen = initLen,
  99. memStart = pointer(nil),
  100. accessFlags = memReadWrite,
  101. maxCommittedAndUnusedPages = 3): ReservedMem =
  102. assert initLen <= initCommitLen
  103. let commitSize = nextAlignedOffset(initCommitLen, allocationGranularity)
  104. when defined(windows):
  105. result.memStart = virtualAlloc(memStart, maxLen, MEM_RESERVE,
  106. accessFlags.cint)
  107. check result.memStart
  108. if commitSize > 0:
  109. check virtualAlloc(result.memStart, commitSize, MEM_COMMIT,
  110. accessFlags.cint)
  111. else:
  112. var allocFlags = MAP_PRIVATE or MAP_ANONYMOUS # or MAP_NORESERVE
  113. # if memStart != nil:
  114. # allocFlags = allocFlags or MAP_FIXED_NOREPLACE
  115. result.memStart = mmap(memStart, maxLen, PROT_NONE, allocFlags, -1, 0)
  116. check result.memStart != MAP_FAILED
  117. if commitSize > 0:
  118. check mprotect(result.memStart, commitSize, cint(accessFlags)) == 0
  119. result.usedMemEnd = result.memStart.shift(initLen)
  120. result.committedMemEnd = result.memStart.shift(commitSize)
  121. result.memEnd = result.memStart.shift(maxLen)
  122. result.accessFlags = accessFlags
  123. result.maxCommittedAndUnusedPages = maxCommittedAndUnusedPages
  124. func len*(m: ReservedMem): int =
  125. distance(m.memStart, m.usedMemEnd)
  126. func commitedLen*(m: ReservedMem): int =
  127. distance(m.memStart, m.committedMemEnd)
  128. func maxLen*(m: ReservedMem): int =
  129. distance(m.memStart, m.memEnd)
  130. proc setLen*(m: var ReservedMem, newLen: int) =
  131. let len = m.len
  132. m.usedMemEnd = m.memStart.shift(newLen)
  133. if newLen > len:
  134. let d = distance(m.committedMemEnd, m.usedMemEnd)
  135. if d > 0:
  136. let commitExtensionSize = nextAlignedOffset(d, allocationGranularity)
  137. when defined(windows):
  138. check virtualAlloc(m.committedMemEnd, commitExtensionSize,
  139. MEM_COMMIT, m.accessFlags.cint)
  140. else:
  141. check mprotect(m.committedMemEnd, commitExtensionSize,
  142. m.accessFlags.cint) == 0
  143. else:
  144. let d = distance(m.usedMemEnd, m.committedMemEnd) -
  145. m.maxCommittedAndUnusedPages * allocationGranularity
  146. if d > 0:
  147. let commitSizeShrinkage = nextAlignedOffset(d, allocationGranularity)
  148. let newCommitEnd = m.committedMemEnd.shift(-commitSizeShrinkage)
  149. when defined(windows):
  150. check virtualFree(newCommitEnd, commitSizeShrinkage, MEM_DECOMMIT)
  151. else:
  152. check posix_madvise(newCommitEnd, commitSizeShrinkage,
  153. POSIX_MADV_DONTNEED) == 0
  154. m.committedMemEnd = newCommitEnd
  155. proc init*(SeqType: type ReservedMemSeq,
  156. maxLen: Natural,
  157. initLen: Natural = 0,
  158. initCommitLen: Natural = 0,
  159. memStart = pointer(nil),
  160. accessFlags = memReadWrite,
  161. maxCommittedAndUnusedPages = 3): SeqType =
  162. let elemSize = sizeof(SeqType.T)
  163. result.mem = ReservedMem.init(maxLen * elemSize,
  164. initLen * elemSize,
  165. initCommitLen * elemSize,
  166. memStart, accessFlags,
  167. maxCommittedAndUnusedPages)
  168. func `[]`*[T](s: ReservedMemSeq[T], pos: Natural): lent T =
  169. let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  170. rangeCheck elemAddr < s.mem.usedMemEnd
  171. result = (cast[ptr T](elemAddr))[]
  172. func `[]`*[T](s: var ReservedMemSeq[T], pos: Natural): var T =
  173. let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  174. rangeCheck elemAddr < s.mem.usedMemEnd
  175. result = (cast[ptr T](elemAddr))[]
  176. func `[]`*[T](s: ReservedMemSeq[T], rpos: BackwardsIndex): lent T =
  177. return s[int(s.len) - int(rpos)]
  178. func `[]`*[T](s: var ReservedMemSeq[T], rpos: BackwardsIndex): var T =
  179. return s[int(s.len) - int(rpos)]
  180. func len*[T](s: ReservedMemSeq[T]): int =
  181. s.mem.len div sizeof(T)
  182. proc setLen*[T](s: var ReservedMemSeq[T], newLen: int) =
  183. # TODO call destructors
  184. s.mem.setLen(newLen * sizeof(T))
  185. proc add*[T](s: var ReservedMemSeq[T], val: T) =
  186. let len = s.len
  187. s.setLen(len + 1)
  188. s[len] = val
  189. proc pop*[T](s: var ReservedMemSeq[T]): T =
  190. assert s.usedMemEnd != s.memStart
  191. let lastIdx = s.len - 1
  192. result = s[lastIdx]
  193. s.setLen(lastIdx)
  194. func commitedLen*[T](s: ReservedMemSeq[T]): int =
  195. s.mem.commitedLen div sizeof(T)
  196. func maxLen*[T](s: ReservedMemSeq[T]): int =
  197. s.mem.maxLen div sizeof(T)