/lib/

ame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#
#
#            Nim's Runtime Library
#        (c) Copyright 2015 Nim Contributors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## :Authors: Zahary Karadjov
##
## This module provides utilities for reserving portions of the
## address space of a program without consuming physical memory.
## It can be used to implement a dynamically resizable buffer that
## is guaranteed to remain in the same memory location. The buffer
## will be able to grow up to the size of the initially reserved
## portion of the address space.
##
## Unstable API.

from std/oserrors import raiseOSError, osLastError

when defined(nimPreviewSlimSystem):
  import std/assertions

template distance*(lhs, rhs: pointer): int =
  cast[int](rhs) - cast[int](lhs)

template shift*(p: pointer, distance: int): pointer =
  cast[pointer](cast[int](p) + distance)

type
  MemAccessFlags* = int

  ReservedMem* = object
    memStart: pointer
    usedMemEnd: pointer
    committedMemEnd: pointer
    memEnd: pointer
    maxCommittedAndUnusedPages: int
    accessFlags: MemAccessFlags

  ReservedMemSeq*[T] = object
    mem: ReservedMem

when defined(windows):
  import std/winlean
  import std/private/win_getsysteminfo

  proc getAllocationGranularity: uint =
    var sysInfo: SystemInfo
    getSystemInfo(addr sysInfo)
    return uint(sysInfo.dwAllocationGranularity)

  let allocationGranularity = getAllocationGranularity().int

  const
    memNoAccess = MemAccessFlags(PAGE_NOACCESS)
    memExec* = MemAccessFlags(PAGE_EXECUTE)
    memExecRead* = MemAccessFlags(PAGE_EXECUTE_READ)
    memExecReadWrite* = MemAccessFlags(PAGE_EXECUTE_READWRITE)
    memRead* = MemAccessFlags(PAGE_READONLY)
    memReadWrite* = MemAccessFlags(PAGE_READWRITE)

  template check(expr) =
    let r = expr
    if r == cast[typeof(r)](0):
      raiseOSError(osLastError())

else:
  import std/posix

  let allocationGranularity = sysconf(SC_PAGESIZE)

  let
    memNoAccess = MemAccessFlags(PROT_NONE)
    memExec* = MemAccessFlags(PROT_EXEC)
    memExecRead* = MemAccessFlags(PROT_EXEC or PROT_READ)
    memExecReadWrite* = MemAccessFlags(PROT_EXEC or PROT_READ or PROT_WRITE)
    memRead* = MemAccessFlags(PROT_READ)
    memReadWrite* = MemAccessFlags(PROT_READ or PROT_WRITE)

  template check(expr) =
    if not expr:
      raiseOSError(osLastError())

func nextAlignedOffset(n, alignment: int): int =
  result = n
  let m = n mod alignment
  if m != 0: result += alignment - m


when defined(windows):
  const
    MEM_DECOMMIT = 0x4000
    MEM_RESERVE = 0x2000
    MEM_COMMIT = 0x1000
  proc virtualFree(lpAddress: pointer, dwSize: int,
                   dwFreeType: int32): cint {.header: "<windows.h>", stdcall,
                   importc: "VirtualFree".}
  proc virtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
                    flProtect: int32): pointer {.
                    header: "<windows.h>", stdcall, importc: "VirtualAlloc".}

proc init*(T: type ReservedMem,
           maxLen: Natural,
           initLen: Natural = 0,
           initCommitLen = initLen,
           memStart = pointer(nil),
           accessFlags = memReadWrite,
           maxCommittedAndUnusedPages = 3): ReservedMem =

  assert initLen <= initCommitLen
  let commitSize = nextAlignedOffset(initCommitLen, allocationGranularity)

  when defined(windows):
    result.memStart = virtualAlloc(memStart, maxLen, MEM_RESERVE,
        accessFlags.cint)
    check result.memStart
    if commitSize > 0:
      check virtualAlloc(result.memStart, commitSize, MEM_COMMIT,
          accessFlags.cint)
  else:
    var allocFlags = MAP_PRIVATE or MAP_ANONYMOUS # or MAP_NORESERVE
                                                  # if memStart != nil:
                                                  #  allocFlags = allocFlags or MAP_FIXED_NOREPLACE
    result.memStart = mmap(memStart, maxLen, PROT_NONE, allocFlags, -1, 0)
    check result.memStart != MAP_FAILED
    if commitSize > 0:
      check mprotect(result.memStart, commitSize, cint(accessFlags)) == 0

  result.usedMemEnd = result.memStart.shift(initLen)
  result.committedMemEnd = result.memStart.shift(commitSize)
  result.memEnd = result.memStart.shift(maxLen)
  result.accessFlags = accessFlags
  result.maxCommittedAndUnusedPages = maxCommittedAndUnusedPages

