Make garbage collection thread safe

- You can now use _gc(malloc()) in multithreaded programs
- This change fixes a bug where fork() on NT disabled TLS
- Fixed TLS code morphing on XNU/NT, for R8-R15 registers
This commit is contained in:
Justine Tunney 2022-09-08 02:33:01 -07:00
parent 571c2c3c69
commit 0e2b1bfeed
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
37 changed files with 310 additions and 189 deletions

View file

@ -16,9 +16,93 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/likely.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/gc.internal.h"
#include "libc/nexgen32e/gettls.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/gc.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/runtime/runtime.h"
#include "libc/str/str.h"
#include "libc/thread/thread.h"
static inline bool PointerNotOwnedByParentStackFrame(struct StackFrame *frame,
struct StackFrame *parent,
void *ptr) {
return !(((intptr_t)ptr > (intptr_t)frame) &&
((intptr_t)ptr < (intptr_t)parent));
}
static void TeardownGc(void) {
int i;
cthread_t tls;
struct Garbages *g;
if (__tls_enabled) {
tls = (cthread_t)__get_tls();
if ((g = tls->garbages)) {
// exit() currently doesn't use _gclongjmp() like pthread_exit()
// so we need to run the deferred functions manually.
while (g->i) {
--g->i;
((void (*)(intptr_t))g->p[g->i].fn)(g->p[g->i].arg);
}
free(g->p);
free(g);
}
}
}
__attribute__((__constructor__)) static void InitializeGc(void) {
atexit(TeardownGc);
}
// add item to garbage shadow stack.
// then rewrite caller's return address on stack.
static void DeferFunction(struct StackFrame *frame, void *fn, void *arg) {
int n2;
cthread_t tls;
struct Garbage *p2;
struct Garbages *g;
TlsIsRequired();
tls = (cthread_t)__get_tls();
g = tls->garbages;
if (UNLIKELY(!g)) {
if (!(g = malloc(sizeof(struct Garbages)))) notpossible;
g->i = 0;
g->n = 4;
if (!(g->p = malloc(g->n * sizeof(struct Garbage)))) {
kprintf("malloc failed\n");
notpossible;
}
tls->garbages = g;
} else if (UNLIKELY(g->i == g->n)) {
p2 = g->p;
n2 = g->n + (g->n >> 1);
if ((p2 = realloc(p2, n2 * sizeof(*p2)))) {
g->p = p2;
g->n = n2;
} else {
notpossible;
}
}
g->p[g->i].frame = frame;
g->p[g->i].fn = (intptr_t)fn;
g->p[g->i].arg = (intptr_t)arg;
g->p[g->i].ret = frame->addr;
g->i++;
frame->addr = (intptr_t)__gc;
}
// the gnu extension macros for _gc / _defer point here
void __defer(void *rbp, void *fn, void *arg) {
struct StackFrame *f, *frame = rbp;
f = __builtin_frame_address(0);
assert(f->next == frame);
assert(PointerNotOwnedByParentStackFrame(f, frame, arg));
DeferFunction(frame, fn, arg);
}
/**
* Frees memory when function returns.
@ -38,11 +122,12 @@
* @warning do not realloc() with gc()'d pointer
* @warning be careful about static keyword due to impact of inlining
* @note you should use -fno-omit-frame-pointer
* @threadsafe
*/
void *(_gc)(void *thing) {
struct StackFrame *frame;
frame = __builtin_frame_address(0);
__deferer(frame->next, _weakfree, thing);
DeferFunction(frame->next, _weakfree, thing);
return thing;
}
@ -57,10 +142,11 @@ void *(_gc)(void *thing) {
* @warning do not realloc() with gc()'d pointer
* @warning be careful about static keyword due to impact of inlining
* @note you should use -fno-omit-frame-pointer
* @threadsafe
*/
void *(_defer)(void *fn, void *arg) {
struct StackFrame *frame;
frame = __builtin_frame_address(0);
__deferer(frame->next, fn, arg);
DeferFunction(frame->next, fn, arg);
return arg;
}