Expose public garbage collector API for C language

You can now do epic things like this:

    puts(_gc(xasprintf("%d", 123)));

The _gc() API is shorthand for _defer() which works like Go's keyword:

    const char *s = xasprintf("%d", 123);
    _defer(free, s);
    puts(s);

Be sure to always use -fno-omit-frame-pointer which makes code fast too.

Enjoy! See also #114
This commit is contained in:
Justine Tunney 2021-03-08 10:56:09 -08:00
parent 0ad609268f
commit 33e8fc8687
26 changed files with 192 additions and 182 deletions

View file

@ -26,5 +26,7 @@ void free_s(void *v) {
void **pp = (void **)v;
void *p = NULL;
lockxchg(pp, &p);
if (isheap(p)) weakfree(p);
if (_isheap(p)) {
_weakfree(p);
}
}

30
libc/runtime/gc.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_GC_H_
#define COSMOPOLITAN_LIBC_RUNTIME_GC_H_
#include "libc/calls/calls.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/runtime.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
void *_gc(void *) hidden;
void *_defer(void *, void *) hidden;
void __defer(struct StackFrame *, void *, void *) hidden;
void __deferer(struct StackFrame *, void *, void *) hidden;
void _gclongjmp(jmp_buf, int) nothrow wontreturn;
#if defined(__GNUC__) && !defined(__STRICT_ANSI__)
#define _gc(THING) _defer((void *)_weakfree, (void *)(THING))
#define _defer(FN, ARG) \
({ \
autotype(ARG) Arg = (ARG); \
/* prevent weird opts like tail call */ \
asm volatile("" : "+g"(Arg) : : "memory"); \
__defer((struct StackFrame *)__builtin_frame_address(0), FN, Arg); \
asm volatile("" : "+g"(Arg) : : "memory"); \
Arg; \
})
#endif /* defined(__GNUC__) && !defined(__STRICT_ANSI__) */
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_GC_H_ */

View file

@ -1,49 +1,10 @@
#ifndef COSMOPOLITAN_LIBC_RUNTIME_GC_H_
#define COSMOPOLITAN_LIBC_RUNTIME_GC_H_
#include "libc/calls/calls.h"
#include "libc/nexgen32e/stackframe.h"
#include "libc/runtime/runtime.h"
#ifndef COSMOPOLITAN_LIBC_RUNTIME_GC_INTERNAL_H_
#define COSMOPOLITAN_LIBC_RUNTIME_GC_INTERNAL_H_
#include "libc/runtime/gc.h"
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
/**
* @fileoverview Cosmopolitan Return-Oriented Garbage Collector.
*
* This is the same thing as {@code std::unique_ptr<>} in C++ or the
* {@code defer} keyword in Go. We harness the power of ROP for good
* using very few lines of code.
*/
#define gc(THING) _gc(THING)
#define defer(FN, ARG) _defer(FN, ARG)
/**
* Releases resource when function returns.
*
* @warning do not return a gc()'d pointer
* @warning do not realloc() with gc()'d pointer
*/
#define gc(THING) defer((void *)weakfree, (void *)(THING))
/**
* Same as longjmp() but runs gc() / defer() destructors.
*/
void gclongjmp(jmp_buf, int) nothrow wontreturn paramsnonnull();
/**
* Calls FN(ARG) when function returns.
*/
#ifndef __VSCODE_INTELLISENSE__
#define defer(FN, ARG) \
({ \
autotype(ARG) Arg = (ARG); \
/* prevent weird opts like tail call */ \
asm volatile("" : "+g"(Arg) : : "memory"); \
__defer((struct StackFrame *)__builtin_frame_address(0), FN, Arg); \
asm volatile("" : "+g"(Arg) : : "memory"); \
Arg; \
})
#endif /* __VSCODE_INTELLISENSE__ */
void __defer(struct StackFrame *, void *, void *) hidden paramsnonnull((1, 2));
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_GC_H_ */
#endif /* COSMOPOLITAN_LIBC_RUNTIME_GC_INTERNAL_H_ */

View file

