Do more quality assurance work

This commit is contained in:
Justine Tunney 2024-06-24 06:53:49 -07:00
parent 67b19ae733
commit d461c6f47d
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
31 changed files with 194 additions and 108 deletions

View file

@ -135,6 +135,8 @@ o/$(MODE)/libc/intrin/kweekdayname.o: libc/intrin/kweekdayname.S
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<
o/$(MODE)/libc/intrin/kweekdaynameshort.o: libc/intrin/kweekdaynameshort.S
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<
o/$(MODE)/libc/intrin/sched_yield.o: libc/intrin/sched_yield.S
@$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -c $<
LIBC_INTRIN_LIBS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)))
LIBC_INTRIN_HDRS = $(foreach x,$(LIBC_INTRIN_ARTIFACTS),$($(x)_HDRS))

View file

@ -26,6 +26,8 @@ struct Maps {
struct Dll *free;
struct Map stack;
struct Dll *used;
size_t count;
size_t pages;
};
struct AddrSize {
@ -35,9 +37,9 @@ struct AddrSize {
extern struct Maps __maps;
int maps_check(void);
void __maps_init(void);
void __maps_lock(void);
void __maps_check(void);
void __maps_unlock(void);
struct Map *__maps_alloc(void);
void __maps_free(struct Map *);

View file

@ -25,9 +25,11 @@
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/intrin/atomic.h"
#include "libc/intrin/describebacktrace.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/directmap.internal.h"
#include "libc/intrin/dll.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/maps.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
@ -44,6 +46,7 @@
#include "libc/sysv/errfuns.h"
#include "libc/thread/thread.h"
#define MMDEBUG 0 // this code is too slow for openbsd/windows
#define WINBASE 0x100080040000 // TODO: Can we support Windows Vista again?
#define WINMAXX 0x200080000000
@ -51,21 +54,80 @@
#define PGUP(x) (((x) + granularity - 1) & -granularity)
#if MMDEBUG
#define ASSERT(x) (void)0
#else
#define ASSERT(x) \
do { \
if (!(x)) { \
char bt[160]; \
struct StackFrame *bp = __builtin_frame_address(0); \
kprintf("%!s:%d: assertion failed: %!s\n", __FILE__, __LINE__, #x); \
kprintf("bt %!s\n", (DescribeBacktrace)(bt, bp)); \
__print_maps(); \
_Exit(99); \
} \
} while (0)
#endif
static atomic_ulong rollo;
static bool overlaps_existing_map(const char *addr, size_t size) {
int granularity = __granularity();
for (struct Map *map = __maps.maps; map; map = map->next)
if (MAX(addr, map->addr) <
MIN(addr + PGUP(size), map->addr + PGUP(map->size)))
return true;
return false;
}
void __maps_check(void) {
#if MMDEBUG
size_t maps = 0;
size_t pages = 0;
int granularity = getauxval(AT_PAGESZ);
for (struct Map *map = __maps.maps; map; map = map->next) {
ASSERT(map->addr != MAP_FAILED);
ASSERT(map->size);
pages += PGUP(map->size) / granularity;
maps += 1;
}
ASSERT(maps = __maps.count);
ASSERT(pages == __maps.pages);
for (struct Map *m1 = __maps.maps; m1; m1 = m1->next)
for (struct Map *m2 = m1->next; m2; m2 = m2->next)
ASSERT(MAX(m1->addr, m2->addr) >=
MIN(m1->addr + PGUP(m1->size), m2->addr + PGUP(m2->size)));
#endif
}
void __maps_free(struct Map *map) {
map->next = 0;
map->size = 0;
map->addr = MAP_FAILED;
ASSERT(dll_is_alone(&map->elem));
dll_make_last(&__maps.free, &map->elem);
}
void __maps_insert(struct Map *map) {
struct Map *last = __maps.maps;
if (last && //
int granularity = getauxval(AT_PAGESZ);
__maps.pages += PGUP(map->size) / granularity;
if (last && !IsWindows() && //
map->addr == last->addr + last->size && //
(map->flags & MAP_ANONYMOUS) && //
map->flags == last->flags && //
map->prot == last->prot && //
map->off == last->off && //
map->h == last->h && //
map->off == -1) {
map->prot == last->prot) {
last->size += map->size;
dll_remove(&__maps.used, &last->elem);
dll_make_first(&__maps.used, &last->elem);
__maps_free(map);
} else if (last && !IsWindows() && //
map->addr + map->size == last->addr && //
(map->flags & MAP_ANONYMOUS) && //
map->flags == last->flags && //
map->prot == last->prot) {
last->addr -= map->size;
last->size += map->size;
dll_remove(&__maps.used, &last->elem);
dll_make_first(&__maps.used, &last->elem);
@ -74,7 +136,9 @@ void __maps_insert(struct Map *map) {
dll_make_first(&__maps.used, &map->elem);
map->next = __maps.maps;
__maps.maps = map;
++__maps.count;
}
__maps_check();
}
struct Map *__maps_alloc(void) {
@ -104,14 +168,6 @@ struct Map *__maps_alloc(void) {
return map;
}
static bool __overlaps_existing_map(const char *addr, size_t size) {
for (struct Map *map = __maps.maps; map; map = map->next) {
if (MAX(addr, map->addr) < MIN(addr + size, map->addr + map->size))
return true;
}
return false;
}
static int __munmap_chunk(void *addr, size_t size) {
return sys_munmap(addr, size);
}
@ -119,6 +175,7 @@ static int __munmap_chunk(void *addr, size_t size) {
static int __munmap(char *addr, size_t size, bool untrack_only) {
// validate arguments
int pagesz = getauxval(AT_PAGESZ);
int granularity = __granularity();
if (((uintptr_t)addr & (granularity - 1)) || //
!size || (uintptr_t)addr + size < size)
@ -127,7 +184,6 @@ static int __munmap(char *addr, size_t size, bool untrack_only) {
// untrack and delete mapping
int rc = 0;
__maps_lock();
// we can't call strace, kprintf, or nothing
StartOver:;
struct Map *map = __maps.maps;
_Atomic(struct Map *) *prev = &__maps.maps;
@ -141,12 +197,16 @@ StartOver:;
// remove mapping completely
dll_remove(&__maps.used, &map->elem);
*prev = next;
map->size = 0;
map->addr = MAP_FAILED;
__maps.pages -= (map_size + pagesz - 1) / pagesz;
__maps.count -= 1;
if (untrack_only) {
__maps_free(map);
__maps_check();
} else {
__maps_unlock();
if (!IsWindows()) {
ASSERT(addr <= map_addr);
ASSERT(map_addr + PGUP(map_size) <= addr + PGUP(size));
if (__munmap_chunk(map_addr, map_size))
rc = -1;
} else {
@ -155,7 +215,9 @@ StartOver:;
if (!CloseHandle(map->h))
rc = -1;
}
__maps_lock();
__maps_free(map);
__maps_check();
goto StartOver;
}
map = next;
@ -167,15 +229,24 @@ StartOver:;
rc = einval();
} else if (addr <= map_addr) {
// shave off lefthand side of mapping
size_t left = addr + size - map_addr;
size_t right = map_addr + map_size - (addr + size);
ASSERT(addr + size < map_addr + PGUP(map_size));
size_t left = PGUP(addr + size - map_addr);
size_t right = map_size - left;
ASSERT(right > 0);
ASSERT(left > 0);
map->addr += left;
map->size = right;
if (map->off != -1)
map->off += left;
__maps.pages -= (left + pagesz - 1) / pagesz;
__maps_check();
if (!untrack_only) {
__maps_unlock();
ASSERT(addr <= map_addr);
ASSERT(map_addr + PGUP(left) <= addr + PGUP(size));
if (__munmap_chunk(map_addr, left) == -1)
rc = -1;
__maps_lock();
goto StartOver;
}
} else if (addr + PGUP(size) >= map_addr + PGUP(map_size)) {
@ -183,9 +254,14 @@ StartOver:;
size_t left = addr - map_addr;
size_t right = map_addr + map_size - addr;
map->size = left;
__maps.pages -= (right + pagesz - 1) / pagesz;
__maps_check();
if (!untrack_only) {
__maps_unlock();
ASSERT(PGUP(right) <= PGUP(size));
if (__munmap_chunk(addr, right) == -1)
rc = -1;
__maps_lock();
goto StartOver;
}
} else {
@ -207,9 +283,14 @@ StartOver:;
map->off += left + middle;
dll_make_first(&__maps.used, &leftmap->elem);
*prev = leftmap;
__maps.pages -= (middle + pagesz - 1) / pagesz;
__maps.count += 1;
__maps_check();
if (!untrack_only) {
__maps_unlock();
if (__munmap_chunk(addr, size) == -1)
rc = -1;
__maps_lock();
goto StartOver;
}
} else {
@ -241,7 +322,7 @@ static void *__mmap_chunk(void *addr, size_t size, int prot, int flags, int fd,
sysflags |= MAP_FIXED_NOREPLACE_linux;
} else if (IsFreebsd() || IsNetbsd()) {
sysflags |= MAP_FIXED;
if (__overlaps_existing_map(addr, size))
if (overlaps_existing_map(addr, size))
return (void *)eexist();
} else {
noreplace = true;
@ -296,7 +377,7 @@ TryAgain:
// untrack mapping we blew away
if (should_untrack)
__munmap(addr, size, true);
__munmap(res.addr, size, true);
// track Map object
map->addr = res.addr;
@ -346,7 +427,7 @@ static void *__mmap_impl(char *addr, size_t size, int prot, int flags, int fd,
// so we create an separate map for each granule in the mapping
if (!(flags & MAP_FIXED)) {
while (__overlaps_existing_map(addr, size)) {
while (overlaps_existing_map(addr, size)) {
if (flags & MAP_FIXED_NOREPLACE)
return (void *)eexist();
addr += granularity;

View file

@ -91,8 +91,8 @@ int __mprotect(char *addr, size_t size, int prot) {
rc = -1;
} else if (addr <= map_addr) {
// cleave lefthand side of mapping
size_t left = addr + size - map_addr;
size_t right = map_addr + map_size - (addr + size);
size_t left = PGUP(addr + size - map_addr);
size_t right = map_size - left;
struct Map *leftmap;
if ((leftmap = __maps_alloc())) {
if (!__mprotect_chunk(map_addr, left, prot, false)) {
@ -108,6 +108,8 @@ int __mprotect(char *addr, size_t size, int prot) {
map->off += left;
dll_make_first(&__maps.used, &leftmap->elem);
*prev = leftmap;
__maps.count += 1;
__maps_check();
} else {
__maps_free(leftmap);
rc = -1;
@ -135,6 +137,8 @@ int __mprotect(char *addr, size_t size, int prot) {
map->off += left;
dll_make_first(&__maps.used, &leftmap->elem);
*prev = leftmap;
__maps.count += 1;
__maps_check();
} else {
__maps_free(leftmap);
rc = -1;
@ -171,6 +175,8 @@ int __mprotect(char *addr, size_t size, int prot) {
dll_make_first(&__maps.used, &leftmap->elem);
dll_make_first(&__maps.used, &midlmap->elem);
*prev = leftmap;
__maps.count += 2;
__maps_check();
} else {
__maps_free(midlmap);
__maps_free(leftmap);

View file

@ -34,6 +34,7 @@ textwindows int sys_msync_nt(char *addr, size_t size, int flags) {
return einval();
int rc = 0;
__maps_lock();
for (struct Map *map = __maps.maps; map; map = map->next) {
char *beg = MAX(addr, map->addr);
char *end = MIN(addr + size, map->addr + map->size);
@ -42,6 +43,7 @@ textwindows int sys_msync_nt(char *addr, size_t size, int flags) {
rc = -1;
// TODO(jart): FlushFileBuffers too on g_fds handle if MS_SYNC?
}
__maps_unlock();
return rc;
}

View file

@ -24,15 +24,20 @@
#include "libc/macros.internal.h"
#include "libc/runtime/memtrack.internal.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/auxv.h"
/**
* Prints memory mappings.
*/
void __print_maps(void) {
int limit = 10;
int limit = 13;
long maptally = 0;
char mappingbuf[8], sb[16];
for (struct Map *map = __maps.maps; map; map = map->next) {
__maps_lock();
struct Dll *e, *e2;
for (e = dll_first(__maps.used); e; e = e2) {
e2 = dll_next(__maps.used, e);
struct Map *map = MAP_CONTAINER(e);
maptally += map->size;
kprintf("%012lx-%012lx %!s", map->addr, map->addr + map->size,
(DescribeMapping)(mappingbuf, map->prot, map->flags));
@ -45,11 +50,10 @@ void __print_maps(void) {
if (map->readonlyfile)
kprintf(" readonlyfile");
kprintf("\n");
if (!--limit) {
kprintf("...\n");
if (!--limit)
break;
}
}
sizefmt(sb, maptally, 1024);
kprintf("# %!sb mapped memory\n", sb);
kprintf("# %'zu bytes in %'zu mappings\n",
__maps.pages * getauxval(AT_PAGESZ), __maps.count);
__maps_unlock();
}

111
libc/intrin/sched_yield.S Normal file
View file

@ -0,0 +1,111 @@
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
vi: set noet ft=asm ts=8 sw=8 fenc=utf-8 :vi
Copyright 2022 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/dce.h"
#include "libc/sysv/consts/nr.h"
#include "libc/macros.internal.h"
.privileged
// Relinquishes scheduled quantum.
//
// @return 0 on success, or -1 w/ errno
sched_yield:
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
xor %eax,%eax
mov __hostos(%rip),%dl
#if SupportsMetal()
testb $_HOSTMETAL,%dl
jnz 9f
#endif
#if SupportsWindows()
// Windows Support
//
// A value of zero, together with the bAlertable parameter set to
// FALSE, causes the thread to relinquish the remainder of its time
// slice to any other thread that is ready to run, if there are no
// pending user APCs on the calling thread. If there are no other
// threads ready to run and no user APCs are queued, the function
// returns immediately, and the thread continues execution.
// Quoth MSDN
testb $_HOSTWINDOWS,%dl
jz 1f
xor %ecx,%ecx
xor %edx,%edx
ntcall __imp_SleepEx
xor %eax,%eax
jmp 9f
1:
#endif
#if SupportsSystemv()
// On XNU we polyfill sched_yield() using sleep() which'll
// be polyfilled using select() with a zero timeout, which
// means to wait zero microseconds and then returns a zero
// and this hopefully will give other threads a chance too
// XNU has a special version we use called select_nocancel
//
// "If the readfds, writefds, and errorfds arguments are
// all null pointers and the timeout argument is not a
// null pointer, the pselect() or select() function shall
// block for the time specified, or until interrupted by
// a signal." Quoth IEEE 1003.1-2017 §functions/select
//
// On other platforms, sched_yield() takes no arguments.
push $0 // timeout.tv_usec
push $0 // timeout.tv_sec
xor %edi,%edi // nfds
xor %esi,%esi // readfds
xor %edx,%edx // writefds
xor %r10d,%r10d // exceptfds
mov %rsp,%r8 // timeout
mov __NR_sched_yield,%eax // ordinal
clc // linux
syscall
// It should not be possible for this to fail so we don't
// bother going through the errno ritual. If this somehow
// fails a positive or negative errno might get returned.
#endif
9: leave
ret
#elif defined(__aarch64__)
stp x29,x30,[sp,-32]!
mov x29,sp
mov x3,0
mov x2,0
add x4,sp,16
mov x1,0
mov w0,0
stp xzr,xzr,[sp,16]
mov x8,#0x7c // sched_yield() for gnu/systemd
mov x16,#0x5d // select(0,0,0,0,&blah) for xnu
svc 0
ldp x29,x30,[sp],32
ret
#else
#error "arch unsupported"
#endif
.endfn sched_yield,globl
.previous