reservedmem.nim 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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 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 std/oserrors import raiseOSError, osLastError
  20. when defined(nimPreviewSlimSystem):
  21. import std/assertions
  22. template distance*(lhs, rhs: pointer): int =
  23. cast[int](rhs) - cast[int](lhs)
  24. template shift*(p: pointer, distance: int): pointer =
  25. cast[pointer](cast[int](p) + distance)
  26. type
  27. MemAccessFlags* = int
  28. ReservedMem* = object
  29. memStart: pointer
  30. usedMemEnd: pointer
  31. committedMemEnd: pointer
  32. memEnd: pointer
  33. maxCommittedAndUnusedPages: int
  34. accessFlags: MemAccessFlags
  35. ReservedMemSeq*[T] = object
  36. mem: ReservedMem
  37. when defined(windows):
  38. import std/winlean
  39. import std/private/win_getsysteminfo
  40. proc getAllocationGranularity: uint =
  41. var sysInfo: SystemInfo
  42. getSystemInfo(addr sysInfo)
  43. return uint(sysInfo.dwAllocationGranularity)
  44. let allocationGranularity = getAllocationGranularity().int
  45. const
  46. memNoAccess = MemAccessFlags(PAGE_NOACCESS)
  47. memExec* = MemAccessFlags(PAGE_EXECUTE)
  48. memExecRead* = MemAccessFlags(PAGE_EXECUTE_READ)
  49. memExecReadWrite* = MemAccessFlags(PAGE_EXECUTE_READWRITE)
  50. memRead* = MemAccessFlags(PAGE_READONLY)
  51. memReadWrite* = MemAccessFlags(PAGE_READWRITE)
  52. template check(expr) =
  53. let r = expr
  54. if r == cast[typeof(r)](0):
  55. raiseOSError(osLastError())
  56. else:
  57. import std/posix
  58. let allocationGranularity = sysconf(SC_PAGESIZE)
  59. let
  60. memNoAccess = MemAccessFlags(PROT_NONE)
  61. memExec* = MemAccessFlags(PROT_EXEC)
  62. memExecRead* = MemAccessFlags(PROT_EXEC or PROT_READ)
  63. memExecReadWrite* = MemAccessFlags(PROT_EXEC or PROT_READ or PROT_WRITE)
  64. memRead* = MemAccessFlags(PROT_READ)
  65. memReadWrite* = MemAccessFlags(PROT_READ or PROT_WRITE)
  66. template check(expr) =
  67. if not expr:
  68. raiseOSError(osLastError())
  69. func nextAlignedOffset(n, alignment: int): int =
  70. result = n
  71. let m = n mod alignment
  72. if m != 0: result += alignment - m
  73. when defined(windows):
  74. const
  75. MEM_DECOMMIT = 0x4000
  76. MEM_RESERVE = 0x2000
  77. MEM_COMMIT = 0x1000
  78. proc virtualFree(lpAddress: pointer, dwSize: int,
  79. dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
  80. importc: "VirtualFree".}
  81. proc virtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
  82. flProtect: int32): pointer {.
  83. header: "<windows.h>", stdcall, importc: "VirtualAlloc".}
  84. proc init*(T: type ReservedMem,
  85. maxLen: Natural,
  86. initLen: Natural = 0,
  87. initCommitLen = initLen,
  88. memStart = pointer(nil),
  89. accessFlags = memReadWrite,
  90. maxCommittedAndUnusedPages = 3): ReservedMem =
  91. assert initLen <= initCommitLen
  92. let commitSize = nextAlignedOffset(initCommitLen, allocationGranularity)
  93. when defined(windows):
  94. result.memStart = virtualAlloc(memStart, maxLen, MEM_RESERVE,
  95. accessFlags.cint)
  96. check result.memStart
  97. if commitSize > 0:
  98. check virtualAlloc(result.memStart, commitSize, MEM_COMMIT,
  99. accessFlags.cint)
  100. else:
  101. var allocFlags = MAP_PRIVATE or MAP_ANONYMOUS # or MAP_NORESERVE
  102. # if memStart != nil:
  103. # allocFlags = allocFlags or MAP_FIXED_NOREPLACE
  104. result.memStart = mmap(memStart, maxLen, PROT_NONE, allocFlags, -1, 0)
  105. check result.memStart != MAP_FAILED
  106. if commitSize > 0:
  107. check mprotect(result.memStart, commitSize, cint(accessFlags)) == 0
  108. result.usedMemEnd = result.memStart.shift(initLen)
  109. result.committedMemEnd = result.memStart.shift(commitSize)
  110. result.memEnd = result.memStart.shift(maxLen)
  111. result.accessFlags = accessFlags
  112. result.maxCommittedAndUnusedPages = maxCommittedAndUnusedPages
  113. func len*(m: ReservedMem): int =
  114. distance(m.memStart, m.usedMemEnd)
  115. func commitedLen*(m: ReservedMem): int =
  116. distance(m.memStart, m.committedMemEnd)
  117. func maxLen*(m: ReservedMem): int =
  118. distance(m.memStart, m.memEnd)
  119. proc setLen*(m: var ReservedMem, newLen: int) =
  120. let len = m.len
  121. m.usedMemEnd = m.memStart.shift(newLen)
  122. if newLen > len:
  123. let d = distance(m.committedMemEnd, m.usedMemEnd)
  124. if d > 0:
  125. let commitExtensionSize = nextAlignedOffset(d, allocationGranularity)
  126. when defined(windows):
  127. check virtualAlloc(m.committedMemEnd, commitExtensionSize,
  128. MEM_COMMIT, m.accessFlags.cint)
  129. else:
  130. check mprotect(m.committedMemEnd, commitExtensionSize,
  131. m.accessFlags.cint) == 0
  132. else:
  133. let d = distance(m.usedMemEnd, m.committedMemEnd) -
  134. m.maxCommittedAndUnusedPages * allocationGranularity
  135. if d > 0:
  136. let commitSizeShrinkage = nextAlignedOffset(d, allocationGranularity)
  137. let newCommitEnd = m.committedMemEnd.shift(-commitSizeShrinkage)
  138. when defined(windows):
  139. check virtualFree(newCommitEnd, commitSizeShrinkage, MEM_DECOMMIT)
  140. else:
  141. check posix_madvise(newCommitEnd, commitSizeShrinkage,
  142. POSIX_MADV_DONTNEED) == 0
  143. m.committedMemEnd = newCommitEnd
  144. proc init*(SeqType: type ReservedMemSeq,
  145. maxLen: Natural,
  146. initLen: Natural = 0,
  147. initCommitLen: Natural = 0,
  148. memStart = pointer(nil),
  149. accessFlags = memReadWrite,
  150. maxCommittedAndUnusedPages = 3): SeqType =
  151. let elemSize = sizeof(SeqType.T)
  152. result.mem = ReservedMem.init(maxLen * elemSize,
  153. initLen * elemSize,
  154. initCommitLen * elemSize,
  155. memStart, accessFlags,
  156. maxCommittedAndUnusedPages)
  157. func `[]`*[T](s: ReservedMemSeq[T], pos: Natural): lent T =
  158. let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  159. rangeCheck elemAddr < s.mem.usedMemEnd
  160. result = (cast[ptr T](elemAddr))[]
  161. func `[]`*[T](s: var ReservedMemSeq[T], pos: Natural): var T =
  162. let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  163. rangeCheck elemAddr < s.mem.usedMemEnd
  164. result = (cast[ptr T](elemAddr))[]
  165. func `[]`*[T](s: ReservedMemSeq[T], rpos: BackwardsIndex): lent T =
  166. return s[int(s.len) - int(rpos)]
  167. func `[]`*[T](s: var ReservedMemSeq[T], rpos: BackwardsIndex): var T =
  168. return s[int(s.len) - int(rpos)]
  169. func len*[T](s: ReservedMemSeq[T]): int =
  170. s.mem.len div sizeof(T)
  171. proc setLen*[T](s: var ReservedMemSeq[T], newLen: int) =
  172. # TODO call destructors
  173. s.mem.setLen(newLen * sizeof(T))
  174. proc add*[T](s: var ReservedMemSeq[T], val: T) =
  175. let len = s.len
  176. s.setLen(len + 1)
  177. s[len] = val
  178. proc pop*[T](s: var ReservedMemSeq[T]): T =
  179. assert s.usedMemEnd != s.memStart
  180. let lastIdx = s.len - 1
  181. result = s[lastIdx]
  182. s.setLen(lastIdx)
  183. func commitedLen*[T](s: ReservedMemSeq[T]): int =
  184. s.mem.commitedLen div sizeof(T)
  185. func maxLen*[T](s: ReservedMemSeq[T]): int =
  186. s.mem.maxLen div sizeof(T)