diff --git a/libc/calls/madvise-nt.c b/libc/calls/madvise-nt.c index fe29ac94a..77ad7351c 100644 --- a/libc/calls/madvise-nt.c +++ b/libc/calls/madvise-nt.c @@ -17,46 +17,43 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/syscall_support-nt.internal.h" -#include "libc/macros.internal.h" #include "libc/nt/enum/offerpriority.h" -#include "libc/nt/memory.h" #include "libc/nt/runtime.h" #include "libc/nt/struct/memoryrangeentry.h" #include "libc/sysv/consts/madv.h" #include "libc/sysv/errfuns.h" -forceinline typeof(PrefetchVirtualMemory) *GetPrefetchVirtualMemory(void) { - static bool once; - static typeof(PrefetchVirtualMemory) *PrefetchVirtualMemory_; - if (!once) { +typedef bool32 (*__msabi PrefetchVirtualMemoryPtr)( + int64_t hProcess, uintptr_t NumberOfEntries, + struct NtMemoryRangeEntry *VirtualAddresses, uint32_t reserved_Flags); + +textwindows static PrefetchVirtualMemoryPtr GetPrefetchVirtualMemory(void) { + static PrefetchVirtualMemoryPtr PrefetchVirtualMemory_; + if (!PrefetchVirtualMemory_) { PrefetchVirtualMemory_ = /* win8.1+ */ GetProcAddressModule("Kernel32.dll", "PrefetchVirtualMemory"); - once = true; } return PrefetchVirtualMemory_; } -forceinline typeof(OfferVirtualMemory) *GetOfferVirtualMemory(void) { - static bool once; - static typeof(OfferVirtualMemory) *OfferVirtualMemory_; - if (!once) { +typedef bool32 (*__msabi OfferVirtualMemoryPtr)(void *inout_VirtualAddress, + size_t Size, int Priority); + +textwindows static OfferVirtualMemoryPtr GetOfferVirtualMemory(void) { + static OfferVirtualMemoryPtr OfferVirtualMemory_; + if (!OfferVirtualMemory_) { OfferVirtualMemory_ = /* win8.1+ */ GetProcAddressModule("Kernel32.dll", "OfferVirtualMemory"); - once = true; } return OfferVirtualMemory_; } textwindows int sys_madvise_nt(void *addr, size_t length, int advice) { - uint32_t rangecount; - struct NtMemoryRangeEntry ranges[1]; if (advice == MADV_WILLNEED || advice == MADV_SEQUENTIAL) { - typeof(PrefetchVirtualMemory) *fn = GetPrefetchVirtualMemory(); + PrefetchVirtualMemoryPtr fn = GetPrefetchVirtualMemory(); if (fn) { - ranges[0].VirtualAddress = addr; - ranges[0].NumberOfBytes = length; - rangecount = ARRAYLEN(ranges); - if (fn(GetCurrentProcess(), &rangecount, ranges, 0)) { + if (fn(GetCurrentProcess(), 1, &(struct NtMemoryRangeEntry){addr, length}, + 0)) { return 0; } else { return __winerr(); @@ -65,7 +62,7 @@ textwindows int sys_madvise_nt(void *addr, size_t length, int advice) { return enosys(); } } else if (advice == MADV_FREE) { - typeof(OfferVirtualMemory) *fn = GetOfferVirtualMemory(); + OfferVirtualMemoryPtr fn = GetOfferVirtualMemory(); if (fn) { if (fn(addr, length, kNtVmOfferPriorityNormal)) { return 0; diff --git a/libc/calls/madvise.c b/libc/calls/madvise.c index ebe6e6088..dcd81d02c 100644 --- a/libc/calls/madvise.c +++ b/libc/calls/madvise.c @@ -29,21 +29,21 @@ * * @param advice can be MADV_WILLNEED, MADV_SEQUENTIAL, MADV_FREE, etc. * @return 0 on success, or -1 w/ errno + * @raise EINVAL if `advice` isn't valid or supported by system + * @raise EINVAL on Linux if addr/length isn't page size aligned with + * respect to `getauxval(AT_PAGESZ)` + * @raise ENOMEM on Linux if addr/length overlaps unmapped regions * @see libc/sysv/consts.sh * @see fadvise() */ int madvise(void *addr, size_t length, int advice) { int rc; - if (advice != 127 /* see consts.sh */) { - if (IsAsan() && !__asan_is_valid(addr, length)) { - rc = efault(); - } else if (!IsWindows()) { - rc = sys_madvise(addr, length, advice); - } else { - rc = sys_madvise_nt(addr, length, advice); - } + if (IsAsan() && !__asan_is_valid(addr, length)) { + rc = efault(); + } else if (!IsWindows()) { + rc = sys_madvise(addr, length, advice); } else { - rc = einval(); + rc = sys_madvise_nt(addr, length, advice); } STRACE("madvise(%p, %'zu, %d) → %d% m", addr, length, advice, rc); return rc; diff --git a/libc/calls/posix_madvise.c b/libc/calls/posix_madvise.c index 4bf5b71eb..9503cd7a4 100644 --- a/libc/calls/posix_madvise.c +++ b/libc/calls/posix_madvise.c @@ -23,6 +23,10 @@ * Advises kernel about memory intentions, the POSIX way. * * @return 0 on success, or errno on error + * @raise EINVAL if `advice` isn't valid or supported by system + * @raise EINVAL on Linux if addr/length isn't page size aligned with + * respect to `getauxval(AT_PAGESZ)` + * @raise ENOMEM on Linux if addr/length overlaps unmapped regions * @returnserrno */ errno_t posix_madvise(void *addr, uint64_t len, int advice) { diff --git a/libc/nt/memory.h b/libc/nt/memory.h index b9c716f71..68ac1ef83 100644 --- a/libc/nt/memory.h +++ b/libc/nt/memory.h @@ -73,12 +73,6 @@ uint64_t VirtualQuery(const void *lpAddress, void *VirtualAllocEx(int64_t hProcess, void *lpAddress, uint64_t dwSize, uint32_t flAllocationType, uint32_t flProtect); -bool32 PrefetchVirtualMemory(int64_t hProcess, const uint32_t *NumberOfEntries, - struct NtMemoryRangeEntry *VirtualAddresses, - uint32_t reserved_Flags); -bool32 OfferVirtualMemory(void *inout_VirtualAddress, size_t Size, - int Priority); - int64_t GetProcessHeap(void); void *HeapAlloc(int64_t hHeap, uint32_t dwFlags, size_t dwBytes) __wur; bool32 HeapFree(int64_t hHeap, uint32_t dwFlags, void *opt_lpMem); diff --git a/test/libc/calls/madvise_test.c b/test/libc/calls/madvise_test.c new file mode 100644 index 000000000..ed6701307 --- /dev/null +++ b/test/libc/calls/madvise_test.c @@ -0,0 +1,91 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" +#include "libc/dce.h" +#include "libc/errno.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/madv.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" +#include "libc/testlib/testlib.h" + +void SetUpOnce(void) { + testlib_enable_tmp_setup_teardown(); +} + +TEST(madvise, anon) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + ASSERT_SYS(0, 0, madvise(p, FRAMESIZE, MADV_WILLNEED)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); +} + +TEST(madvise, file) { + char *p; + ASSERT_SYS(0, 3, creat("foo.dat", 0644)); + ASSERT_SYS(0, 0, ftruncate(3, FRAMESIZE)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 3, open("foo.dat", O_RDWR)); + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE, 3, 0))); + ASSERT_SYS(0, 0, madvise(p, FRAMESIZE, MADV_WILLNEED)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); + ASSERT_SYS(0, 0, close(3)); +} + +TEST(madvise, short) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + ASSERT_SYS(0, 0, madvise(p, FRAMESIZE - 1, MADV_WILLNEED)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); +} + +TEST(madvise, subPages) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + ASSERT_SYS(0, 0, madvise(p + 4096, FRAMESIZE - 4096, MADV_WILLNEED)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); +} + +TEST(madvise, misalign) { + char *p; + if (!IsLinux()) return; // most platforms don't care + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + ASSERT_SYS(EINVAL, -1, madvise(p + 1, FRAMESIZE - 1, MADV_WILLNEED)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); +} + +TEST(madvise, badAdvice) { + char *p; + ASSERT_NE(MAP_FAILED, (p = mmap(0, FRAMESIZE, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0))); + ASSERT_SYS(EINVAL, -1, madvise(p, FRAMESIZE, 127)); + ASSERT_SYS(0, 0, munmap(p, FRAMESIZE)); +} + +TEST(madvise, missingMemory) { + if (!IsLinux()) return; + ASSERT_SYS(ENOMEM, -1, + madvise((char *)0x83483838000, FRAMESIZE, MADV_WILLNEED)); +}