func len*(m: ReservedMem): int =
  distance(m.memStart, m.usedMemEnd)

func commitedLen*(m: ReservedMem): int =
  distance(m.memStart, m.committedMemEnd)

func maxLen*(m: ReservedMem): int =
  distance(m.memStart, m.memEnd)

proc setLen*(m: var ReservedMem, newLen: int) =
  let len = m.len
  m.usedMemEnd = m.memStart.shift(newLen)
  if newLen > len:
    let d = distance(m.committedMemEnd, m.usedMemEnd)
    if d > 0:
      let commitExtensionSize = nextAlignedOffset(d, allocationGranularity)
      when defined(windows):
        check virtualAlloc(m.committedMemEnd, commitExtensionSize,
                           MEM_COMMIT, m.accessFlags.cint)
      else:
        check mprotect(m.committedMemEnd, commitExtensionSize,
            m.accessFlags.cint) == 0
  else:
    let d = distance(m.usedMemEnd, m.committedMemEnd) -
            m.maxCommittedAndUnusedPages * allocationGranularity
    if d > 0:
      let commitSizeShrinkage = nextAlignedOffset(d, allocationGranularity)
      let newCommitEnd = m.committedMemEnd.shift(-commitSizeShrinkage)

      when defined(windows):
        check virtualFree(newCommitEnd, commitSizeShrinkage, MEM_DECOMMIT)
      else:
        check posix_madvise(newCommitEnd, commitSizeShrinkage,
                            POSIX_MADV_DONTNEED) == 0

      m.committedMemEnd = newCommitEnd

proc init*(SeqType: type ReservedMemSeq,
           maxLen: Natural,
           initLen: Natural = 0,
           initCommitLen: Natural = 0,
           memStart = pointer(nil),
           accessFlags = memReadWrite,
           maxCommittedAndUnusedPages = 3): SeqType =

  let elemSize = sizeof(SeqType.T)
  result.mem = ReservedMem.init(maxLen * elemSize,
                                initLen * elemSize,
                                initCommitLen * elemSize,
                                memStart, accessFlags,
                                maxCommittedAndUnusedPages)

func `[]`*[T](s: ReservedMemSeq[T], pos: Natural): lent T =
  let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  rangeCheck elemAddr < s.mem.usedMemEnd
  result = (cast[ptr T](elemAddr))[]

func `[]`*[T](s: var ReservedMemSeq[T], pos: Natural): var T =
  let elemAddr = s.mem.memStart.shift(pos * sizeof(T))
  rangeCheck elemAddr < s.mem.usedMemEnd
  result = (cast[ptr T](elemAddr))[]

func `[]`*[T](s: ReservedMemSeq[T], rpos: BackwardsIndex): lent T =
  return s[int(s.len) - int(rpos)]

func `[]`*[T](s: var ReservedMemSeq[T], rpos: BackwardsIndex): var T =
  return s[int(s.len) - int(rpos)]

func len*[T](s: ReservedMemSeq[T]): int =
  s.mem.len div sizeof(T)

proc setLen*[T](s: var ReservedMemSeq[T], newLen: int) =
  # TODO call destructors
  s.mem.setLen(newLen * sizeof(T))

proc add*[T](s: var ReservedMemSeq[T], val: T) =
  let len = s.len
  s.setLen(len + 1)
  s[len] = val

proc pop*[T](s: var ReservedMemSeq[T]): T =
  assert s.usedMemEnd != s.memStart
  let lastIdx = s.len - 1
  result = s[lastIdx]
  s.setLen(lastIdx)

func commitedLen*[T](s: ReservedMemSeq[T]): int =
  s.mem.commitedLen div sizeof(T)

func maxLen*[T](s: ReservedMemSeq[T]): int =
  s.mem.maxLen div sizeof(T)