wa-lang.org/wazero@v1.0.2/internal/platform/mmap_windows.go (about) 1 //go:build windows 2 3 package platform 4 5 import ( 6 "fmt" 7 "io" 8 "reflect" 9 "syscall" 10 "unsafe" 11 ) 12 13 var ( 14 kernel32 = syscall.NewLazyDLL("kernel32.dll") 15 procVirtualAlloc = kernel32.NewProc("VirtualAlloc") 16 procVirtualProtect = kernel32.NewProc("VirtualProtect") 17 procVirtualFree = kernel32.NewProc("VirtualFree") 18 ) 19 20 const ( 21 windows_MEM_COMMIT uintptr = 0x00001000 22 windows_MEM_RELEASE uintptr = 0x00008000 23 windows_PAGE_READWRITE uintptr = 0x00000004 24 windows_PAGE_EXECUTE_READ uintptr = 0x00000020 25 windows_PAGE_EXECUTE_READWRITE uintptr = 0x00000040 26 ) 27 28 func munmapCodeSegment(code []byte) error { 29 return freeMemory(code) 30 } 31 32 // allocateMemory commits the memory region via the "VirtualAlloc" function. 33 // See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc 34 func allocateMemory(size uintptr, protect uintptr) (uintptr, error) { 35 address := uintptr(0) // TODO: document why zero 36 alloctype := windows_MEM_COMMIT 37 if r, _, err := procVirtualAlloc.Call(address, size, alloctype, protect); r == 0 { 38 return 0, fmt.Errorf("compiler: VirtualAlloc error: %w", ensureErr(err)) 39 } else { 40 return r, nil 41 } 42 } 43 44 // freeMemory releases the memory region via the "VirtualFree" function. 45 // See https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualfree 46 func freeMemory(code []byte) error { 47 address := uintptr(unsafe.Pointer(&code[0])) 48 size := uintptr(0) // size must be 0 because we're using MEM_RELEASE. 49 freetype := windows_MEM_RELEASE 50 if r, _, err := procVirtualFree.Call(address, size, freetype); r == 0 { 51 return fmt.Errorf("compiler: VirtualFree error: %w", ensureErr(err)) 52 } 53 return nil 54 } 55 56 func virtualProtect(address, size, newprotect uintptr, oldprotect *uint32) error { 57 if r, _, err := procVirtualProtect.Call(address, size, newprotect, uintptr(unsafe.Pointer(oldprotect))); r == 0 { 58 return fmt.Errorf("compiler: VirtualProtect error: %w", ensureErr(err)) 59 } 60 return nil 61 } 62 63 func mmapCodeSegmentAMD64(code io.Reader, size int) ([]byte, error) { 64 p, err := allocateMemory(uintptr(size), windows_PAGE_EXECUTE_READWRITE) 65 if err != nil { 66 return nil, err 67 } 68 69 var mem []byte 70 sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem)) 71 sh.Data = p 72 sh.Len = size 73 sh.Cap = size 74 75 w := &bufWriter{underlying: mem} 76 _, err = io.CopyN(w, code, int64(size)) 77 return mem, err 78 } 79 80 func mmapCodeSegmentARM64(code io.Reader, size int) ([]byte, error) { 81 p, err := allocateMemory(uintptr(size), windows_PAGE_READWRITE) 82 if err != nil { 83 return nil, err 84 } 85 86 var mem []byte 87 sh := (*reflect.SliceHeader)(unsafe.Pointer(&mem)) 88 sh.Data = p 89 sh.Len = size 90 sh.Cap = size 91 w := &bufWriter{underlying: mem} 92 _, err = io.CopyN(w, code, int64(size)) 93 if err != nil { 94 return nil, err 95 } 96 97 old := uint32(windows_PAGE_READWRITE) 98 err = virtualProtect(p, uintptr(size), windows_PAGE_EXECUTE_READ, &old) 99 if err != nil { 100 return nil, err 101 } 102 return mem, nil 103 } 104 105 // ensureErr returns syscall.EINVAL when the input error is nil. 106 // 107 // We are supposed to use "GetLastError" which is more precise, but it is not safe to execute in goroutines. While 108 // "GetLastError" is thread-local, goroutines are not pinned to threads. 109 // 110 // See https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 111 func ensureErr(err error) error { 112 if err != nil { 113 return err 114 } 115 return syscall.EINVAL 116 }