Home | History | Annotate | Download | only in runtime
      1 // Copyright 2010 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package runtime
      6 
      7 import (
      8 	"unsafe"
      9 )
     10 
     11 const (
     12 	_MEM_COMMIT   = 0x1000
     13 	_MEM_RESERVE  = 0x2000
     14 	_MEM_DECOMMIT = 0x4000
     15 	_MEM_RELEASE  = 0x8000
     16 
     17 	_PAGE_READWRITE = 0x0004
     18 	_PAGE_NOACCESS  = 0x0001
     19 
     20 	_ERROR_NOT_ENOUGH_MEMORY = 8
     21 	_ERROR_COMMITMENT_LIMIT  = 1455
     22 )
     23 
     24 // Don't split the stack as this function may be invoked without a valid G,
     25 // which prevents us from allocating more stack.
     26 //go:nosplit
     27 func sysAlloc(n uintptr, sysStat *uint64) unsafe.Pointer {
     28 	mSysStatInc(sysStat, n)
     29 	return unsafe.Pointer(stdcall4(_VirtualAlloc, 0, n, _MEM_COMMIT|_MEM_RESERVE, _PAGE_READWRITE))
     30 }
     31 
     32 func sysUnused(v unsafe.Pointer, n uintptr) {
     33 	r := stdcall3(_VirtualFree, uintptr(v), n, _MEM_DECOMMIT)
     34 	if r != 0 {
     35 		return
     36 	}
     37 
     38 	// Decommit failed. Usual reason is that we've merged memory from two different
     39 	// VirtualAlloc calls, and Windows will only let each VirtualFree handle pages from
     40 	// a single VirtualAlloc. It is okay to specify a subset of the pages from a single alloc,
     41 	// just not pages from multiple allocs. This is a rare case, arising only when we're
     42 	// trying to give memory back to the operating system, which happens on a time
     43 	// scale of minutes. It doesn't have to be terribly fast. Instead of extra bookkeeping
     44 	// on all our VirtualAlloc calls, try freeing successively smaller pieces until
     45 	// we manage to free something, and then repeat. This ends up being O(n log n)
     46 	// in the worst case, but that's fast enough.
     47 	for n > 0 {
     48 		small := n
     49 		for small >= 4096 && stdcall3(_VirtualFree, uintptr(v), small, _MEM_DECOMMIT) == 0 {
     50 			small /= 2
     51 			small &^= 4096 - 1
     52 		}
     53 		if small < 4096 {
     54 			print("runtime: VirtualFree of ", small, " bytes failed with errno=", getlasterror(), "\n")
     55 			throw("runtime: failed to decommit pages")
     56 		}
     57 		v = add(v, small)
     58 		n -= small
     59 	}
     60 }
     61 
     62 func sysUsed(v unsafe.Pointer, n uintptr) {
     63 	r := stdcall4(_VirtualAlloc, uintptr(v), n, _MEM_COMMIT, _PAGE_READWRITE)
     64 	if r == uintptr(v) {
     65 		return
     66 	}
     67 
     68 	// Commit failed. See SysUnused.
     69 	for n > 0 {
     70 		small := n
     71 		for small >= 4096 && stdcall4(_VirtualAlloc, uintptr(v), small, _MEM_COMMIT, _PAGE_READWRITE) == 0 {
     72 			small /= 2
     73 			small &^= 4096 - 1
     74 		}
     75 		if small < 4096 {
     76 			print("runtime: VirtualAlloc of ", small, " bytes failed with errno=", getlasterror(), "\n")
     77 			throw("runtime: failed to commit pages")
     78 		}
     79 		v = add(v, small)
     80 		n -= small
     81 	}
     82 }
     83 
     84 // Don't split the stack as this function may be invoked without a valid G,
     85 // which prevents us from allocating more stack.
     86 //go:nosplit
     87 func sysFree(v unsafe.Pointer, n uintptr, sysStat *uint64) {
     88 	mSysStatDec(sysStat, n)
     89 	r := stdcall3(_VirtualFree, uintptr(v), 0, _MEM_RELEASE)
     90 	if r == 0 {
     91 		print("runtime: VirtualFree of ", n, " bytes failed with errno=", getlasterror(), "\n")
     92 		throw("runtime: failed to release pages")
     93 	}
     94 }
     95 
     96 func sysFault(v unsafe.Pointer, n uintptr) {
     97 	// SysUnused makes the memory inaccessible and prevents its reuse
     98 	sysUnused(v, n)
     99 }
    100 
    101 func sysReserve(v unsafe.Pointer, n uintptr, reserved *bool) unsafe.Pointer {
    102 	*reserved = true
    103 	// v is just a hint.
    104 	// First try at v.
    105 	v = unsafe.Pointer(stdcall4(_VirtualAlloc, uintptr(v), n, _MEM_RESERVE, _PAGE_READWRITE))
    106 	if v != nil {
    107 		return v
    108 	}
    109 
    110 	// Next let the kernel choose the address.
    111 	return unsafe.Pointer(stdcall4(_VirtualAlloc, 0, n, _MEM_RESERVE, _PAGE_READWRITE))
    112 }
    113 
    114 func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) {
    115 	mSysStatInc(sysStat, n)
    116 	p := stdcall4(_VirtualAlloc, uintptr(v), n, _MEM_COMMIT, _PAGE_READWRITE)
    117 	if p != uintptr(v) {
    118 		errno := getlasterror()
    119 		print("runtime: VirtualAlloc of ", n, " bytes failed with errno=", errno, "\n")
    120 		switch errno {
    121 		case _ERROR_NOT_ENOUGH_MEMORY, _ERROR_COMMITMENT_LIMIT:
    122 			throw("out of memory")
    123 		default:
    124 			throw("runtime: cannot map pages in arena address space")
    125 		}
    126 	}
    127 }
    128