@ -43,8 +43,8 @@ bool __grow(void *pp, size_t *capacity, size_t itemsize, size_t extra) {
p = (void **)pp;
assert(itemsize);
assert((*p && *capacity) || (!*p && !*capacity));
assert(!isheap(*p) || ((intptr_t)*p & 15) == 0);
p1 = isheap(*p) ? *p : NULL;
assert(!_isheap(*p) || ((intptr_t)*p & 15) == 0);
p1 = _isheap(*p) ? *p : NULL;
p2 = NULL;
n1 = *capacity;
n2 = (*p ? n1 + (n1 >> 1) : MAX(4, INITIAL_CAPACITY / itemsize)) + extra;

View file

@ -26,7 +26,7 @@
* @assume stack addresses are always greater than heap addresses
* @assume stack memory isn't stored beneath %rsp (-mno-red-zone)
*/
bool isheap(void *p) {
bool _isheap(void *p) {
int x, i;
uintptr_t rsp;
asm("mov\t%%rsp,%0" : "=r"(rsp));

View file

@ -18,10 +18,10 @@
*/
#include "ape/relocations.h"
#include "libc/macros.internal.h"
.source __FILE__
// Loads all pages from program image into memory.
peekall:.leafprologue
_peekall:
.leafprologue
ezlea _base,si
ezlea _end,cx
0: mov (%rsi),%eax
@ -29,4 +29,4 @@ peekall:.leafprologue
cmp %rcx,%rsi
jb 0b
.leafepilogue
.endfn peekall,globl
.endfn _peekall,globl

View file

@ -34,10 +34,10 @@ static void __sys_print_nt(const void *data, size_t len) {
int64_t hand;
char xmm[256];
uint32_t wrote;
savexmm(xmm + 128);
_savexmm(xmm + 128);
hand = __imp_GetStdHandle(kNtStdErrorHandle);
__imp_WriteFile(hand, data, len, &wrote, NULL);
loadxmm(xmm + 128);
_loadxmm(xmm + 128);
}
/**

View file

@ -33,7 +33,6 @@ extern unsigned char *__relo_end[]; /* αpε */
extern uint8_t __zip_start[]; /* αpε */
extern uint8_t __zip_end[]; /* αpε */
long missingno();
void mcount(void);
unsigned long getauxval(unsigned long);
void *mapanon(size_t) vallocesque attributeallocsize((1));
@ -45,34 +44,35 @@ void exit(int) wontreturn;
void _exit(int) libcesque wontreturn;
void _Exit(int) libcesque wontreturn;
void abort(void) wontreturn noinstrument;
void triplf(void) wontreturn noinstrument privileged;
int __cxa_atexit(void *, void *, void *) libcesque;
int atfork(void *, void *) libcesque;
int atexit(void (*)(void)) libcesque;
void free_s(void *) paramsnonnull() libcesque;
int close_s(int *) paramsnonnull() libcesque;
char *getenv(const char *) paramsnonnull() nosideeffect libcesque;
int putenv(char *) paramsnonnull();
int setenv(const char *, const char *, int) paramsnonnull();
int unsetenv(const char *);
int clearenv(void);
void fpreset(void);
void savexmm(void *);
void loadxmm(void *);
void peekall(void);
int issetugid(void);
void weakfree(void *) libcesque;
bool isheap(void *);
void *mmap(void *, uint64_t, int32_t, int32_t, int32_t, int64_t);
void *mremap(void *, uint64_t, uint64_t, int32_t, void *);
int munmap(void *, uint64_t);
int mprotect(void *, uint64_t, int) privileged;
int msync(void *, size_t, int);
void __print(const void *, size_t);
void __print_string(const char *);
void *sbrk(intptr_t);
int brk(void *);
bool _isheap(void *);
int NtGetVersion(void);
long missingno();
void __print(const void *, size_t);
void __print_string(const char *);
void _loadxmm(void *);
void _peekall(void);
void _savexmm(void *);
void _weakfree(void *);
void free_s(void *) paramsnonnull() libcesque;
int close_s(int *) paramsnonnull() libcesque;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -50,21 +50,22 @@ privileged noasan void __stack_chk_fail(void) {
: "=S"(si), "=c"(cx)
: "0"(msg), "1"(len), "d"(0x3F8 /* COM1 */)
: "memory");
triplf();
asm("push\t$0\n\t"
"push\t$0\n\t"
"cli\n\t"
"lidt\t(%rsp)");
for (;;) asm("ud2");
}
if (NtGetVersion() < kNtVersionFuture) {
do {
asm volatile(
"syscall"
: "=a"(ax), "=c"(cx)
: "0"(NtGetVersion() < kNtVersionWindows8
? 0x0029
: NtGetVersion() < kNtVersionWindows81
? 0x002a
: NtGetVersion() < kNtVersionWindows10 ? 0x002b
: 0x002c),
"1"(pushpop(-1L)), "d"(42)
: "r11", "cc", "memory");
asm volatile("syscall"
: "=a"(ax), "=c"(cx)
: "0"(NtGetVersion() < kNtVersionWindows8 ? 0x0029
: NtGetVersion() < kNtVersionWindows81 ? 0x002a
: NtGetVersion() < kNtVersionWindows10 ? 0x002b
: 0x002c),
"1"(pushpop(-1L)), "d"(42)
: "r11", "cc", "memory");
} while (!ax);
}
for (;;) {

View file

@ -1,7 +1,7 @@
/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
Copyright 2021 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
@ -16,20 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/macros.internal.h"
.source __FILE__
#include "libc/bits/weaken.h"
#include "libc/mem/mem.h"
#include "libc/runtime/runtime.h"
// Thunks free() if it's linked, otherwise do nothing.
//
// @see free_s() which can ignore static/stack and clear refs
weakfree:
push %rbp
mov %rsp,%rbp
.weak free
ezlea free,ax
test %rax,%rax
jz 1f
call free
1: pop %rbp
ret
.endfn weakfree,globl
/**
* Thunks free() if it's linked, otherwise do nothing.
*/
void _weakfree(void *p) {
if (weaken(free)) {
weaken(free)(p);
}
}