Extracts platform-specific runtime code into its own package (#608)
This moves the platform-specific runtime code (currently only used by the compiler) into its own package. Specifically, this moves the mmap logic, and in doing so makes it easier to test, for example new operating systems. This also backfills missing RATIONALE about x/sys and hints at a future possibility to allow a plugin. However, the next step is to get FreeBSD working natively on the compiler without any additional dependencies. See #607 Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
108
internal/platform/mmap_windows.go
Normal file
108
internal/platform/mmap_windows.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
procVirtualAlloc = kernel32.NewProc("VirtualAlloc")
|
||||
procVirtualProtect = kernel32.NewProc("VirtualProtect")
|
||||
procVirtualFree = kernel32.NewProc("VirtualFree")
|
||||
)
|
||||
|
||||
const (
|
||||
windows_MEM_COMMIT uintptr = 0x00001000
|
||||
windows_MEM_RELEASE uintptr = 0x00008000
|
||||
windows_PAGE_READWRITE uintptr = 0x00000004
|
||||
windows_PAGE_EXECUTE_READ uintptr = 0x00000020
|
||||
windows_PAGE_EXECUTE_READWRITE uintptr = 0x00000040
|
||||
)
|
||||
|
||||
func munmapCodeSegment(code []byte) error {
|
||||
return freeMemory(code)
|
||||
}
|
||||
|
||||
// allocateMemory commits the memory region via the "VirtualAlloc" function.
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc
|
||||
func allocateMemory(code []byte, protect uintptr) (uintptr, error) {
|
||||
address := uintptr(0) // TODO: document why zero
|
||||
size := uintptr(len(code))
|
||||
alloctype := windows_MEM_COMMIT
|
||||
if r, _, err := procVirtualAlloc.Call(address, size, alloctype, protect); r == 0 {
|
||||
return 0, fmt.Errorf("compiler: VirtualAlloc error: %w", ensureErr(err))
|
||||
} else {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
// freeMemory releases the memory region via the "VirtualFree" function.
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree
|
||||
func freeMemory(code []byte) error {
|
||||
address := uintptr(unsafe.Pointer(&code[0]))
|
||||
size := uintptr(0) // size must be 0 because we're using MEM_RELEASE.
|
||||
freetype := windows_MEM_RELEASE
|
||||
if r, _, err := procVirtualFree.Call(address, size, freetype); r == 0 {
|
||||
return fmt.Errorf("compiler: VirtualFree error: %w", ensureErr(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func virtualProtect(address, size, newprotect uintptr, oldprotect *uint32) error {
|
||||
if r, _, err := procVirtualProtect.Call(address, size, newprotect, uintptr(unsafe.Pointer(oldprotect))); r == 0 {
|
||||
return fmt.Errorf("compiler: VirtualProtect error: %w", ensureErr(err))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mmapCodeSegmentAMD64(code []byte) ([]byte, error) {
|
||||
p, err := allocateMemory(code, windows_PAGE_EXECUTE_READWRITE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mem []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
|
||||
sh.Data = p
|
||||
sh.Len = len(code)
|
||||
sh.Cap = len(code)
|
||||
copy(mem, code)
|
||||
return mem, nil
|
||||
}
|
||||
|
||||
func mmapCodeSegmentARM64(code []byte) ([]byte, error) {
|
||||
p, err := allocateMemory(code, windows_PAGE_READWRITE)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mem []byte
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem))
|
||||
sh.Data = p
|
||||
sh.Len = len(code)
|
||||
sh.Cap = len(code)
|
||||
copy(mem, code)
|
||||
|
||||
old := uint32(windows_PAGE_READWRITE)
|
||||
err = virtualProtect(p, uintptr(len(code)), windows_PAGE_EXECUTE_READ, &old)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return mem, nil
|
||||
}
|
||||
|
||||
// ensureErr returns syscall.EINVAL when the input error is nil.
|
||||
//
|
||||
// We are supposed to use "GetLastError" which is more precise, but it is not safe to execute in goroutines. While
|
||||
// "GetLastError" is thread-local, goroutines are not pinned to threads.
|
||||
//
|
||||
// See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror
|
||||
func ensureErr(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.EINVAL
|
||||
}
|
||||
Reference in New Issue
Block a user