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
230
231
232
233
234
235
236
237
238
239
240
241
|
#
#
# 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 a 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 ospaths import raiseOSError, osLastError
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 winlean
type
SYSTEM_INFO {.final, pure.} = object
u1: uint32
dwPageSize: uint32
lpMinimumApplicationAddress: pointer
lpMaximumApplicationAddress: pointer
dwActiveProcessorMask: ptr uint32
dwNumberOfProcessors: uint32
dwProcessorType: uint32
dwAllocationGranularity: uint32
wProcessorLevel: uint16
wProcessorRevision: uint16
proc getSystemInfo(lpSystemInfo: ptr SYSTEM_INFO) {.stdcall,
dynlib: "kernel32", importc: "GetSystemInfo".}
proc getAllocationGranularity: uint =
var sysInfo: SYSTEM_INFO
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[type(r)](0):
raiseOSError(osLastError())
else:
import 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)
|