Import Debian changes 20180207-1
[hcoop/debian/mlton.git] / runtime / platform / windows.c
CommitLineData
7f918cf1
CE
1HANDLE fileDesHandle (int fd);
2
3#define BUFSIZE 512
4
5static HANDLE tempFileDes (void) {
6 /* Based on http://msdn.microsoft.com/en-us/library/aa363875(VS.85).aspx */
7 HANDLE hTempFile;
8 DWORD dwRetVal;
9 DWORD dwBufSize=BUFSIZE;
10 UINT uRetVal;
11 TCHAR szTempName[BUFSIZE];
12 TCHAR lpPathBuffer[BUFSIZE];
13
14 dwRetVal = GetTempPath(dwBufSize, lpPathBuffer);
15 if (dwRetVal > dwBufSize || (dwRetVal == 0))
16 die ("GetTempPath(%ld,...) failed with error %ld\n",
17 dwBufSize, GetLastError());
18 uRetVal = GetTempFileName(lpPathBuffer, TEXT("MLtonTempFile"), 0, szTempName);
19 if (uRetVal == 0)
20 die ("GetTempFileName(\"%s\",...) failed with error %ld\n",
21 lpPathBuffer, GetLastError());
22 hTempFile = CreateFile((LPTSTR) szTempName,
23 GENERIC_READ | GENERIC_WRITE,
24 0,
25 NULL,
26 TRUNCATE_EXISTING,
27 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE,
28 NULL);
29 if (hTempFile == INVALID_HANDLE_VALUE)
30 die ("CreateFile(\"%s\",...) failed with error %ld\n",
31 szTempName, GetLastError());
32 return hTempFile;
33}
34
35typedef struct {
36 HANDLE handle;
37} *WriteToDiskData;
38
39void GC_diskBack_read (void *data, pointer buf, size_t size) {
40 HANDLE h;
41 DWORD d;
42 DWORD dwBytesRead;
43
44 h = ((WriteToDiskData)data)->handle;
45 d = SetFilePointer (h, 0, NULL, FILE_BEGIN);
46 if (d == INVALID_SET_FILE_POINTER)
47 die ("SetFilePointer failed with error %ld\n", GetLastError());
48 unless (ReadFile(h, buf, size, &dwBytesRead, NULL))
49 die ("ReadFile failed with error %ld\n", GetLastError());
50}
51
52void GC_diskBack_close (void *data) {
53 HANDLE h;
54
55 h = ((WriteToDiskData)data)->handle;
56 unless (CloseHandle (h))
57 die ("CloseHandle failed with error %ld.", GetLastError());
58 free (data);
59}
60
61void *GC_diskBack_write (pointer buf, size_t size) {
62 HANDLE h;
63 WriteToDiskData d;
64 DWORD dwBytesWritten;
65
66 h = tempFileDes ();
67 unless (WriteFile (h, buf, size, &dwBytesWritten, NULL))
68 die ("WriteFile failed with error %ld\n", GetLastError());
69 d = (WriteToDiskData)(malloc_safe (sizeof(*d)));
70 d->handle = h;
71 return d;
72}
73
74static void displayMaps (void) {
75 MEMORY_BASIC_INFORMATION buf;
76 const char *state = "<unset>";
77 const char *protect = "<unset>";
78 uintptr_t address;
79
80 buf.RegionSize = 0;
81 for (address = 0;
82 address + buf.RegionSize >= address;
83 address += buf.RegionSize) {
84 if (0 == VirtualQuery ((LPCVOID)address, &buf, sizeof (buf)))
85 break;
86 if (0 == buf.RegionSize)
87 break;
88
89 switch (buf.Protect) {
90 case PAGE_READONLY:
91 protect = "PAGE_READONLY";
92 break;
93 case PAGE_READWRITE:
94 protect = "PAGE_READWRITE";
95 break;
96 case PAGE_WRITECOPY:
97 protect = "PAGE_WRITECOPY";
98 break;
99 case PAGE_EXECUTE:
100 protect = "PAGE_EXECUTE";
101 break;
102 case PAGE_EXECUTE_READ:
103 protect = "PAGE_EXECUTE_READ";
104 break;
105 case PAGE_EXECUTE_READWRITE:
106 protect = "PAGE_EXECUTE_READWRITE";
107 break;
108 case PAGE_EXECUTE_WRITECOPY:
109 protect = "PAGE_EXECUTE_WRITECOPY";
110 break;
111 case PAGE_GUARD:
112 protect = "PAGE_GUARD";
113 break;
114 case PAGE_NOACCESS:
115 protect = "PAGE_NOACCESS";
116 break;
117 case PAGE_NOCACHE:
118 protect = "PAGE_NOCACHE";
119 break;
120 default:
121 assert (FALSE);
122 }
123 switch (buf.State) {
124 case MEM_COMMIT:
125 state = "MEM_COMMIT";
126 break;
127 case MEM_FREE:
128 state = "MEM_FREE";
129 break;
130 case MEM_RESERVE:
131 state = "MEM_RESERVE";
132 break;
133 default:
134 assert (FALSE);
135 }
136
137 fprintf(stderr, FMTPTR " %10"PRIuMAX" %s %s\n",
138 (uintptr_t)buf.BaseAddress, (uintmax_t)buf.RegionSize,
139 state, protect);
140 }
141}
142
143void GC_displayMem (void) {
144#ifdef _WIN64
145 MEMORYSTATUSEX ms;
146 ms.dwLength = sizeof (MEMORYSTATUSEX);
147 GlobalMemoryStatusEx (&ms);
148
149 fprintf(stderr, "Total Phys. Mem: %"PRIuMAX"\n"
150 "Avail Phys. Mem: %"PRIuMAX"\n"
151 "Total Page File: %"PRIuMAX"\n"
152 "Avail Page File: %"PRIuMAX"\n"
153 "Total Virtual: %"PRIuMAX"\n"
154 "Avail Virtual: %"PRIuMAX"\n",
155 (uintmax_t)ms.ullTotalPhys,
156 (uintmax_t)ms.ullAvailPhys,
157 (uintmax_t)ms.ullTotalPageFile,
158 (uintmax_t)ms.ullAvailPageFile,
159 (uintmax_t)ms.ullTotalVirtual,
160 (uintmax_t)ms.ullAvailVirtual);
161#else
162 MEMORYSTATUS ms;
163 ms.dwLength = sizeof (MEMORYSTATUS);
164 GlobalMemoryStatus (&ms);
165
166 fprintf(stderr, "Total Phys. Mem: %"PRIuMAX"\n"
167 "Avail Phys. Mem: %"PRIuMAX"\n"
168 "Total Page File: %"PRIuMAX"\n"
169 "Avail Page File: %"PRIuMAX"\n"
170 "Total Virtual: %"PRIuMAX"\n"
171 "Avail Virtual: %"PRIuMAX"\n",
172 (uintmax_t)ms.dwTotalPhys,
173 (uintmax_t)ms.dwAvailPhys,
174 (uintmax_t)ms.dwTotalPageFile,
175 (uintmax_t)ms.dwAvailPageFile,
176 (uintmax_t)ms.dwTotalVirtual,
177 (uintmax_t)ms.dwAvailVirtual);
178#endif
179 displayMaps ();
180}
181
182static HANDLE dupHandle (int fd) {
183 HANDLE dupd;
184 HANDLE raw;
185
186 raw = fileDesHandle (fd);
187 if (raw == (HANDLE)-1 or raw == 0) {
188 errno = EBADF;
189 return 0;
190 }
191 /* 'Inspired' by http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/creating_a_child_process_with_redirected_input_and_output.asp
192 * It's interesting that you can open files for/from other processes...
193 */
194 unless (DuplicateHandle (
195 GetCurrentProcess(), /* source process */
196 raw, /* source handle */
197 GetCurrentProcess(), /* target process */
198 &dupd, /* target handle - valid in target proc */
199 0, /* ignored b/c DUPLICATE_SAME_ACCESS used */
200 TRUE, /* this can be inherited by children */
201 DUPLICATE_SAME_ACCESS))/* keep the same permissions */
202 {
203 errno = ENOMEM;
204 return 0;
205 }
206 return dupd;
207}
208
209/* Windows memory is allocated in two phases: reserve and commit.
210 * A reservation makes the address space unavailable to other reservations.
211 * Commiting reserved memory actually maps the reserved memory for use.
212 * Decommitting a portion of a reservation releases the physical memory only.
213 * The complicating detail is that one cannot partially release a reservation.
214 *
215 * The management routines below manage a 'heap' that is composed of several
216 * distinct reservations, laid out in the following order:
217 * 0+ reservations set MEM_COMMIT
218 * 1 reservation starting MEM_COMMIT with an optional MEM_RESERVE tail
219 *
220 * The heap always starts on a reservation and ends at where the MEM_RESERVE
221 * region (if any) begins.
222 */
223
224/* Create a new heap */
225static inline void *Windows_mmapAnon (void *base, size_t length) {
226 void *res;
227
228 /* We prevoiusly used "0" instead of start, which lead to crashes.
229 * After reading win32 documentation, the reason for these crashes
230 * becomes clear: we were using only MEM_COMMIT! If there was memory
231 * decommitted in a previous heap shrink, a new heap might end up
232 * inside the reserved (but uncommitted) memory. When the old heap is
233 * freed, it will kill the new heap as well. This bug will not happen
234 * now because we reserve, then commit. Reserved memory cannot conflict.
235 */
236 res = VirtualAlloc (base, length, MEM_RESERVE, PAGE_NOACCESS);
237 if (0 == res)
238 return (void*)-1;
239
240 /* Actually get the memory for use */
241 if (0 == VirtualAlloc (res, length, MEM_COMMIT, PAGE_READWRITE)) {
242 VirtualFree(res, 0, MEM_RELEASE);
243 return (void*)-1;
244 }
245
246 return res;
247}
248
249static inline void Windows_release (void *base, size_t length) {
250 MEMORY_BASIC_INFORMATION mi;
251
252 if (length == 0) return;
253
254 /* We might not be able to release the first reservation because
255 * it overlaps the base address we wish to keep. The idea is to
256 * decommit the part we don't need, and release all reservations
257 * that may be after this point.
258 */
259
260 if (0 == VirtualQuery(base, &mi, sizeof(mi)))
261 die("VirtualQuery failed");
262 assert (mi.State != MEM_FREE);
263 assert (mi.RegionSize <= length);
264
265 if (mi.AllocationBase != base) {
266 if (0 == VirtualFree(base, mi.RegionSize, MEM_DECOMMIT))
267 die("VirtualFree(MEM_DECOMMIT)");
268
269 /* Requery: the following region might also be decommit */
270 VirtualQuery(base, &mi, sizeof(mi));
271 assert (mi.State == MEM_RESERVE);
272
273 /* It's possible the consolidated reserved space is larger
274 * than the range we were asked to free. Bail out early.
275 */
276 if (mi.RegionSize >= length) return;
277
278 /* Skip decommited region and move to the next reservation */
279 base = (char*)base + mi.RegionSize;
280 length -= mi.RegionSize;
281 }
282
283 /* Clean-up the remaining tail. */
284 while (length > 0) {
285 if (0 == VirtualQuery(base, &mi, sizeof(mi)))
286 die("VirtualQuery");
287
288 /* We should never have a completely decommitted alloc */
289 assert (mi.State == MEM_COMMIT);
290 /* This method is supposed to only do complete releases */
291 assert (mi.AllocationBase == base);
292 /* The committed region should never exceed the length */
293 assert (mi.RegionSize <= length);
294
295 if (0 == VirtualFree(base, 0, MEM_RELEASE))
296 die("VirtualFree(MEM_RELEASE) failed");
297
298 base = (char*)base + mi.RegionSize;
299 length -= mi.RegionSize;
300 }
301
302 /* The last release also handled the optional MEM_RESERVE region */
303}
304
305/* Extend an existing heap */
306static inline void* Windows_extend (void *base, size_t length) {
307 MEMORY_BASIC_INFORMATION mi;
308 void *end;
309
310 /* Check the status of memory after the end of the allocation */
311 VirtualQuery(base, &mi, sizeof(mi));
312
313 if (mi.State == MEM_FREE) {
314 /* No tail of reserved memory -> simply try to allocate */
315 return Windows_mmapAnon(base, length);
316 } else if (mi.State == MEM_RESERVE) {
317 assert (mi.AllocationBase <= base);
318 end = (char*)base + mi.RegionSize;
319
320 if (mi.RegionSize > length) { /* only commit is needed */
321 if (0 == VirtualAlloc(base, length,
322 MEM_COMMIT, PAGE_READWRITE)) {
323 return (void*)-1;
324 } else {
325 return base;
326 }
327 } else if (end == Windows_mmapAnon(end, length-mi.RegionSize)) {
328 if (0 == VirtualAlloc(base, mi.RegionSize,
329 MEM_COMMIT, PAGE_READWRITE)) {
330 VirtualFree(end, 0, MEM_RELEASE);
331 return (void*)-1;
332 } else {
333 return base;
334 }
335 } else {
336 /* Failed to allocate tail */
337 return (void*)-1;
338 }
339 } else {
340 /* The memory is used by another mapping */
341 return (void*)-1;
342 }
343}
344
345C_Errno_t(C_PId_t)
346Windows_Process_create (NullString8_t cmds, NullString8_t args, NullString8_t envs,
347 C_Fd_t in, C_Fd_t out, C_Fd_t err) {
348 char *cmd;
349 char *arg;
350 char *env;
351 STARTUPINFO si;
352 PROCESS_INFORMATION pi;
353
354 cmd = (char*)cmds;
355 arg = (char*)args;
356 env = (char*)envs;
357
358 ZeroMemory (&si, sizeof(STARTUPINFO));
359 si.cb = sizeof(STARTUPINFO);
360 si.hStdInput = dupHandle (in);
361 si.hStdOutput = dupHandle (out);
362 si.hStdError = dupHandle (err);
363 si.dwFlags = STARTF_USESTDHANDLES; /* use the above */
364 if (!si.hStdInput or !si.hStdOutput or !si.hStdError) {
365 if (si.hStdInput) CloseHandle (si.hStdInput);
366 if (si.hStdOutput) CloseHandle (si.hStdOutput);
367 if (si.hStdError) CloseHandle (si.hStdError);
368 /* errno already faked by dupHandle */
369 return -1;
370 }
371 ZeroMemory (&pi, sizeof(PROCESS_INFORMATION));
372 unless (CreateProcess (
373 cmd, /* Module name */
374 arg, /* Command line */
375 NULL, /* Process handle not inheritable */
376 NULL, /* Thread handle not inheritable */
377 TRUE, /* Set handle inheritance to TRUE */
378 0, /* No creation flags */
379 env, /* Environment */
380 NULL, /* Use parent's starting directory */
381 &si, /* Pointer to STARTUPINFO structure */
382 &pi /* Pointer to PROCESS_INFORMATION structure */
383 )) {
384 errno = ENOENT; /* probably does not exist (aka ENOFILE)*/
385 return -1;
386 }
387 /* Process created successfully */
388 /* We will return the process handle for the 'pid'.
389 * This way we can TerminateProcess (kill) it and
390 * WaitForSingleObject/GetExitCodeProcess (reap) it.
391 * The thread handle is not needed, so clean it.
392 */
393 CloseHandle (pi.hThread);
394 CloseHandle (si.hStdInput);
395 CloseHandle (si.hStdOutput);
396 CloseHandle (si.hStdError);
397
398 return (C_PId_t)pi.hProcess;
399}
400
401C_Errno_t(C_PId_t)
402Windows_Process_createNull (NullString8_t cmds, NullString8_t args,
403 C_Fd_t in, C_Fd_t out, C_Fd_t err) {
404 return Windows_Process_create (cmds, args, NULL, in, out, err);
405}
406
407C_Errno_t(C_Int_t) Windows_Process_getexitcode (C_PId_t pid, Ref(C_Status_t) status) {
408 HANDLE h;
409
410 h = (HANDLE)pid;
411 unless (WaitForSingleObject (h, INFINITE) == WAIT_OBJECT_0) {
412 errno = ECHILD;
413 return -1;
414 }
415 unless (GetExitCodeProcess (h, (DWORD*)status)) {
416 errno = ECHILD;
417 return -1;
418 }
419 return 0;
420}
421
422C_Errno_t(C_Int_t) Windows_Process_terminate (C_PId_t pid, C_Signal_t sig) {
423 HANDLE h;
424
425 h = (HANDLE)pid;
426 unless (TerminateProcess (h, 0x80000000UL | sig)) {
427 errno = ECHILD;
428 return -1;
429 }
430 return 0;
431}