1/*
2    Title:  osomem.cpp - Interface to OS memory management - Windows version
3
4    Copyright (c) 2006, 2017-18, 2020 David C.J. Matthews
5
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Lesser General Public
8    License version 2.1 as published by the Free Software Foundation.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19*/
20
21#ifdef HAVE_CONFIG_H
22#include "config.h"
23#elif defined(_WIN32)
24#include "winconfig.h"
25#else
26#error "No configuration file"
27#endif
28
29#ifdef HAVE_ASSERT_H
30#include <assert.h>
31#define ASSERT(x)   assert(x)
32#else
33#define ASSERT(x)
34#endif
35
36#include "osmem.h"
37#include "bitmap.h"
38#include "locking.h"
39
40// Use Windows memory management.
41#include <windows.h>
42
43#ifdef POLYML32IN64
44OSMem::OSMem()
45{
46    memBase = 0;
47}
48
49OSMem::~OSMem()
50{
51}
52
53bool OSMem::Initialise(enum _MemUsage usage, size_t space /* = 0 */, void** pBase /* = 0 */)
54{
55    memUsage = usage;
56    // Get the page size and round up to that multiple.
57    SYSTEM_INFO sysInfo;
58    GetSystemInfo(&sysInfo);
59    // Get the page size.  Put it in a size_t variable otherwise the rounding
60    // up of "space" may go wrong on 64-bits.
61    pageSize = sysInfo.dwPageSize;
62
63    memBase = (char*)VirtualAlloc(0, space, MEM_RESERVE, PAGE_NOACCESS);
64    if (memBase == 0) return 0;
65    // We need the heap to be such that the top 32-bits are non-zero.
66    if ((uintptr_t)memBase < ((uintptr_t)1 << 32))
67    {
68        // Allocate again.
69        void* newSpace = VirtualAlloc(0, space, MEM_RESERVE, PAGE_NOACCESS);
70        VirtualFree(memBase, 0, MEM_RELEASE); // Free the old area that isn't suitable.
71        // Return what we got, or zero if it failed.
72        memBase = (char*)newSpace;
73    }
74
75    if (pBase != 0) *pBase = memBase;
76
77    // Create a bitmap with a bit for each page.
78    if (!pageMap.Create(space / pageSize))
79        return false;
80    lastAllocated = space / pageSize; // Beyond the last page in the area
81    // Set the last bit in the area so that we don't use it.
82    // This is effectively a work-around for a problem with the heap.
83    // If we have a zero-sized cell at the end of the memory its address is
84    // going to be zero.  This causes problems with forwarding pointers.
85    // There may be better ways of doing this.
86    pageMap.SetBit(space / pageSize - 1);
87    return true;
88}
89
90void* OSMem::AllocateDataArea(size_t& space)
91{
92    char* baseAddr;
93    {
94        PLocker l(&bitmapLock);
95        uintptr_t pages = (space + pageSize - 1) / pageSize;
96        // Round up to an integral number of pages.
97        space = pages * pageSize;
98        // Find some space
99        while (pageMap.TestBit(lastAllocated - 1)) // Skip the wholly allocated area.
100            lastAllocated--;
101        uintptr_t free = pageMap.FindFree(0, lastAllocated, pages);
102        if (free == lastAllocated)
103            return 0; // Can't find the space.
104        pageMap.SetBits(free, pages);
105        // TODO: Do we need to zero this?  It may have previously been set.
106        baseAddr = memBase + free * pageSize;
107    }
108    return VirtualAlloc(baseAddr, space, MEM_COMMIT, PAGE_READWRITE);
109}
110
111bool OSMem::FreeDataArea(void* p, size_t space)
112{
113    char* addr = (char*)p;
114    uintptr_t offset = (addr - memBase) / pageSize;
115    if (!VirtualFree(p, space, MEM_DECOMMIT))
116        return false;
117    uintptr_t pages = space / pageSize;
118    {
119        PLocker l(&bitmapLock);
120        pageMap.ClearBits(offset, pages);
121        if (offset + pages > lastAllocated) // We allocate from the top down.
122            lastAllocated = offset + pages;
123    }
124    return true;
125}
126
127void* OSMem::AllocateCodeArea(size_t& space, void*& shadowArea)
128{
129    char* baseAddr;
130    {
131        PLocker l(&bitmapLock);
132        uintptr_t pages = (space + pageSize - 1) / pageSize;
133        // Round up to an integral number of pages.
134        space = pages * pageSize;
135        // Find some space
136        while (pageMap.TestBit(lastAllocated - 1)) // Skip the wholly allocated area.
137            lastAllocated--;
138        uintptr_t free = pageMap.FindFree(0, lastAllocated, pages);
139        if (free == lastAllocated)
140            return 0; // Can't find the space.
141        pageMap.SetBits(free, pages);
142        // TODO: Do we need to zero this?  It may have previously been set.
143        baseAddr = memBase + free * pageSize;
144    }
145
146    void* dataArea =
147        VirtualAlloc(baseAddr, space, MEM_COMMIT, memUsage == UsageExecutableCode ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
148    shadowArea = dataArea;
149    return dataArea;
150}
151
152bool OSMem::FreeCodeArea(void* codeAddr, void* dataAddr, size_t space)
153{
154    ASSERT(codeAddr == dataAddr);
155    char* addr = (char*)codeAddr;
156    uintptr_t offset = (addr - memBase) / pageSize;
157    if (! VirtualFree(codeAddr, space, MEM_DECOMMIT))
158        return false;
159    uintptr_t pages = space / pageSize;
160    {
161        PLocker l(&bitmapLock);
162        pageMap.ClearBits(offset, pages);
163        if (offset + pages > lastAllocated) // We allocate from the top down.
164            lastAllocated = offset + pages;
165    }
166    return true;
167}
168
169bool OSMem::EnableWrite(bool enable, void* p, size_t space)
170{
171    DWORD oldProtect;
172    return VirtualProtect(p, space, enable ? PAGE_READWRITE : PAGE_READONLY, &oldProtect) == TRUE;
173}
174
175bool OSMem::DisableWriteForCode(void* codeAddr, void* dataAddr, size_t space)
176{
177    ASSERT(codeAddr == dataAddr);
178    DWORD oldProtect;
179    return VirtualProtect(codeAddr, space,
180        memUsage == UsageExecutableCode ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldProtect) == TRUE;
181}
182
183#else
184
185// Native address versions
186OSMem::OSMem()
187{
188}
189
190OSMem::~OSMem()
191{
192}
193
194bool OSMem::Initialise(enum _MemUsage usage, size_t space /* = 0 */, void **pBase /* = 0 */)
195{
196    memUsage = usage;
197    // Get the page size and round up to that multiple.
198    SYSTEM_INFO sysInfo;
199    GetSystemInfo(&sysInfo);
200    // Get the page size.  Put it in a size_t variable otherwise the rounding
201    // up of "space" may go wrong on 64-bits.
202    pageSize = sysInfo.dwPageSize;
203    return true;
204}
205
206// Allocate space and return a pointer to it.  The size is the minimum
207// size requested and it is updated with the actual space allocated.
208// Returns NULL if it cannot allocate the space.
209void *OSMem::AllocateDataArea(size_t &space)
210{
211    space = (space + pageSize - 1) & ~(pageSize - 1);
212    DWORD options = MEM_RESERVE | MEM_COMMIT;
213    return VirtualAlloc(0, space, options, PAGE_READWRITE);
214}
215
216// Release the space previously allocated.  This must free the whole of
217// the segment.  The space must be the size actually allocated.
218bool OSMem::FreeDataArea(void *p, size_t space)
219{
220    return VirtualFree(p, 0, MEM_RELEASE) == TRUE;
221}
222
223// Adjust the permissions on a segment.  This must apply to the
224// whole of a segment.
225bool OSMem::EnableWrite(bool enable, void* p, size_t space)
226{
227    DWORD oldProtect;
228    return VirtualProtect(p, space, enable ? PAGE_READWRITE: PAGE_READONLY, &oldProtect) == TRUE;
229}
230
231void* OSMem::AllocateCodeArea(size_t& space, void*& shadowArea)
232{
233    space = (space + pageSize - 1) & ~(pageSize - 1);
234    DWORD options = MEM_RESERVE | MEM_COMMIT;
235    void * dataAddr = VirtualAlloc(0, space, options,
236        memUsage == UsageExecutableCode ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE);
237    shadowArea = dataAddr;
238    return dataAddr;
239}
240
241bool OSMem::FreeCodeArea(void* codeAddr, void* dataAddr, size_t space)
242{
243    ASSERT(codeAddr == dataAddr);
244    return VirtualFree(codeAddr, 0, MEM_RELEASE) == TRUE;
245}
246
247bool OSMem::DisableWriteForCode(void* codeAddr, void* dataAddr, size_t space)
248{
249    ASSERT(codeAddr == dataAddr);
250    DWORD oldProtect;
251    return VirtualProtect(codeAddr, space,
252        memUsage == UsageExecutableCode ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldProtect) == TRUE;
253}
254
255#endif
256
257