1 // Written in the D programming language.
2 /**
3 Source: $(PHOBOSSRC std/experimental/allocator/_mmap_allocator.d)
4 */
5 module std.experimental.allocator.mmap_allocator;
6 
7 /**
8 Allocator (currently defined only for Posix and Windows) using
9 $(D $(LINK2 https://en.wikipedia.org/wiki/Mmap, mmap))
10 and $(D $(LUCKY munmap)) directly (or their Windows equivalents). There is no
11 additional structure: each call to `allocate(s)` issues a call to
12 $(D mmap(null, s, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)),
13 and each call to `deallocate(b)` issues $(D munmap(b.ptr, b.length)).
14 So `MmapAllocator` is usually intended for allocating large chunks to be
15 managed by fine-granular allocators.
16 */
17 struct MmapAllocator
18 {
19     /// The one shared instance.
20     static shared const MmapAllocator instance;
21 
22     /**
23     Alignment is page-size and hardcoded to 4096 (even though on certain systems
24     it could be larger).
25     */
26     enum size_t alignment = 4096;
27 
28     version (Posix)
29     {
30         /// Allocator API.
31         pure nothrow @nogc @safe
32         void[] allocate(size_t bytes) shared const
33         {
34             import core.sys.posix.sys.mman : MAP_ANON, PROT_READ,
35                 PROT_WRITE, MAP_PRIVATE, MAP_FAILED;
36             if (!bytes) return null;
37             const errnosave = (() @trusted => fakePureErrno())(); // For purity revert changes to errno.
38             auto p = (() @trusted => fakePureMmap(null, bytes, PROT_READ | PROT_WRITE,
39                 MAP_PRIVATE | MAP_ANON, -1, 0))();
40             if (p is MAP_FAILED)
41             {
42                 (() @trusted => fakePureErrno() = errnosave)(); // errno only changed on MAP_FAILED.
43                 return null;
44             }
45             return (() @trusted => p[0 .. bytes])();
46         }
47 
48         /// Ditto
49         pure nothrow @nogc
50         bool deallocate(void[] b) shared const
51         {
52             // Because we assert(0) on error we don't need to reset errno for purity.
53             if (b.ptr) fakePureMunmap(b.ptr, b.length) == 0 || assert(0);
54             return true;
55         }
56 
57         // Anonymous mmap might be zero-filled on all Posix systems but
58         // not all commit to this in the documentation.
59         version (linux)
60             // http://man7.org/linux/man-pages/man2/mmap.2.html
61             package alias allocateZeroed = allocate;
62         else version (NetBSD)
63             // https://man.netbsd.org/mmap.2
64             package alias allocateZeroed = allocate;
65         else version (Solaris)
66             // https://docs.oracle.com/cd/E88353_01/html/E37841/mmap-2.html
67             package alias allocateZeroed = allocate;
68         else version (AIX)
69             // https://www.ibm.com/support/knowledgecenter/en/ssw_aix_71/com.ibm.aix.basetrf1/mmap.htm
70             package alias allocateZeroed = allocate;
71     }
72     else version (Windows)
73     {
74         import core.sys.windows.winnt : MEM_COMMIT, PAGE_READWRITE, MEM_RELEASE;
75 
76         /// Allocator API.
77         pure nothrow @nogc @safe
78         void[] allocate(size_t bytes) shared const
79         {
80             if (!bytes) return null;
81             // For purity ensure last-error does not visibly change.
82             const lastErrorSave = (() @trusted => GetLastError())();
83             auto p = (() @trusted => VirtualAlloc(null, bytes, MEM_COMMIT, PAGE_READWRITE))();
84             if (p == null)
85             {
86                 // Last-error only changed if allocation failed.
87                 (() @trusted => SetLastError(lastErrorSave))();
88                 return null;
89             }
90             return (() @trusted => p[0 .. bytes])();
91         }
92 
93         /// Ditto
94         pure nothrow @nogc
95         bool deallocate(void[] b) shared const
96         {
97             const lastErrorSave = GetLastError(); // For purity ensure last-error does not visibly change.
98             scope(exit) SetLastError(lastErrorSave);
99             return b.ptr is null || VirtualFree(b.ptr, 0, MEM_RELEASE) != 0;
100         }
101 
102         package alias allocateZeroed = allocate;
103     }
104 }
105 
106 // pure wrappers around `mmap` and `munmap` because they are used here locally
107 // solely to perform allocation and deallocation which in this case is `pure`
108 version (Posix)
109 extern (C) private pure @system @nogc nothrow
110 {
111     import core.sys.posix.sys.types : off_t;
112     pragma(mangle, "fakePureErrnoImpl") ref int fakePureErrno();
113     pragma(mangle, "mmap") void* fakePureMmap(void*, size_t, int, int, int, off_t);
114     pragma(mangle, "munmap") int fakePureMunmap(void*, size_t);
115 }
116 
117 // Pure wrappers around VirtualAlloc/VirtualFree for use here only. Their use is sound
118 // because when we call them we ensure that last-error is not visibly changed.
119 version (Windows)
120 extern (Windows) private pure @system @nogc nothrow
121 {
122     import core.sys.windows.basetsd : SIZE_T;
123     import core.sys.windows.windef : BOOL, DWORD;
124     import core.sys.windows.winnt : LPVOID, PVOID;
125 
126     DWORD GetLastError();
127     void SetLastError(DWORD);
128     PVOID VirtualAlloc(PVOID, SIZE_T, DWORD, DWORD);
129     BOOL VirtualFree(PVOID, SIZE_T, DWORD);
130 }
131 
132 pure nothrow @safe @nogc unittest
133 {
134     alias alloc = MmapAllocator.instance;
135     auto p = alloc.allocate(100);
136     assert(p.length == 100);
137     () @trusted { alloc.deallocate(p); p = null; }();
138 }