Interface to the SML# concurrent mark/sweep GC

This is work-in-progress, however it does usually complete self-build
on Linux (less so on macOS) and can run some of the regression suite.
This commit is mainly for other developers to view the state.

Using the "buildit" script which is for now the suggested way to build,
make-target-2 will show (in lines prefixed with "Stack scan") pause times
as low as 5 microseconds, or up to maybe 100 microseconds on the high end.

Anything having to do with thread start/exit is potentially broken,
and there are some obviously missing pieces which are denoted by purposely-
inserted lose() calls.
This commit is contained in:
Douglas Katzman 2023-10-09 16:49:33 -04:00
parent 1d668eb621
commit 4adce93853
94 changed files with 7166 additions and 649 deletions

8
buildit Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
export SMLGC=1
host=$HOME/sbcl/pristine/bin/sbcl
SBCL_MAKE_PARALLEL=8,0 sh make.sh $host --prefix=$HOME/sbcl/pristine \
--without-soft-card-marks --without-immobile-space --with-weak-vector-readbarrier \
--with-int4-breakpoints --without-sb-eval --with-sb-fasteval

View file

@ -392,6 +392,10 @@ not supported."
(let ((pid (posix-fork))) (let ((pid (posix-fork)))
(when (= pid 0) ; child (when (= pid 0) ; child
#+darwin (darwin-reinit) #+darwin (darwin-reinit)
(when (/= (extern-alien "use_smlgc" int) 0)
(alien-funcall (extern-alien "smlgc_init" (function void unsigned))
(sb-ext:dynamic-space-size))
(alien-funcall (extern-alien "enable_collector_thread" (function void))))
#+mark-region-gc (alien-funcall (extern-alien "thread_pool_init" (function void)))) #+mark-region-gc (alien-funcall (extern-alien "thread_pool_init" (function void))))
#+sb-thread (sb-impl::finalizer-thread-start) #+sb-thread (sb-impl::finalizer-thread-start)
pid)) pid))

15
smlsharpgc/alloc_ptr.h Normal file
View file

@ -0,0 +1,15 @@
#ifndef SMLSHARP__ALLOC_PTR_H__
#define SMLSHARP__ALLOC_PTR_H__
#include <stdint.h>
typedef uint32_t sml_bmword_t;
struct sml_bitptr { const sml_bmword_t *ptr; sml_bmword_t mask; };
struct sml_bitptrw { sml_bmword_t *wptr; sml_bmword_t mask; };
typedef struct sml_bitptr sml_bitptr_t;
typedef struct sml_bitptrw sml_bitptrw_t;
/* sizeof(struct alloc_ptr) must be power of 2 for performance */
struct alloc_ptr {
sml_bitptr_t freebit;
char *free;
unsigned int blocksize_bytes;
};
#endif

View file

@ -4,15 +4,36 @@
* @author UENO Katsuhiro * @author UENO Katsuhiro
*/ */
#ifdef HAVE_GENESIS_CONFIG
#include "genesis/config.h"
#include "genesis/thread.h"
#include "genesis/symbol.h"
#include "genesis/static-symbols.h"
#include "genesis/constants.h"
#include "interr.h"
#include <stdio.h>
#include <string.h>
#endif
#include "smlsharp.h" #include "smlsharp.h"
#include <stdlib.h> #include <stdlib.h>
#include "heap.h" #include "heap.h"
#include <signal.h>
int gc_verbose = 1;
#ifndef WITHOUT_MULTITHREAD #ifndef WITHOUT_MULTITHREAD
struct control { struct control {
_Atomic(unsigned int) state; //_Atomic(unsigned int) state;
//unsigned int* statepointer;
_Atomic(unsigned int) flags; _Atomic(unsigned int) flags;
struct control *next; /* double-linked list */ struct control *next; /* singly-linked list */
// Deletion of the vm_thread (the 'struct thread' and all associated
// memory) can be done only while holding this lock.
// Also the thread is not allowed to completely exit (i.e. cease to be
// a valid pthread identifier) until clearing the vm_thread field,
// which is also required to be done under the lock.
pthread_mutex_t state_lock;
struct thread *vm_thread;
}; };
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
@ -43,6 +64,7 @@ struct sml_user {
void *bottom, *top; void *bottom, *top;
struct frame_stack_range *next; struct frame_stack_range *next;
} *frame_stack; } *frame_stack;
void *arbdata;
void *exn_object; void *exn_object;
}; };
@ -62,6 +84,9 @@ struct sml_worker {
static _Atomic(struct control *) workers; static _Atomic(struct control *) workers;
static enum sml_sync_phase new_worker_phase = ASYNC; static enum sml_sync_phase new_worker_phase = ASYNC;
static sml_spinlock_t worker_creation_lock = SPIN_LOCK_INIT; static sml_spinlock_t worker_creation_lock = SPIN_LOCK_INIT;
union sml_alloc* get_worker_aps() {
return ((struct sml_worker*)workers)->thread_local_heap;
}
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
@ -73,12 +98,22 @@ _Atomic(unsigned int) sml_check_flag;
#define FLAG_GC 1U #define FLAG_GC 1U
#define FLAG_SIGNAL (~(-1U >> 1)) #define FLAG_SIGNAL (~(-1U >> 1))
int use_gcsignal = 1;
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
#define USE_SYNC_SEM 1
#ifdef USE_SYNC_SEM
#include <semaphore.h>
os_sem_t gc_sync_semaphore;
#elif defined USE_SYNC_PIPE
static int sync_pipe[2];
#else
static pthread_mutex_t sync_wait_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t sync_wait_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sync_wait_cond = PTHREAD_COND_INITIALIZER; static pthread_cond_t sync_wait_cond = PTHREAD_COND_INITIALIZER;
#endif
static _Atomic(unsigned int) sync_counter; static _Atomic(unsigned int) sync_counter;
#endif /* !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_CONCURRENCY */
#include <unistd.h>
#ifndef WITHOUT_MULTITHREAD #ifndef WITHOUT_MULTITHREAD
static void cancel(void *p) static void cancel(void *p)
{ {
@ -90,11 +125,31 @@ static void cancel(void *p)
* but no clever programmer use it ;p). * but no clever programmer use it ;p).
*/ */
fetch_or(relaxed, &((struct control *)p)->flags, CANCELED_FLAG); fetch_or(relaxed, &((struct control *)p)->flags, CANCELED_FLAG);
TPRINTF(1, "set CANCELED_FLAG");
} }
#endif /* WITHOUT_MULTITHREAD */ #endif /* WITHOUT_MULTITHREAD */
worker_tlv_alloc(struct sml_worker *, current_worker, cancel); worker_tlv_alloc(struct sml_worker *, current_worker, cancel);
void sml_current_worker_set_thread(struct thread* thread, int set_aps) {
struct sml_worker *worker = worker_tlv_get(current_worker);
worker->control.vm_thread = thread;
if (thread) {
spin_lock(&worker_creation_lock);
thread->gc_phase = ACTIVE(new_worker_phase);
spin_unlock(&worker_creation_lock);
}
if (set_aps) {
}
}
void sml_current_user_set_arbdata(void* data) {
struct sml_worker *worker = worker_tlv_get(current_worker);
struct sml_user *user = worker->user;
fprintf(stderr, "current SML worker = %p, user = %p\n", worker, user);
user->arbdata = data;
}
void* get_sml_user_arbdata(struct sml_user* user) { return user->arbdata; }
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
user_tlv_alloc(struct sml_user *, current_user, cancel); user_tlv_alloc(struct sml_user *, current_user, cancel);
#endif /* !WITHOUT_MASSIVETHREADS */ #endif /* !WITHOUT_MASSIVETHREADS */
@ -168,10 +223,15 @@ control_insert(_Atomic(struct control *) *list, struct control *item)
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
#ifndef WITHOUT_MULTITHREAD #ifndef WITHOUT_MULTITHREAD
extern __thread struct thread *current_thread;
// pointer to control state (stored in the SBCL 'struct thread')
#define pCTRL_STATE(c) &(c).vm_thread->gc_phase
static void static void
control_init(struct control *control, unsigned int state) control_init(struct control *control, unsigned int state)
{ {
atomic_init(&control->state, state); struct thread* vm_thread = control->vm_thread;
if (!vm_thread) vm_thread = control->vm_thread = current_thread;
if (vm_thread) atomic_init(&vm_thread->gc_phase, state);
atomic_init(&control->flags, 0); atomic_init(&control->flags, 0);
} }
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
@ -183,12 +243,12 @@ activate(struct control *control)
unsigned int old; unsigned int old;
/* all updates by other threads must happen before here */ /* all updates by other threads must happen before here */
old = fetch_and(acquire, &control->state, ~INACTIVE_FLAG); old = fetch_and(acquire, pCTRL_STATE(*control), ~INACTIVE_FLAG);
while (IS_ACTIVE(old)) { while (IS_ACTIVE(old)) {
sched_yield(); sched_yield();
old = fetch_and(acquire, &control->state, ~INACTIVE_FLAG); old = fetch_and(acquire, pCTRL_STATE(*control), ~INACTIVE_FLAG);
} }
assert(IS_ACTIVE(load_relaxed(&control->state))); assert(IS_ACTIVE(load_relaxed(pCTRL_STATE(*control))));
} }
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
@ -242,7 +302,11 @@ worker_register(struct sml_worker *worker)
atomic_init(&worker->control.flags, PTHREAD_FLAG); atomic_init(&worker->control.flags, PTHREAD_FLAG);
#endif /* !WITHOUT_MASSIVETHREADS */ #endif /* !WITHOUT_MASSIVETHREADS */
spin_lock(&worker_creation_lock); spin_lock(&worker_creation_lock);
atomic_init(&worker->control.state, ACTIVE(new_worker_phase)); if (worker->control.vm_thread)
atomic_init(pCTRL_STATE(worker->control), ACTIVE(new_worker_phase));
else {
fprintf(stderr, "WARNING: no sb-vm:thread for GC worker yet\n");
}
control_insert(&workers, &worker->control); control_insert(&workers, &worker->control);
spin_unlock(&worker_creation_lock); spin_unlock(&worker_creation_lock);
worker_tlv_set(current_worker, worker); worker_tlv_set(current_worker, worker);
@ -253,7 +317,8 @@ static struct sml_user *
user_new() user_new()
{ {
struct sml_user *user; struct sml_user *user;
user = xmalloc(sizeof(struct sml_user)); user = xmalloc(sizeof(struct sml_user), "user");
user->arbdata = NULL;
user->frame_stack = NULL; user->frame_stack = NULL;
user->exn_object = NULL; user->exn_object = NULL;
return user; return user;
@ -263,19 +328,22 @@ static struct sml_worker *
worker_new() worker_new()
{ {
struct sml_worker *worker; struct sml_worker *worker;
worker = xmalloc(sizeof(struct sml_worker)); worker = xmalloc(sizeof(struct sml_worker), "worker");
worker->thread_local_heap = sml_heap_worker_init(); worker->thread_local_heap = sml_heap_worker_init();
worker->user = NULL; worker->user = NULL;
worker->control.vm_thread = NULL;
pthread_mutex_init(&worker->control.state_lock, 0);
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
atomic_init(&worker->new_users, NULL); atomic_init(&worker->new_users, NULL);
#endif /* WITHOUT_MASSIVETHREADS */ #endif /* WITHOUT_MASSIVETHREADS */
fprintf(stderr, "GC: new worker @ %p\n", worker);
return worker; return worker;
} }
static void static void
user_destroy(struct sml_user *user) user_destroy(struct sml_user *user)
{ {
free(user); xfree(user, "user");
} }
static void static void
@ -285,18 +353,26 @@ worker_destroy(struct sml_worker *worker)
#ifdef WITHOUT_MASSIVETHREADS #ifdef WITHOUT_MASSIVETHREADS
user_destroy(worker->user); user_destroy(worker->user);
#endif /* WITHOUT_MASSIVETHREADS */ #endif /* WITHOUT_MASSIVETHREADS */
free(worker); pthread_mutex_destroy(&worker->control.state_lock);
xfree(worker, "worker");
} }
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
extern char* cur_thread_name();
static void static void
decr_sync_counter_relaxed() decr_sync_counter_relaxed()
{ {
if (fetch_sub(relaxed, &sync_counter, 1) - 1 == 0) { unsigned int oldval = fetch_sub(relaxed, &sync_counter, 1);
TPRINTF(1, "dec_sync_relaxed: old=%x", oldval);
#if USE_SYNC_SEM
os_sem_post(&gc_sync_semaphore);
#else
if (oldval == 1) {
mutex_lock(&sync_wait_lock); mutex_lock(&sync_wait_lock);
cond_signal(&sync_wait_cond); cond_signal(&sync_wait_cond);
mutex_unlock(&sync_wait_lock); mutex_unlock(&sync_wait_lock);
} }
#endif
} }
#endif /* !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_CONCURRENCY */
@ -304,11 +380,17 @@ decr_sync_counter_relaxed()
static void static void
decr_sync_counter_release() decr_sync_counter_release()
{ {
if (fetch_sub(release, &sync_counter, 1) - 1 == 0) { unsigned int oldval = fetch_sub(release, &sync_counter, 1);
TPRINTF(1, "dec_sync_release: old=%x", oldval);
#if USE_SYNC_SEM
os_sem_post(&gc_sync_semaphore);
#else
if (oldval == 1) {
mutex_lock(&sync_wait_lock); mutex_lock(&sync_wait_lock);
cond_signal(&sync_wait_cond); cond_signal(&sync_wait_cond);
mutex_unlock(&sync_wait_lock); mutex_unlock(&sync_wait_lock);
} }
#endif
} }
#endif /* !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_CONCURRENCY */
@ -355,6 +437,8 @@ static void
worker_sync2(struct sml_worker *worker) worker_sync2(struct sml_worker *worker)
{ {
sml_heap_worker_sync2(worker->thread_local_heap); sml_heap_worker_sync2(worker->thread_local_heap);
extern void sbcl_thread_sync2(struct thread*);
sbcl_thread_sync2(worker->control.vm_thread);
/* all updates by this thread must happen before here */ /* all updates by this thread must happen before here */
decr_sync_counter_release(); decr_sync_counter_release();
} }
@ -407,10 +491,10 @@ static void
worker_leave(struct sml_worker *worker) worker_leave(struct sml_worker *worker)
{ {
unsigned int old; unsigned int old;
assert(IS_ACTIVE(load_relaxed(&worker->control.state))); assert(IS_ACTIVE(load_relaxed(pCTRL_STATE(worker->control))));
/* all updates by this thread must happen before here */ /* all updates by this thread must happen before here */
/* PRESYNC1 -> SYNC1 or PRESYNC2 -> SYNC2 */ /* PRESYNC1 -> SYNC1 or PRESYNC2 -> SYNC2 */
old = fetch_or(release, &worker->control.state, INACTIVE_FLAG | 1); old = fetch_or(release, pCTRL_STATE(worker->control), INACTIVE_FLAG | 1);
if (old == ACTIVE(PRESYNC1)) { if (old == ACTIVE(PRESYNC1)) {
worker_sync1(worker); worker_sync1(worker);
} else if (old == ACTIVE(PRESYNC2)) { } else if (old == ACTIVE(PRESYNC2)) {
@ -429,6 +513,10 @@ worker_leave(struct sml_worker *worker)
SML_PRIMITIVE void SML_PRIMITIVE void
sml_leave() sml_leave()
{ {
// transfer of control from SML to C.
// As long as the user has left the managed context, if the collector asks for
// co-operation from user code (to get roots or change phase), the collector will
// perform the action on behalf of the user thread.
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
assert(worker->user->frame_stack->top == NULL); assert(worker->user->frame_stack->top == NULL);
worker->user->frame_stack->top = CALLER_FRAME_END_ADDRESS(); worker->user->frame_stack->top = CALLER_FRAME_END_ADDRESS();
@ -441,6 +529,7 @@ sml_leave_internal(void *frame_pointer)
{ {
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
void *old_frame_top = worker->user->frame_stack->top; void *old_frame_top = worker->user->frame_stack->top;
TPRINTF(0, "leaving managed context. FP=%p old=%p", frame_pointer, old_frame_top);
if (!old_frame_top) if (!old_frame_top)
worker->user->frame_stack->top = frame_pointer; worker->user->frame_stack->top = frame_pointer;
user_leave(worker->user); user_leave(worker->user);
@ -545,11 +634,13 @@ sml_enter()
worker = worker_enter(worker, user); worker = worker_enter(worker, user);
assert(worker->user->frame_stack->top == CALLER_FRAME_END_ADDRESS()); assert(worker->user->frame_stack->top == CALLER_FRAME_END_ADDRESS());
worker->user->frame_stack->top = NULL; worker->user->frame_stack->top = NULL;
TPRINTF(0, "re-entered Lisp");
} }
void void
sml_enter_internal(void *old_frame_top) sml_enter_internal(void *old_frame_top)
{ {
TPRINTF(0, "re-entering managed context with old_frame_top=%p", old_frame_top);
struct sml_user *user = NULL; struct sml_user *user = NULL;
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
@ -591,27 +682,96 @@ sml_check_internal(void *frame_pointer ATTR_UNUSED)
} }
#endif /* WITHOUT_MULTITHREAD */ #endif /* WITHOUT_MULTITHREAD */
char *phase_names[8] = {"?", "ASYNC", "PRESYNC1", "SYNC1", "PRESYNC2", "SYNC2", "", "MARK"};
char *phase_names_inactive[8] =
{"-?","-<ASYNC>", "-<PRESYNC1>", "-<SYNC1>", "-<PRESYNC2>", "-<SYNC2>", "", "-<MARK>"};
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
extern FILE* get_log_file_for_thread();
extern void backtrace_to_file(FILE*f);
/*void poll_until_phase(long dummy, enum sml_sync_phase this_phase,
enum sml_sync_phase desired_phase) {
enum sml_sync_phase newphase;
struct timespec sleeptime;
TPRINTF(0, "==== waiting for %s phase ====", phase_name(desired_phase));
FILE* f = get_log_file_for_thread();
if (!f) f = stderr;
fprintf(f, "Waiting for phase %s:\n", phase_name(desired_phase));
backtrace_to_file(f);
int i = 0;
sleeptime.tv_sec = 0;
sleeptime.tv_nsec = (1000*1000*1000)/10000;
for(;;) {
++i;
sml_check_internal(&dummy);
newphase = sml_current_phase();
if (newphase != this_phase) {
TPRINTF(0, "detected phase change to %s (sleep iteration %d)", phase_name(newphase), i);
this_phase = newphase;
if (this_phase == desired_phase) break;
} else {
nanosleep(&sleeptime,0);
if (sleeptime.tv_nsec < (1000*1000*1000)/10) sleeptime.tv_nsec *= 2;
}
}
TPRINTF(0, "==== resuming ===");
fprintf(f, "Resuming\n\n");
}*/
ATTR_UNUSED static int heapdump_counter;
__thread unsigned int last_observed_gc_state;
void void
sml_check_internal(void *frame_pointer) sml_check_internal(void *frame_pointer)
{ {
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
unsigned int state = load_relaxed(&worker->control.state); unsigned int state = load_relaxed(pCTRL_STATE(worker->control));
assert(IS_ACTIVE(state)); assert(IS_ACTIVE(state));
if (state != last_observed_gc_state) {
TPRINTF(2, "check_internal: phase chage %x to %x", last_observed_gc_state, state);
struct thread* th = current_thread;
if (th) {
TPRINTF(2, "new_obj=%d stack_store=%d heap_store=%d barrier=%d spinwait=%d",
th->ct_new_objects, th->ct_stack_obj_stores, th->ct_heap_obj_stores,
th->ct_store_barriers, th->ct_spinlock_yields);
th->ct_new_objects = th->ct_stack_obj_stores = th->ct_heap_obj_stores
= th->ct_store_barriers = th->ct_spinlock_yields = 0;
}
last_observed_gc_state = state;
}
switch(state) { switch(state) {
case ACTIVE(PRESYNC1): case ACTIVE(PRESYNC1):
store_relaxed(&worker->control.state, ACTIVE(SYNC1)); #if 0
if (enable_async_gc && getenv("SMLGC_SYNC1_DUMP")) {
++heapdump_counter;
char pathbuf[100];
int n = snprintf(pathbuf, sizeof pathbuf, "heapsnap%d.txt", heapdump_counter);
(void)n;
hexdump_sml_heap_to_file(pathbuf);
TPRINTF(0, "Dumped heap to '%s'", pathbuf);
}
#endif
store_relaxed(pCTRL_STATE(worker->control), ACTIVE(SYNC1));
worker_sync1(worker); worker_sync1(worker);
// if (!enable_async_gc) poll_until_phase(0, ACTIVE(SYNC1), ASYNC);
break; break;
case ACTIVE(PRESYNC2): case ACTIVE(PRESYNC2):
store_relaxed(&worker->control.state, ACTIVE(SYNC2)); store_relaxed(pCTRL_STATE(worker->control), ACTIVE(SYNC2));
user_sync2_check(worker->user, frame_pointer); user_sync2_check(worker->user, frame_pointer);
worker_sync2(worker); worker_sync2(worker);
break; break;
} }
} }
void cooperate_with_gc(uword_t stackptr_at_interrupt) {
TPRINTF(1, "co-operate with GC");
sml_check_internal((void*)stackptr_at_interrupt);
}
#endif /* !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_CONCURRENCY */
void sml_call_with_cleanup(void(*)(void), void(*)(void*,void*,void*), void*); void sml_call_with_cleanup(void(*)(void), void(*)(void*,void*,void*), void*);
@ -620,6 +780,7 @@ struct signal_cleanup_arg {
void (*signal_handler)(void); void (*signal_handler)(void);
}; };
#ifndef LISP_FEATURE_SBCL
static void static void
signal_cleanup(void *arg, void *u, void *e) signal_cleanup(void *arg, void *u, void *e)
{ {
@ -628,7 +789,9 @@ signal_cleanup(void *arg, void *u, void *e)
sml_enter_internal(a->frame_pointer); sml_enter_internal(a->frame_pointer);
sml_unsave_exn(e); sml_unsave_exn(e);
} }
#endif
#ifndef LISP_FEATURE_SBCL
SML_PRIMITIVE void SML_PRIMITIVE void
sml_check(unsigned int flag) sml_check(unsigned int flag)
{ {
@ -649,6 +812,7 @@ sml_check(unsigned int flag)
sml_check_internal(frame_pointer); sml_check_internal(frame_pointer);
} }
#endif
#ifdef WITHOUT_MULTITHREAD #ifdef WITHOUT_MULTITHREAD
enum sml_sync_phase enum sml_sync_phase
@ -669,8 +833,12 @@ sml_current_phase()
* If worker->control.state is SYNC1, then the thread is in SYNC1 * If worker->control.state is SYNC1, then the thread is in SYNC1
* at this instant. * at this instant.
*/ */
return PHASE(load_relaxed(&worker->control.state)); return PHASE(load_relaxed(pCTRL_STATE(worker->control)));
} }
/*void* sml_current_phase_pointer() {
struct sml_worker *worker = worker_tlv_get(current_worker);
return &worker->control.state;
}*/
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
SML_PRIMITIVE void SML_PRIMITIVE void
@ -678,7 +846,7 @@ sml_save()
{ {
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
struct sml_user *user = worker->user; struct sml_user *user = worker->user;
assert(IS_ACTIVE(load_relaxed(&worker->control.state))); assert(IS_ACTIVE(load_relaxed(pCTRL_STATE(worker->control))));
assert(user->frame_stack->top == NULL); assert(user->frame_stack->top == NULL);
user->frame_stack->top = CALLER_FRAME_END_ADDRESS(); user->frame_stack->top = CALLER_FRAME_END_ADDRESS();
} }
@ -688,7 +856,7 @@ sml_unsave()
{ {
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
struct sml_user *user = worker->user; struct sml_user *user = worker->user;
assert(IS_ACTIVE(load_relaxed(&worker->control.state))); assert(IS_ACTIVE(load_relaxed(pCTRL_STATE(worker->control))));
assert(user->frame_stack->top == CALLER_FRAME_END_ADDRESS()); assert(user->frame_stack->top == CALLER_FRAME_END_ADDRESS());
user->frame_stack->top = NULL; user->frame_stack->top = NULL;
} }
@ -734,6 +902,7 @@ sml_saved()
} }
#endif /* NDEBUG */ #endif /* NDEBUG */
static struct timespec wallclock;
void void
sml_control_init() sml_control_init()
{ {
@ -741,6 +910,14 @@ sml_control_init()
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
user_tlv_init(current_user); user_tlv_init(current_user);
#endif /* !WITHOUT_MASSIVETHREADS */ #endif /* !WITHOUT_MASSIVETHREADS */
#ifdef USE_SYNC_SEM
os_sem_init(&gc_sync_semaphore, 0);
#elif defined USE_SYNC_PIPE
pipe(sync_pipe);
assert(sync_pipe[0] != 0 || sync_pipe[1] != 0);
#endif
atomic_init(&sml_check_flag, 0);
clock_gettime(CLOCK_MONOTONIC, &wallclock);
} }
SML_PRIMITIVE void SML_PRIMITIVE void
@ -752,6 +929,7 @@ sml_start(void *arg)
range->bottom = CALLER_FRAME_END_ADDRESS(); range->bottom = CALLER_FRAME_END_ADDRESS();
range->top = NULL; range->top = NULL;
fprintf(stderr, "sml_start arg=%p range=%p:%p\n", arg, range->bottom, range->top);
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
worker = worker_tlv_get(current_worker); worker = worker_tlv_get(current_worker);
if (worker) { if (worker) {
@ -792,8 +970,15 @@ sml_end()
struct sml_worker *worker = worker_tlv_get(current_worker); struct sml_worker *worker = worker_tlv_get(current_worker);
struct sml_user *user = worker->user; struct sml_user *user = worker->user;
assert(IS_ACTIVE(load_relaxed(&worker->control.state))); fprintf(stderr, "sml_end: vmthread = %p\n", worker->control.vm_thread);
assert(user->frame_stack->bottom == CALLER_FRAME_END_ADDRESS()); assert(IS_ACTIVE(load_relaxed(pCTRL_STATE(worker->control))));
if (user->frame_stack->bottom == CALLER_FRAME_END_ADDRESS()) {
} else {
// shouldn't matter, because user_leave does nothing with the stack
// if no massivethreads.
fprintf(stderr, "sml_end: frame stack bottom not right? oh well (%p vs %p)\n",
user->frame_stack->bottom, CALLER_FRAME_END_ADDRESS());
}
user->frame_stack = user->frame_stack->next; user->frame_stack = user->frame_stack->next;
user_leave(worker->user); user_leave(worker->user);
@ -838,7 +1023,7 @@ static void
control_gc() control_gc()
{ {
struct control *first_worker, **p; struct control *first_worker, **p;
struct control **new_users; struct control ATTR_UNUSED **new_users;
struct sml_worker *w; struct sml_worker *w;
#ifndef WITHOUT_MASSIVETHREADS #ifndef WITHOUT_MASSIVETHREADS
@ -846,6 +1031,7 @@ control_gc()
#endif /* !WITHOUT_MASSIVETHREADS */ #endif /* !WITHOUT_MASSIVETHREADS */
first_worker = load_acquire(&workers); first_worker = load_acquire(&workers);
TPRINTF(0, "control_gc: first_worker=%p", first_worker);
if (!first_worker) if (!first_worker)
return; return;
@ -900,6 +1086,8 @@ sml_detach()
void void
sml_detach() sml_detach()
{ {
TPRINTF(1, "sml_detach");
// why would a thread with no 'struct sml_worker' ever get detached?
struct sml_worker *worker = worker_tlv_get_or_init(current_worker); struct sml_worker *worker = worker_tlv_get_or_init(current_worker);
struct sml_user *user = worker ? worker->user : NULL; struct sml_user *user = worker ? worker->user : NULL;
@ -921,43 +1109,118 @@ sml_detach()
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
long this_gc_phase_time[4], cumulative_phase_time[4];
static void static void
change_phase(struct control *list, change_phase(struct control *list,
enum sml_sync_phase old, enum sml_sync_phase new) enum sml_sync_phase old, enum sml_sync_phase new)
{ {
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
int index;
switch (old) {
case ASYNC: index = 0; break;
case SYNC1: index = 1; break;
case SYNC2: index = 2; break;
case MARK: index = 3; break;
default: lose("bad phase change");
}
long delta = (now.tv_sec - wallclock.tv_sec)*1000000
+ (now.tv_nsec - wallclock.tv_nsec)/1000;
this_gc_phase_time[index] = delta;
wallclock = now;
if (smlgc_verbose) printf("change phase %s -> %s\n", phase_name(old), phase_name(new));
if (old==MARK) {
cumulative_phase_time[0] += this_gc_phase_time[0];
cumulative_phase_time[1] += this_gc_phase_time[1];
cumulative_phase_time[2] += this_gc_phase_time[2];
cumulative_phase_time[3] += this_gc_phase_time[3];
long this_sum = this_gc_phase_time[0] + this_gc_phase_time[1] +
this_gc_phase_time[2] + this_gc_phase_time[3];
long tot_sum = cumulative_phase_time[0] + cumulative_phase_time[1] +
cumulative_phase_time[2] + cumulative_phase_time[3];
int this_pct_async = 100 * this_gc_phase_time[0] / this_sum;
int this_pct_sync2 = 100 * this_gc_phase_time[2] / this_sum;
int this_pct_mark = 100 * this_gc_phase_time[3] / this_sum;
// The percent time in sync1 is negligible. Just absorb whatever
// amount makes the sum of percents 100
int this_pct_sync1 = 100 - (this_pct_async + this_pct_sync2 + this_pct_mark);
int tot_pct_async = 100 * cumulative_phase_time[0] / tot_sum;
int tot_pct_sync2 = 100 * cumulative_phase_time[2] / tot_sum;
int tot_pct_mark = 100 * cumulative_phase_time[3] / tot_sum;
int tot_pct_sync1 = 100 - (tot_pct_async + tot_pct_sync2 + tot_pct_mark);
printf("Phase time(asy/syn1/syn2/mrk): cur=%ld+%ld+%ld+%ld (%d/%d/%d/%d) tot=(%d/%d/%d/%d)\n",
this_gc_phase_time[0], this_gc_phase_time[1],
this_gc_phase_time[2], this_gc_phase_time[3],
this_pct_async, this_pct_sync1, this_pct_sync2, this_pct_mark,
tot_pct_async, tot_pct_sync1, tot_pct_sync2, tot_pct_mark);
}
}
TPRINTF(2, "change-phase %s -> %s", phase_name(old), phase_name(new));
struct control *control; struct control *control;
unsigned int state ATTR_UNUSED; unsigned int state ATTR_UNUSED;
for (control = list; control; control = control->next) { for (control = list; control; control = control->next) {
state = fetch_xor(relaxed, &control->state, old ^ new); state = fetch_xor(relaxed, pCTRL_STATE(*control), old ^ new);
assert(PHASE(state) == old); assert(PHASE(state) == old);
} }
} }
#endif /* !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_CONCURRENCY */
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
#if USE_SYNC_SEM
static void wait_for_sync(int count) {
TPRINTF(1, "waiting on sync semaphore (count=%d)", count);
do os_sem_wait(&gc_sync_semaphore); while (--count);
}
#endif
static void raise_gcsignal(struct control* control, ATTR_UNUSED char *reason) {
if (!use_gcsignal) return;
struct thread* vmthread = control->vm_thread;
if (vmthread->os_kernel_tid == 0) {
lose("vmthread %p exists but has no OS thread", vmthread);
}
TPRINTF(1, "signaling ACTIVE thread %p for %s", vmthread, reason);
pthread_kill(vmthread->os_thread, SIG_STOP_FOR_GC);
}
static void static void
sync1(struct control *workers) sync1(struct control *workers)
{ {
struct control *control; struct control *control;
unsigned int old, new, count = 0; unsigned int old, new, count = 0;
assert(load_acquire(&sync_counter) == 0);
fetch_or(relaxed, &sml_check_flag, FLAG_GC); fetch_or(relaxed, &sml_check_flag, FLAG_GC);
for (control = workers; control; control = control->next) { for (control = workers; control; control = control->next) {
pthread_mutex_lock(&control->state_lock);
assert(control->vm_thread);
//if (!vmthread) lose("can't phase change control %p - no vm_thread", control);
old = INACTIVE(PRESYNC1); old = INACTIVE(PRESYNC1);
new = INACTIVE(SYNC1); new = INACTIVE(SYNC1);
if (cmpswap_relaxed(&control->state, &old, new)) if (cmpswap_relaxed(pCTRL_STATE(*control), &old, new)) {
TPRINTF(1, "SYNC1 on behalf of INACTIVE %p", control);
worker_sync1((struct sml_worker *)control); worker_sync1((struct sml_worker *)control);
} else {
raise_gcsignal(control, "sync1");
}
pthread_mutex_unlock(&control->state_lock);
count++; count++;
} }
#if USE_SYNC_SEM
fetch_add(relaxed, &sync_counter, count);
wait_for_sync(count);
#else
if (fetch_add(relaxed, &sync_counter, count) + count != 0) { if (fetch_add(relaxed, &sync_counter, count) + count != 0) {
mutex_lock(&sync_wait_lock); mutex_lock(&sync_wait_lock);
while (!(load_relaxed(&sync_counter) == 0)) while (!(load_relaxed(&sync_counter) == 0))
cond_wait(&sync_wait_cond, &sync_wait_lock); cond_wait(&sync_wait_cond, &sync_wait_lock);
mutex_unlock(&sync_wait_lock); mutex_unlock(&sync_wait_lock);
} }
#endif
fetch_and(relaxed, &sml_check_flag, ~FLAG_GC); fetch_and(relaxed, &sml_check_flag, ~FLAG_GC);
@ -1005,33 +1268,46 @@ sync2(struct control *workers)
struct control *control; struct control *control;
unsigned int old, new, count = 0; unsigned int old, new, count = 0;
assert(load_acquire(&sync_counter) == 0);
sml_heap_collector_sync2(); sml_heap_collector_sync2();
fetch_or(relaxed, &sml_check_flag, FLAG_GC); fetch_or(relaxed, &sml_check_flag, FLAG_GC);
for (control = workers; control; control = control->next) { for (control = workers; control; control = control->next) {
pthread_mutex_lock(&control->state_lock);
assert(control->vm_thread);
// if (!vmthread) lose("can't phase change control %p - no vm_thread", control);
old = INACTIVE(PRESYNC2); old = INACTIVE(PRESYNC2);
new = ACTIVE(SYNC2); new = ACTIVE(SYNC2);
/* all updates so far must happen before here */ /* all updates so far must happen before here */
if (cmpswap_acquire(&control->state, &old, new)) { if (cmpswap_acquire(pCTRL_STATE(*control), &old, new)) {
TPRINTF(1, "SYNC2 on behalf of INACTIVE %p", control);
struct sml_worker *w = (struct sml_worker *)control; struct sml_worker *w = (struct sml_worker *)control;
#ifdef WITHOUT_MASSIVETHREADS #ifdef WITHOUT_MASSIVETHREADS
user_sync2(w->user); user_sync2(w->user);
#endif /* WITHOUT_MASSIVETHREADS */ #endif /* WITHOUT_MASSIVETHREADS */
worker_sync2(w); worker_sync2(w);
/* all updates by this thread must happen before here */ /* all updates by this thread must happen before here */
store_release(&control->state, INACTIVE(SYNC2)); store_release(pCTRL_STATE(*control), INACTIVE(SYNC2));
} else {
raise_gcsignal(control, "sync2");
} }
pthread_mutex_unlock(&control->state_lock);
count++; count++;
} }
/* all updates so far must happen before here */ /* all updates so far must happen before here */
#if USE_SYNC_SEM
fetch_add(relaxed, &sync_counter, count);
wait_for_sync(count);
#else
if (fetch_add(acquire, &sync_counter, count) + count != 0) { if (fetch_add(acquire, &sync_counter, count) + count != 0) {
mutex_lock(&sync_wait_lock); mutex_lock(&sync_wait_lock);
while (!(load_acquire(&sync_counter) == 0)) while (!(load_acquire(&sync_counter) == 0))
cond_wait(&sync_wait_cond, &sync_wait_lock); cond_wait(&sync_wait_cond, &sync_wait_lock);
mutex_unlock(&sync_wait_lock); mutex_unlock(&sync_wait_lock);
} }
#endif
fetch_and(relaxed, &sml_check_flag, ~FLAG_GC); fetch_and(relaxed, &sml_check_flag, ~FLAG_GC);
} }
@ -1096,9 +1372,18 @@ sml_gc()
#endif /* !defined WITHOUT_MULTITHREAD && defined WITHOUT_CONCURRENCY */ #endif /* !defined WITHOUT_MULTITHREAD && defined WITHOUT_CONCURRENCY */
#ifndef WITHOUT_CONCURRENCY #ifndef WITHOUT_CONCURRENCY
extern pthread_rwlock_t valid_obj_lock;
void void
sml_gc() sml_gc()
{ {
struct timespec now;
extern struct timespec lisp_init_time;
clock_gettime(CLOCK_MONOTONIC, &now);
int sec = now.tv_sec - lisp_init_time.tv_sec;
long millisec = (now.tv_nsec - lisp_init_time.tv_nsec) / 1000000;
float fsec = (float)sec + (float)millisec/1000.0;
fprintf(stderr, "[%f] GC cycle %d\n", fsec, get_gc_cycle_number());
TPRINTF(1, "start of cycle %d", get_gc_cycle_number());
struct control *current_workers; struct control *current_workers;
control_gc(); control_gc();
@ -1112,6 +1397,11 @@ sml_gc()
current_workers = load_relaxed(&workers); current_workers = load_relaxed(&workers);
spin_unlock(&worker_creation_lock); spin_unlock(&worker_creation_lock);
/* Setting the refState to REPEAT now, before tracing, is the right thing
* because it causes mutators to record loads through weak objects
* without checking the referent color. */
if (smlgc_verbose) printf("Setting refState_REPEAT\n");
atomic_store(&weakRefState, weakRefState_REPEAT);
change_phase(current_workers, ASYNC, PRESYNC1); change_phase(current_workers, ASYNC, PRESYNC1);
sync1(current_workers); sync1(current_workers);
@ -1144,22 +1434,43 @@ sml_gc()
change_phase(current_workers, SYNC2, MARK); change_phase(current_workers, SYNC2, MARK);
if (smlgc_verbose) fprintf(stderr, "START MARK\n");
sml_heap_collector_mark(); sml_heap_collector_mark();
/* Mutators can operate with the store barrier off, but can not freely reference
* weak objects yet. We need to clear the dead ones first. The marking bitmap
* is intact, so mutators can and must utilize the bitmap to decide which weak
* refs are live.
* Weak objects newly allocated after the marking cycle can only point to black
* objects because there is no way to hold a white or gray reference.
* Unfortunately, if we were to change phase to ASYNC now, it would cause confusion
* as to whether large objects are allocated black, which would make
* heap_check_alive potentially return no when it meant yes. */
/* ASYNC: turn off snapshot barrier */ /* ASYNC: turn off snapshot barrier */
spin_lock(&worker_creation_lock); spin_lock(&worker_creation_lock);
new_worker_phase = ASYNC; new_worker_phase = ASYNC;
current_workers = load_relaxed(&workers); current_workers = load_relaxed(&workers);
spin_unlock(&worker_creation_lock); spin_unlock(&worker_creation_lock);
pthread_rwlock_wrlock(&valid_obj_lock);
change_phase(current_workers, MARK, ASYNC); change_phase(current_workers, MARK, ASYNC);
sml_heap_collector_after_mark();
#ifdef LISP_FEATURE_SBCL
extern void finalizer_thread_wake();
SYMBOL(GC_EPOCH)->value = make_fixnum(get_gc_cycle_number());
finalizer_thread_wake();
#else
sml_run_finalizer(); sml_run_finalizer();
#endif
sml_heap_collector_async(); sml_heap_collector_async();
pthread_rwlock_unlock(&valid_obj_lock);
} }
#endif /* !WITHOUT_MULTITHREAD && !WITHOUT_CONCURRENCY */ #endif /* !WITHOUT_MULTITHREAD && !WITHOUT_CONCURRENCY */
#ifndef LISP_FEATURE_SBCL
static void * static void *
frame_enum_ptr(void *frame_end, void (*trace)(void **, void *), void *data) frame_enum_ptr(void *frame_end, void (*trace)(void **, void *), void *data)
{ {
@ -1205,6 +1516,7 @@ sml_stack_enum_ptr(struct sml_user *user,
} }
} }
} }
#endif
#ifdef WITHOUT_MULTITHREAD #ifdef WITHOUT_MULTITHREAD
void void
@ -1253,3 +1565,61 @@ sml_exit(int status)
} }
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
int worker_is_inactive() {
struct sml_worker*w = (void*)workers;
unsigned int state = load_relaxed(pCTRL_STATE(w->control));
return state & INACTIVE_FLAG;
}
void* get_user_stack_hot_end() {
return ((struct sml_worker*)workers)->user->frame_stack->top;
}
void smlgc_unregister_lisp_thread(struct thread* thread)
{
struct sml_worker *worker = worker_tlv_get(current_worker);
// stop-for-GC *must* be blocked. Consider: we just acquired the lock,
// if GC wants to ask us to cooperate via the signal, GC needs to acquire
// the lock in order to decide if we're alive. -> Deadlock.
pthread_mutex_lock(&worker->control.state_lock);
// This says to treat the pthread _as_ _if_ it no longer exists
// (technically pthread_t is opaque and can't be tested for 0 or
// other "known invalid" bit pattern)
worker->control.vm_thread->os_kernel_tid = 0;
//worker->control.vm_thread = NULL;
pthread_mutex_unlock(&worker->control.state_lock);
struct alloc_ptr* control_ap = (void*)worker->thread_local_heap;
assert(control_ap[4].blocksize_bytes == 1<<4);
int nbytes = 9*sizeof (struct alloc_ptr);
memcpy(&control_ap[4], &thread->ap4, nbytes);
memset(&thread->ap4, 0, nbytes);
}
#include "segment.inc"
void* find_owning_control(struct segment* seg, int blocksize_log2)
{
struct control* c = workers;
for ( ; c ; c = c->next ) {
struct thread* th = c->vm_thread;
if (th && blocksize_log2 >= 4) {
struct alloc_ptr* ap = &th->ap4;
ap += (blocksize_log2 - 4);
if (segment_addr(ap->freebit.ptr) == seg) return c;
} else {
struct alloc_ptr* ap = (void*)((struct sml_worker*)c)->thread_local_heap;
ap += blocksize_log2;
if (segment_addr(ap->freebit.ptr) == seg) return c;
}
}
return 0;
}
char* vm_thread_name(struct thread* th);
char* owning_thread_name(struct segment* seg, int blocksize_log2)
{
struct control* c = find_owning_control(seg, blocksize_log2);
if (!c) return 0;
if (c->vm_thread)
return vm_thread_name(c->vm_thread);
else
return "GC";
}

View file

@ -68,6 +68,9 @@ void sml_heap_collector_sync2(void);
* At this time, all all mutators has switched to SYNC2. * At this time, all all mutators has switched to SYNC2.
*/ */
void sml_heap_collector_mark(void); void sml_heap_collector_mark(void);
/* Called after switching from MARK to ASYNC but before runing
* finalizers or performing asynchronous sweeping */
void sml_heap_collector_after_mark(void);
/* /*
* Called when the collector has finished MARK and switched to ASYNC. * Called when the collector has finished MARK and switched to ASYNC.
@ -92,12 +95,20 @@ SML_PRIMITIVE void sml_write(void *obj, void **writeaddr, void *new_value);
/* /*
* Check the liveness of the given object. * Check the liveness of the given object.
* slot : pointer to a pointer to be checked * If obj has been marked as live in sml_heap_collector_mark,
* If *slot has been marked as live in sml_heap_collector_mark, * this returns true.
* this returns true and update obj with its forwarded pointer.
* This is called after sml_heap_collector_mark and before * This is called after sml_heap_collector_mark and before
* sml_heap_collector_async. * sml_heap_collector_async.
*/ */
int sml_heap_check_alive(void **slot); int sml_heap_check_alive(void *obj);
extern _Atomic(int) n_remset_insertions_skipped_large,
n_remset_insertions_skipped_small;
extern _Atomic(int) weakRefState;
#define weakRefState_NORMAL 0
#define weakRefState_TRACING 1
#define weakRefState_REPEAT 2
#define weakRefState_SPLAT 3
#endif /* SMLSHARP__HEAP_H__ */ #endif /* SMLSHARP__HEAP_H__ */

File diff suppressed because it is too large Load diff

2023
smlsharpgc/lispobj.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,26 @@
#include <limits.h> #include <limits.h>
#ifdef LISP_FEATURE_SBCL
#define OBJ_BEGIN(obj) obj
#define OBJ_HEADER(obj) (*(long*)(obj))
#define OBJ_FLAG_SKIP 0
#define OBJ_HEADER_SIZE 0
#include "genesis/constants.h"
#include "genesis/closure.h"
static inline void* untagged_baseptr(lispobj taggedptr) {
// no need to read a word at native_pointer(thing) if it isn't fun-pointer tagged
lispobj* base = (lispobj*)(taggedptr & ~LOWTAG_MASK);
if ((taggedptr & LOWTAG_MASK) != FUN_POINTER_LOWTAG) return base;
return widetag_of(base) != SIMPLE_FUN_WIDETAG ? (void*)base :
(void*)fun_code_header((struct simple_fun*)base);
}
#else
/* /*
* size of a bitmap word in heap objects and stack frames. * size of a bitmap word in heap objects and stack frames.
*/ */
@ -118,5 +138,6 @@
* sentinel. OBJ_STR_SIZE returns the length of the string except for * sentinel. OBJ_STR_SIZE returns the length of the string except for
* the sentinel. */ * the sentinel. */
#define OBJ_STR_SIZE(obj) ((size_t)(OBJ_SIZE(obj) - 1)) #define OBJ_STR_SIZE(obj) ((size_t)(OBJ_SIZE(obj) - 1))
#endif
#endif /* SMLSHARP__OBJECT_H__ */ #endif /* SMLSHARP__OBJECT_H__ */

56
smlsharpgc/segment.inc Normal file
View file

@ -0,0 +1,56 @@
#ifndef SEGMENT_SIZE_LOG2
#define SEGMENT_SIZE_LOG2 15 /* 32k */
#endif /* SEGMENT_SIZE_LOG2 */
#define SEGMENT_SIZE (1U << SEGMENT_SIZE_LOG2)
#ifndef SEG_RANK
#define SEG_RANK 3
#endif /* SEG_RANK */
struct segment_layout {
unsigned int blocksize_bytes;
unsigned int bitmap_base[SEG_RANK + 1];
sml_bmword_t bitmap_sentinel[SEG_RANK];
unsigned int stack_offset;
unsigned int stack_limit;
unsigned int block_offset;
unsigned int num_blocks;
unsigned int block_limit;
};
struct segment {
struct list_item as_list;
struct stack_slot {
_Atomic(void *) next;
} *stack; /* == seg + layout->stack_offset */
char *block_base; /* == seg + layout->block_offset */
/* If block_base is null, this segment is in free list */
#if !defined WITHOUT_MULTITHREAD && !defined WITHOUT_CONCURRENCY
/* do not modify snapshot_free during the collector traces objects. */
char *snapshot_free;
#endif /* !WITHOUT_MULTITHREAD && !WITHOUT_CONCURRENCY */
const struct segment_layout *layout;
unsigned int blocksize_log2;
int free_count;
/* The free_count field not only holds the count (as its name) but
* indicates which set the segment is in.
* If free_count is negative, the segment is in the collect set
* and its absolute value is the count of unmarked blocks.
* If free_count is zero, the segment is in the filled set.
* If free_count is positive, the segment is in the partial set.
*/
};
/* assume that segment address is a multiple of SEGMENT_SIZE */
static inline struct segment *
segment_addr(const void *p)
{
return (void*)((uintptr_t)p & ~((uintptr_t)(SEGMENT_SIZE - 1)));
}
/* thread-local use only */
struct object_list {
struct stack_slot begin;
struct stack_slot *last;
int count;
};
void object_list_append(struct object_list *l, void *obj);

View file

@ -7,6 +7,30 @@
#ifndef SMLSHARP__SMLSHARP_H__ #ifndef SMLSHARP__SMLSHARP_H__
#define SMLSHARP__SMLSHARP_H__ #define SMLSHARP__SMLSHARP_H__
extern int use_smlgc;
extern int enable_async_gc;
#define DEADBEEF 0xFFFFFFFFDEADBEEF
#define VERBOSE_LOGGING 0
extern int n_smlgcs;
extern void tprintf_(char *fmt, ...);
#define TPRINTF(msgclass, fmt, ...) if(0 /* msgclass & 2 */) tprintf_(fmt, ##__VA_ARGS__)
extern void suspend_mutator(char* reason);
extern void unsuspend_mutator();
#include <stdio.h>
extern FILE* get_gc_thread_log();
void show_map(char*,void*);
void show_fractional_usage();
extern void get_segment_pool_bounds(char* bounds[2]);
extern char *phase_names[8];
extern char *phase_names_inactive[8];
//extern FILE* get_large_object_logfile();
extern int get_gc_cycle_number();
static inline char* phase_name(int phase) {
if (phase & 0x10U) return phase_names_inactive[phase-0x10];
return phase_names[phase];
}
/* /*
* One of the following macros may be defined by the command line: * One of the following macros may be defined by the command line:
* - WITHOUT_MULTITHREAD: Remove multithread support at all. * - WITHOUT_MULTITHREAD: Remove multithread support at all.
@ -26,6 +50,8 @@
#define WITHOUT_MASSIVETHREADS #define WITHOUT_MASSIVETHREADS
#endif /* WITHOUT_CONCURRENCY */ #endif /* WITHOUT_CONCURRENCY */
#define DPRINTF(x) {}
#if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L #if !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
# error C99 is required # error C99 is required
#endif #endif
@ -33,6 +59,12 @@
#error GCC version 4.0 or later is required #error GCC version 4.0 or later is required
#endif #endif
/*#ifdef FOO_LISP_FEATURE_SBCL
#include "smlsharp-config.h"
#infdef WITHOUT_MASSIVETHREADS
#define WITHOUT_MASSIVETHREADS
#endif */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
# include "config.h" # include "config.h"
#endif #endif
@ -155,16 +187,20 @@
#define cond_signal(c) ((void)0) #define cond_signal(c) ((void)0)
#endif /* !WITHOUT_MULTITHREAD */ #endif /* !WITHOUT_MULTITHREAD */
#undef ATOMIC_VAR_INIT
#define ATOMIC_VAR_INIT(x) x
/* spin lock */ /* spin lock */
#ifndef WITHOUT_MULTITHREAD #ifndef WITHOUT_MULTITHREAD
typedef struct { _Atomic(int) lock; } sml_spinlock_t; typedef struct { _Atomic(int) lock; } sml_spinlock_t;
#define SPIN_LOCK_INIT {ATOMIC_VAR_INIT(0)} #define SPIN_LOCK_INIT {ATOMIC_VAR_INIT(0)}
extern void bump_spinlock_busyct();
static inline void spin_lock(sml_spinlock_t *l) { static inline void spin_lock(sml_spinlock_t *l) {
int old, i = 8192; int old, i = 8192;
for (;;) { for (;;) {
old = 0; old = 0;
if (cmpswap_weak_acquire(&l->lock, &old, 1)) break; if (cmpswap_weak_acquire(&l->lock, &old, 1)) break;
if (--i == 0) { sched_yield(); i = 8192; } if (--i == 0) { sched_yield(); i = 8192; bump_spinlock_busyct(); }
} }
} }
static inline void spin_unlock(sml_spinlock_t *l) { static inline void spin_unlock(sml_spinlock_t *l) {
@ -397,9 +433,10 @@ void sml_msg_init(void);
* If allocation failed, program exits immediately. * If allocation failed, program exits immediately.
*/ */
void *sml_xmalloc(size_t size) ATTR_MALLOC; void *sml_xmalloc(size_t size) ATTR_MALLOC;
void *sml_xrealloc(void *p, size_t size) ATTR_MALLOC; //void *sml_xrealloc(void *p, size_t size) ATTR_MALLOC;
#define xmalloc sml_xmalloc #define xmalloc(addr,reason) sml_xmalloc(addr)
#define xrealloc sml_xrealloc //#define xrealloc sml_xrealloc
#define xfree(addr,reason) free(addr)
/* /*
* GC root set management including stack frame layouts * GC root set management including stack frame layouts
@ -415,7 +452,8 @@ void sml_gcroot(void *, void (*)(void), void *, void *);
struct sml_gcroot *sml_gcroot_load(void (* const *)(void *), unsigned int); struct sml_gcroot *sml_gcroot_load(void (* const *)(void *), unsigned int);
void sml_gcroot_unload(struct sml_gcroot *); void sml_gcroot_unload(struct sml_gcroot *);
const struct sml_frame_layout *sml_lookup_frametable(void *retaddr); const struct sml_frame_layout *sml_lookup_frametable(void *retaddr);
void sml_global_enum_ptr(void (*trace)(void **, void *), void *data); void sml_global_enum_ptr(void (*trace)(void *, void *), void *data);
void lisp_global_enum_ptr(void (*trace)(uintptr_t, void *), void *data);
/* remove all thread-local data for SML# */ /* remove all thread-local data for SML# */
void sml_deatch(void); void sml_deatch(void);
@ -458,6 +496,7 @@ void sml_gc(void);
struct sml_user; struct sml_user;
void sml_stack_enum_ptr(struct sml_user *, void (*)(void **, void *), void *); void sml_stack_enum_ptr(struct sml_user *, void (*)(void **, void *), void *);
void lisp_stack_enum_ptr(struct sml_user *, void (*)(uintptr_t, void *), void *);
int sml_set_signal_handler(void(*)(void)); int sml_set_signal_handler(void(*)(void));
int sml_send_signal(void); int sml_send_signal(void);
@ -501,6 +540,9 @@ SML_PRIMITIVE void *sml_unsave_exn(void *);
/* /*
* SML# heap object management * SML# heap object management
*/ */
struct list_item {
struct list_item *next;
};
SML_PRIMITIVE void *sml_alloc(unsigned int objsize); SML_PRIMITIVE void *sml_alloc(unsigned int objsize);
SML_PRIMITIVE void *sml_load_intinf(const char *hexsrc); SML_PRIMITIVE void *sml_load_intinf(const char *hexsrc);
SML_PRIMITIVE void **sml_find_callback(void *codeaddr, void *env); SML_PRIMITIVE void **sml_find_callback(void *codeaddr, void *env);
@ -515,6 +557,7 @@ struct sml_intinf;
typedef struct sml_intinf sml_intinf_t; typedef struct sml_intinf sml_intinf_t;
void sml_obj_enum_ptr(void *obj, void (*callback)(void **, void *), void *); void sml_obj_enum_ptr(void *obj, void (*callback)(void **, void *), void *);
int lispobj_enum_ptr(void *obj, void (*callback)(uintptr_t, void *), void *);
void *sml_obj_alloc(unsigned int objtype, size_t payload_size); void *sml_obj_alloc(unsigned int objtype, size_t payload_size);
NOINLINE char *sml_str_new(const char *str); NOINLINE char *sml_str_new(const char *str);
char *sml_str_new2(const char *str, unsigned int len); char *sml_str_new2(const char *str, unsigned int len);
@ -561,11 +604,7 @@ ATTR_NORETURN void sml_exit(int status);
/* /*
* bit pointer * bit pointer
*/ */
typedef uint32_t sml_bmword_t; #include "alloc_ptr.h"
struct sml_bitptr { const sml_bmword_t *ptr; sml_bmword_t mask; };
struct sml_bitptrw { sml_bmword_t *wptr; sml_bmword_t mask; };
typedef struct sml_bitptr sml_bitptr_t;
typedef struct sml_bitptrw sml_bitptrw_t;
#define BITPTR_WORDBITS 32U #define BITPTR_WORDBITS 32U
#define BITPTR(p,n) \ #define BITPTR(p,n) \
((sml_bitptr_t){.ptr = (p) + (n) / 32U, .mask = 1 << ((n) % 32U)}) ((sml_bitptr_t){.ptr = (p) + (n) / 32U, .mask = 1 << ((n) % 32U)})
@ -579,9 +618,9 @@ typedef struct sml_bitptrw sml_bitptrw_t;
#define BITPTRW_EQUAL(b1,b2) ((b1).wptr == (b2).wptr && (b1).mask == (b2).mask) #define BITPTRW_EQUAL(b1,b2) ((b1).wptr == (b2).wptr && (b1).mask == (b2).mask)
#define BITPTR_TEST(b) (*(b).ptr & (b).mask) #define BITPTR_TEST(b) (*(b).ptr & (b).mask)
#define BITPTR_WORD(b) (*(b).ptr) #define BITPTR_WORD(b) (*(b).ptr)
#define BITPTR_EQUAL(b1,b2) ((b1).ptr == (b2).ptr && (b1).mask == (b2).mask) //#define BITPTR_EQUAL(b1,b2) ((b1).ptr == (b2).ptr && (b1).mask == (b2).mask)
#define BITPTR_WORDINDEX(b,begin) ((b).ptr - (begin)) #define BITPTR_WORDINDEX(b,begin) ((b).ptr - (begin))
#define BITPTR_NEXTWORD(b) ((b).ptr++, (b).mask = 1U) //#define BITPTR_NEXTWORD(b) ((b).ptr++, (b).mask = 1U)
/* BITPTR_NEXT0: move to next 0 bit in the current word. /* BITPTR_NEXT0: move to next 0 bit in the current word.
* mask becomes zero if failed. */ * mask becomes zero if failed. */
@ -633,20 +672,37 @@ typedef struct sml_bitptrw sml_bitptrw_t;
#define UncommitPage(addr, size) \ #define UncommitPage(addr, size) \
VirtualFree(addr, size, MEM_DECOMMIT) VirtualFree(addr, size, MEM_DECOMMIT)
#else #else
/* inclue <sys/mman.h> */ #include <sys/mman.h>
/* inclue <unistd.h> */ /* inclue <unistd.h> */
#define GetPageSize() sysconf(_SC_PAGESIZE) void *wrapped_mmap(void *addr, size_t length, int prot, int flags, int fd, long offset);
int wrapped_munmap(void *addr, size_t length);
int wrapped_mprotect(void *addr, size_t len, int prot);
#define GetPageSize() getpagesize()
#define AllocPageError MAP_FAILED #define AllocPageError MAP_FAILED
#define AllocPage(addr, size) \ #define AllocPage(addr, size) \
mmap(addr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0) mmap(addr, size, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0)
#define ReservePage(addr, size) \ #define ReservePage(addr, size) \
mmap(addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0) mmap(addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0)
#define ReleasePage(addr, size) \ #define ReleasePage(addr, size) \
munmap(addr, size) munmap(addr, size)
#define CommitPage(addr, size) \ #define CommitPage(addr, size) \
mprotect(addr, size, PROT_READ | PROT_WRITE) mprotect(addr, size, PROT_EXEC | PROT_READ | PROT_WRITE)
#define UncommitPage(addr, size) \ #define UncommitPage(addr, size) \
mmap(addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0) mmap(addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0)
#endif /* MINGW32 */ #endif /* MINGW32 */
void trace_enter(char*);
void trace_leave(char*);
void trace_leaf(char*);
extern int gc_verbose;
extern void hexdump_sml_heap_to_file(char*);
extern int ignorable_space_p(uintptr_t);
extern int large_code_subspace_p(char*);
//extern void* untagged_baseptr(uintptr_t);
extern void* otherptr_mseg(uintptr_t);
extern int smlgc_verbose;
extern void ldb_monitor();
#endif /* SMLSHARP__SMLSHARP_H__ */ #endif /* SMLSHARP__SMLSHARP_H__ */

View file

@ -5,3 +5,8 @@
"src/assembly/{arch}/array" "src/assembly/{arch}/array"
"src/assembly/{arch}/arith" "src/assembly/{arch}/arith"
"src/assembly/{arch}/alloc")) "src/assembly/{arch}/alloc"))
(format t "~&Asm routine hit counters:~%")
(let ((v sb-vm::*asm-routine-hit-counter-map*))
(dotimes (i (length v))
(format t " ~3d = ~a~%" i (aref v i))))

View file

@ -11,6 +11,209 @@
(in-package "SB-VM") (in-package "SB-VM")
#+sb-assembling
(symbol-macrolet ((this-cons rax-tn)
(ap rdi-tn)
(num-conses rcx-tn)
(curbit rdx-tn)
(bmwordptr rsi-tn)
(allocated rbx-tn) ; value loaded from *bmwordptr
(element r8-tn)
(last r9-tn)
(context r10-tn))
(with-bitmap-ap (ap)
;;; Take CONTEXT in RAX and count (number of cons cells) in ECX,
;;; returning a list in RAX. No other registers are affected.
;;; return-address <-- RSP on entry
;;; [saved registers] 7 words
;;; dummy cons car
;;; dummy cons cdr <-- RSP during execution
(defun generate-list-allocator ()
(assemble ()
(inst mov context rax-tn)
;;;(inst mov ap (thread-slot-ea thread-allocptr16-slot))
(inst lea ap (thread-slot-ea thread-ap4-slot))
(inst sub rsp-tn 16) ; rsp := dummy (struct cons)
(inst lea last (ea list-pointer-lowtag rsp-tn))
OUTER
(inst mov bmwordptr (freebit.ptr))
(inst mov :dword curbit (freebit.mask))
(inst mov :dword allocated (ea bmwordptr))
;; if (!(allocated & curbit))
(inst test :dword allocated curbit)
(inst jmp :nz SLOW-PATH)
;; quick path: allocate at least 1 cons
(inst mov this-cons (freeptr))
(inst or :byte this-cons list-pointer-lowtag) ; compute tagged ptr
INNER
(assert-word-unused (ea (- list-pointer-lowtag) this-cons))
(storew this-cons last cons-cdr-slot list-pointer-lowtag)
(inst mov element (ea context))
(inst sub context 8)
(storew element this-cons cons-car-slot list-pointer-lowtag)
(inst mov last this-cons)
(inst add this-cons 16)
(inst rol :dword curbit 1)
(inst jmp :nc NO-CARRY)
(inst add bmwordptr 4)
(inst mov :dword allocated (ea bmwordptr))
NO-CARRY
(inst dec :dword num-conses)
(inst jmp :z WRITEBACK) ; done
(inst test :dword allocated curbit)
(inst jmp :z INNER)
WRITEBACK ; exit from fast loop, flushing cached fields of 'struct alloc_ptr'
(inst mov (freebit.ptr) bmwordptr)
(inst mov (freebit.mask) curbit)
(inst sub :byte this-cons list-pointer-lowtag)
(inst mov (freeptr) this-cons)
(inst jrcxz TERMINATE)
SLOW-PATH
;; Although this is a constructor, the optimization to elide snooping barriers
;; is inadmissible. The _previously_ _allocated_ cell has a store to it,
;; therefore this cell, being the new value, has to be remembered.
(inst call (make-fixup 'bitmap-cons-fallback :assembly-routine))
(assert-word-unused (ea this-cons))
(inst mov element (ea context))
(inst sub context 8)
(storew element this-cons cons-car-slot 0)
(inst or :byte this-cons list-pointer-lowtag) ; compute tagged ptr
(storew this-cons last cons-cdr-slot list-pointer-lowtag)
(inst mov last this-cons)
(inst dec :dword num-conses)
(inst jmp :nz OUTER)
TERMINATE))
(define-assembly-routine (bitmap-listify) ()
(regs-pushlist rbx rdx rsi rdi r8 r9 r10)
(generate-list-allocator)
(storew nil-value last cons-cdr-slot list-pointer-lowtag)
(loadw rax-tn rsp-tn cons-cdr-slot 0) ; (CDR dummy)
(inst add rsp-tn 16)
(regs-poplist rbx rdx rsi rdi r8 r9 r10))
(define-assembly-routine (bitmap-listify*) ()
(regs-pushlist rbx rdx rsi rdi r8 r9 r10)
(generate-list-allocator)
(inst mov element (ea context))
(storew element last cons-cdr-slot list-pointer-lowtag)
(loadw rax-tn rsp-tn cons-cdr-slot 0) ; (CDR dummy)
(inst add rsp-tn 16)
(regs-poplist rbx rdx rsi rdi r8 r9 r10))
;;; Take RAX = element, RCX = count and return (MAKE-LIST ELEMENT COUNT)
;;; in RAX, affecting no other registers.
;;; return-address <-- RSP on entry
;;; [saved registers] 7 words
;;; dummy cons car
;;; dummy cons cdr <-- RSP during execution
#+nil (define-assembly-routine (make-list-helper) ()
(count-hit make-list-helper)
(regs-pushlist rbx rdx rsi rdi r8 r9)
(inst mov element rax-tn)
(inst lea ap (thread-slot-ea thread-ap4-slot))
(inst sub rsp-tn 16) ; rsp := dummy (struct cons)
(inst lea last (ea list-pointer-lowtag rsp-tn))
;; top of outer do { } loop
OUTER
(inst mov bmwordptr (freebit.ptr))
(inst mov :dword curbit (freebit.mask))
(inst mov :dword allocated (ea bmwordptr))
;; if (!(allocated & curbit))
(inst test :dword allocated curbit)
(inst jmp :nz SLOW-PATH)
;; quick path: allocate at least 1 cons
(inst mov this-cons (freeptr))
(inst or :byte this-cons list-pointer-lowtag) ; compute tagged ptr
;; top of inner do {} loop
INNER
(assert-word-unused (ea (- list-pointer-lowtag) this-cons))
(storew this-cons last cons-cdr-slot list-pointer-lowtag)
(storew element this-cons cons-car-slot list-pointer-lowtag)
(inst mov last this-cons)
(inst add this-cons 16)
(inst rol :dword curbit 1)
(inst jmp :nc NO-CARRY)
(inst add bmwordptr 4)
(inst mov :dword allocated (ea bmwordptr))
NO-CARRY
(inst dec :dword num-conses)
(inst jmp :z WRITEBACK) ; done
(inst test :dword allocated curbit)
(inst jmp :z INNER)
WRITEBACK ; exit from fast loop, flushing cached fields of 'struct alloc_ptr'
(inst mov (freebit.ptr) bmwordptr)
(inst mov (freebit.mask) curbit)
(inst sub :byte this-cons list-pointer-lowtag)
(inst mov (freeptr) this-cons)
(inst jrcxz TERMINATE)
SLOW-PATH
(inst call (make-fixup 'bitmap-alloc-fallback :assembly-routine))
(assert-word-unused (ea this-cons))
(storew element this-cons cons-car-slot 0)
(inst or :byte this-cons list-pointer-lowtag) ; compute tagged ptr
(storew this-cons last cons-cdr-slot list-pointer-lowtag)
(inst mov last this-cons)
(inst dec :dword num-conses)
(inst jmp :nz OUTER)
TERMINATE
(storew nil-value last cons-cdr-slot list-pointer-lowtag)
(loadw rax-tn rsp-tn cons-cdr-slot 0) ; (CDR dummy)
(inst add rsp-tn 16)
(regs-poplist rbx rdx rsi rdi r8 r9))
)) ; end SYMBOL-MACROLET
;;; Fallback routine are called after the inline code has tested 'freebit'
;;; and found it to be unavailable.
;;; All registers are preserved except for the destination.
;;; The general routine takes alloc-ptr in the destination reg.
#+sb-assembling
(macrolet
((gen-fallbacks ()
`(progn
,@(loop
for reg in '(rax rbx rcx rdx rsi rdi r8 r9 r10 r11 r12 r14 r15)
collect
`(define-assembly-routine
(,(symbolicate reg "-ALLOC-FALLBACK")
(:return-style :none)
(:export ,(symbolicate reg "-ALLOC16-FALLBACK")
,(symbolicate reg "-ALLOC32-FALLBACK")
,(symbolicate reg "-ALLOC64-FALLBACK")))
((:temp arg/res unsigned-reg ,(symbolicate reg "-OFFSET")))
ENTRY
(count-hit ,(symbolicate reg "-ALLOC-FALLBACK"))
;; Save RDI unless it is the arg/result register
,@(unless (eq reg 'rdi)
'((inst push rdi-tn)
(inst mov rdi-tn arg/res)))
;; Save RAX unless it is the destination register
,@(unless (eq reg 'rax)
'((inst push rax-tn)))
(inst call (make-fixup 'bitmap-alloc-fallback :assembly-routine))
,@(unless (eq reg 'rax) ; move the result if needed
`((inst mov arg/res rax-tn)
(inst pop rax-tn)))
;; Restore RDI
,@(unless (eq reg 'rdi)
'((inst pop rdi-tn)))
(inst ret)
,(symbolicate reg "-ALLOC16-FALLBACK")
(count-hit ,(symbolicate reg "-ALLOC16-FALLBACK"))
(inst lea arg/res (thread-slot-ea thread-ap4-slot))
(inst jmp entry)
,(symbolicate reg "-ALLOC32-FALLBACK")
(count-hit ,(symbolicate reg "-ALLOC32-FALLBACK"))
(inst lea arg/res (thread-slot-ea thread-ap5-slot))
(inst jmp entry)
,(symbolicate reg "-ALLOC64-FALLBACK")
(count-hit ,(symbolicate reg "-ALLOC64-FALLBACK"))
(inst lea arg/res (thread-slot-ea thread-ap6-slot))
(inst jmp entry)
)))))
(gen-fallbacks))
;;;; Signed and unsigned bignums from word-sized integers. Argument ;;;; Signed and unsigned bignums from word-sized integers. Argument
;;;; and return in the same register. No VOPs, as these are only used ;;;; and return in the same register. No VOPs, as these are only used
;;;; when called from a vop. ;;;; when called from a vop.
@ -32,6 +235,7 @@
`(define-assembly-routine (,(symbolicate "ALLOC-SIGNED-BIGNUM-IN-" reg)) `(define-assembly-routine (,(symbolicate "ALLOC-SIGNED-BIGNUM-IN-" reg))
((:temp number unsigned-reg ,(symbolicate reg "-OFFSET"))) ((:temp number unsigned-reg ,(symbolicate reg "-OFFSET")))
(inst push number) (inst push number)
(count-hit ,(symbolicate "ALLOC-SIGNED-BIGNUM-IN-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 1) number nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 1) number nil nil nil)
(popw number bignum-digits-offset other-pointer-lowtag))) (popw number bignum-digits-offset other-pointer-lowtag)))
(unsigned (reg) (unsigned (reg)
@ -42,10 +246,12 @@
(inst push number) (inst push number)
(inst jmp :ns one-word-bignum) (inst jmp :ns one-word-bignum)
;; Two word bignum ;; Two word bignum
(count-hit ,(symbolicate "ALLOC-UNSIGNED-BIGNUM2-IN-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 2) number nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 2) number nil nil nil)
(popw number bignum-digits-offset other-pointer-lowtag) (popw number bignum-digits-offset other-pointer-lowtag)
(inst ret) (inst ret)
ONE-WORD-BIGNUM ONE-WORD-BIGNUM
(count-hit ,(symbolicate "ALLOC-UNSIGNED-BIGNUM1-IN-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 1) number nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 1) number nil nil nil)
(popw number bignum-digits-offset other-pointer-lowtag))) (popw number bignum-digits-offset other-pointer-lowtag)))
(from-digits (reg) (from-digits (reg)
@ -57,11 +263,13 @@
((:temp result unsigned-reg ,(symbolicate reg "-OFFSET"))) ((:temp result unsigned-reg ,(symbolicate reg "-OFFSET")))
(inst test :byte result result) ; is-two-digit flag (inst test :byte result result) ; is-two-digit flag
(inst jmp :z one-word-bignum) (inst jmp :z one-word-bignum)
(count-hit ,(symbolicate "BIGNUM2-TO-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 2) result nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 2) result nil nil nil)
(inst movdqu float0-tn (ea 8 rsp-tn)) (inst movdqu float0-tn (ea 8 rsp-tn))
(inst movdqu (ea (- (ash 1 word-shift) other-pointer-lowtag) result) float0-tn) (inst movdqu (ea (- (ash 1 word-shift) other-pointer-lowtag) result) float0-tn)
(inst ret 16) ; pop args (inst ret 16) ; pop args
ONE-WORD-BIGNUM ONE-WORD-BIGNUM
(count-hit ,(symbolicate "BIGNUM1-TO-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 1) result nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 1) result nil nil nil)
(inst movq float0-tn (ea 8 rsp-tn)) (inst movq float0-tn (ea 8 rsp-tn))
(inst movq (ea (- (ash 1 word-shift) other-pointer-lowtag) result) float0-tn) (inst movq (ea (- (ash 1 word-shift) other-pointer-lowtag) result) float0-tn)
@ -75,6 +283,7 @@
;; rsp : return-pc ;; rsp : return-pc
`(define-assembly-routine (,(symbolicate "+BIGNUM-TO-" reg) (:return-style :none)) `(define-assembly-routine (,(symbolicate "+BIGNUM-TO-" reg) (:return-style :none))
((:temp result unsigned-reg ,(symbolicate reg "-OFFSET"))) ((:temp result unsigned-reg ,(symbolicate reg "-OFFSET")))
(count-hit ,(symbolicate "+BIGNUM-TO-" reg))
(inst test :byte result result) ; is-two-or-three-digit flag (inst test :byte result result) ; is-two-or-three-digit flag
(inst jmp :z one-word-bignum) (inst jmp :z one-word-bignum)
;; Since 2 digits and 3 digits consume the same number of bytes ;; Since 2 digits and 3 digits consume the same number of bytes
@ -105,6 +314,7 @@
(inst set :c number) (inst set :c number)
(inst movzx '(:byte :dword) number number) (inst movzx '(:byte :dword) number number)
(inst push number) (inst push number)
(count-hit ,(symbolicate "TWO-WORD-BIGNUM-TO-" reg))
(alloc-other bignum-widetag (+ bignum-digits-offset 2) number nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 2) number nil nil nil)
(inst pop (ea (- (ash 2 word-shift) other-pointer-lowtag) number)) (inst pop (ea (- (ash 2 word-shift) other-pointer-lowtag) number))
(inst pop (ea (- (ash 1 word-shift) other-pointer-lowtag) number)) (inst pop (ea (- (ash 1 word-shift) other-pointer-lowtag) number))

View file

@ -11,6 +11,20 @@
(in-package "SB-VM") (in-package "SB-VM")
(defparameter *asm-routine-hit-counter-map*
(make-array 200 :fill-pointer 0))
(eval-when (:compile-toplevel)
(defmacro count-hit (name)
(declare (ignorable name))
#+nil
(let ((vector-data
(+ (static-data-collection-vector)
(ash vector-data-offset word-shift)
(- other-pointer-lowtag)))
(index (or (position name *asm-routine-hit-counter-map*)
(vector-push-extend name *asm-routine-hit-counter-map*))))
`(inst inc :qword (ea ,(+ vector-data (ash index word-shift)))))))
#-sb-assembling ; avoid redefinition warning #-sb-assembling ; avoid redefinition warning
(progn (progn
(defun both-fixnum-p (temp x y) (defun both-fixnum-p (temp x y)
@ -55,7 +69,7 @@
(instrument-alloc bignum-widetag nbytes nil alloc-tn) (instrument-alloc bignum-widetag nbytes nil alloc-tn)
(pseudo-atomic () (pseudo-atomic ()
(allocation bignum-widetag nbytes 0 alloc-tn nil nil nil) (allocation bignum-widetag nbytes 0 alloc-tn nil nil nil)
(storew* header alloc-tn 0 0 t) (storew header alloc-tn 0 0)
(storew source alloc-tn bignum-digits-offset 0) (storew source alloc-tn bignum-digits-offset 0)
(if (eq dest alloc-tn) (if (eq dest alloc-tn)
(inst or :byte dest other-pointer-lowtag) (inst or :byte dest other-pointer-lowtag)
@ -100,6 +114,7 @@
(inst rcr res 1) (inst rcr res 1)
(when (> n-fixnum-tag-bits 1) ; don't shift by 0 (when (> n-fixnum-tag-bits 1) ; don't shift by 0
(inst sar res (1- n-fixnum-tag-bits))) (inst sar res (1- n-fixnum-tag-bits)))
(count-hit generic+)
(return-single-word-bignum res rcx res)) (return-single-word-bignum res rcx res))
(define-generic-arith-routine (- 10) (define-generic-arith-routine (- 10)
@ -114,6 +129,7 @@
(inst rcr res 1) (inst rcr res 1)
(when (> n-fixnum-tag-bits 1) ; don't shift by 0 (when (> n-fixnum-tag-bits 1) ; don't shift by 0
(inst sar res (1- n-fixnum-tag-bits))) (inst sar res (1- n-fixnum-tag-bits)))
(count-hit generic-)
(return-single-word-bignum res rcx res)) (return-single-word-bignum res rcx res))
(define-generic-arith-routine (* 30) (define-generic-arith-routine (* 30)
@ -133,12 +149,14 @@
(inst cmp x rcx) (inst cmp x rcx)
(inst jmp :e SINGLE-WORD-BIGNUM) (inst jmp :e SINGLE-WORD-BIGNUM)
(count-hit generic-mul=>2word)
(alloc-other bignum-widetag (+ bignum-digits-offset 2) res nil nil nil) (alloc-other bignum-widetag (+ bignum-digits-offset 2) res nil nil nil)
(storew rax res bignum-digits-offset other-pointer-lowtag) (storew rax res bignum-digits-offset other-pointer-lowtag)
(storew rcx res (1+ bignum-digits-offset) other-pointer-lowtag) (storew rcx res (1+ bignum-digits-offset) other-pointer-lowtag)
(inst clc) (inst ret) (inst clc) (inst ret)
SINGLE-WORD-BIGNUM SINGLE-WORD-BIGNUM
(count-hit generic-mul=>1word)
(return-single-word-bignum res res rax))) (return-single-word-bignum res res rax)))
;;;; negation ;;;; negation

View file

@ -97,27 +97,30 @@
;; Lisp = save GPRs that lisp call can change ;; Lisp = save GPRs that lisp call can change
(aver (member convention '(lisp c))) (aver (member convention '(lisp c)))
(aver (eql card-table-reg 12)) ; change detector (aver (eql card-table-reg 12)) ; change detector
(let ((fpr-align 64) (let* ((except (ensure-list except))
(except (ensure-list except)) (save-fprs (not (member :fprs except)))
(clobberables (stack-align (if save-fprs 64 16))
(remove frame-reg (c-nonvolatile '(rbx r12 r13 r14 r15))
`(rax rbx rcx rdx rsi rdi r8 r9 r10 r11 (clobberables
;; 13 is usable only if not permanently wired to the thread base (remove frame-reg
#+gs-seg r13 `(rax rbx rcx rdx rsi rdi r8 r9 r10 r11
r14 r15))) ;; 13 is usable only if not permanently wired to the thread base
(frame-tn (when frame-reg (symbolicate frame-reg "-TN")))) #+gs-seg r13
r14 r15)))
(frame-tn (when frame-reg (symbolicate frame-reg "-TN"))))
(when (eq convention 'c)
(binding* ((check (intersection c-nonvolatile except) :exit-if-null))
;; specifying registers not to be preseerved that would not be clobbered
;; by C call is indicative of programmer error
(warn "Don't specify nonvolatile regs ~S in registers not to preserve"
check)))
(setq except (delete :fprs except))
(aver (subsetp except clobberables)) ; Catch spelling mistakes (aver (subsetp except clobberables)) ; Catch spelling mistakes
;; Since FPR-SAVE / -RESTORE utilize RAX, returning RAX from an assembly ;; Since FPR-SAVE / -RESTORE utilize RAX, returning RAX from an assembly
;; routine (by *not* preserving it) will be meaningless. ;; routine (by *not* preserving it) does not work,
;; You'd have to modify -SAVE / -RESTORE to avoid clobbering RAX. ;; but you can still ask for it to explicitly not be preserved.
;; This is a bit limiting: if you ask not to preserve RAX, what you mean is exactly that: ;; You'd have to modify -SAVE / -RESTORE to avoid clobbering RAX
;; it does not matter what value is gets. But EXCEPT has a dual purpose of also ;; if that were needed.
;; propagating the value out from BODY. We _should_ allow RAX in the list of things
;; not to save, in case the caller wants to be maximally efficient and specify that RAX
;; can be trashed with impunity. But it helps with incorrect usage for now
;; to raise this error.
(when (member 'rax except)
(error "Excluding RAX from preserved GPRs probably will not do what you want."))
(let* ((gprs ; take SET-DIFFERENCE with EXCEPT but in a predictable order (let* ((gprs ; take SET-DIFFERENCE with EXCEPT but in a predictable order
(remove-if (lambda (x) (member x except)) (remove-if (lambda (x) (member x except))
(ecase convention (ecase convention
@ -126,19 +129,26 @@
;; all GPRs are potentially destroyed across lisp call ;; all GPRs are potentially destroyed across lisp call
(lisp clobberables)))) (lisp clobberables))))
;; each 8 registers pushed preserves 64-byte alignment ;; each 8 registers pushed preserves 64-byte alignment
(alignment-bytes (gpr-pad-bytes
(- (nth-value 1 (ceiling (* n-word-bytes (length gprs)) fpr-align))))) (- (nth-value 1 (ceiling (* n-word-bytes (length gprs))
stack-align)))))
`(progn `(progn
,@(when frame-tn ,@(when frame-tn
`((inst push ,frame-tn) `((inst push ,frame-tn)
(inst mov ,frame-tn rsp-tn))) (inst mov ,frame-tn rsp-tn)))
(inst and rsp-tn ,(- fpr-align)) (inst and rsp-tn ,(- stack-align))
(regs-pushlist ,@gprs) (regs-pushlist ,@gprs)
(inst sub rsp-tn ,(+ alignment-bytes xsave-area-size)) ,@(cond (save-fprs
(call-fpr-save/restore-routine :save) `((inst sub rsp-tn ,(+ gpr-pad-bytes xsave-area-size))
(call-fpr-save/restore-routine :save)))
((plusp gpr-pad-bytes)
`((inst sub rsp-tn ,gpr-pad-bytes))))
(assemble () ,@body) (assemble () ,@body)
(call-fpr-save/restore-routine :restore) ,@(cond (save-fprs
(inst add rsp-tn ,(+ alignment-bytes xsave-area-size)) `((call-fpr-save/restore-routine :restore)
(inst add rsp-tn ,(+ gpr-pad-bytes xsave-area-size))))
((plusp gpr-pad-bytes)
`((inst add rsp-tn ,gpr-pad-bytes))))
(regs-poplist ,@gprs) (regs-poplist ,@gprs)
,@(cond ((eq frame-tn 'rbp) ,@(cond ((eq frame-tn 'rbp)
'((inst leave))) '((inst leave)))

View file

@ -14,7 +14,7 @@
(inst pushf) (inst pushf)
(inst push rbp-tn) (inst push rbp-tn)
(inst mov rbp-tn rsp-tn) (inst mov rbp-tn rsp-tn)
(inst and rsp-tn (- 16)) (inst and rsp-tn -16)
(inst sub rsp-tn 8) ; PUSHing an odd number of GPRs (inst sub rsp-tn 8) ; PUSHing an odd number of GPRs
;; Arrange in the utterly confusing order that a linux signal context has them ;; Arrange in the utterly confusing order that a linux signal context has them
;; so that we can memcpy() into a context. Push RBX twice to maintain alignment. ;; so that we can memcpy() into a context. Push RBX twice to maintain alignment.
@ -31,6 +31,171 @@
(inst leave) (inst leave)
(inst popf)) (inst popf))
;; If reg holds a pointer and does not point to static or readonly space,
;; then invoke the barrier. Should also check not pointing to a stack
;; or arena etc. But this is adequate for now, and avoids the most common
;; effectless barrier of setting a slot to NIL or T.
;; This should use the pointer tag for something
;; (because lists can't be large objects, etc)
;;
(defun test-pointer-interesting (reg &key yep nope)
(aver (not (location= reg rax-tn)))
(assemble ()
;; Either the true or false branch but not both can be :DROPTHRU
(when (eq nope :dropthru) (setq nope DONE))
(when (eq yep :dropthru) (setq yep DONE))
;; First check that reg holds a pointer.
(inst lea rax-tn (ea -3 reg))
;; low 2 bits are 0 if it was a pointer. cf. is_lisp_pointer() in runtime.h
(inst test :byte rax-tn 3)
(inst jmp :nz NOPE) ; if nonzero then it's not a tagged pointer
;; This is super easy if the object is in a small object subheap.
(inst sub rax-tn (static-symbol-value-ea 'bitmap-heap-base))
(inst cmp rax-tn (static-symbol-value-ea 'bitmap-heap-size))
(inst jmp :b YEP)
;; Rule out readonly space
(inst mov rax-tn (ea (make-fixup "read_only_space_free_pointer" :foreign-dataref)))
(inst cmp reg (ea rax-tn))
(inst jmp :ae TEST-STATIC) ; above read-only space, so not in that space
(inst mov rax-tn (ea (make-fixup "READ_ONLY_SPACE_START" :foreign-dataref)))
(inst cmp reg (ea rax-tn))
;; >= space start implies it is in the R/O space, therefore NOT a relevant pointer
(inst jmp :ae NOPE)
TEST-STATIC
;; Rule out static addresses
(inst lea rax-tn (ea (- static-space-start) reg))
;; If "below" after this comparison, that's a "nope", because
;; the pointer sees static space and hence is uninteresting.
(inst cmp rax-tn (- static-space-end static-space-start))
;; Figure out which way is the dropthru, and branch in the opposite case
(if (eq yep done) (inst jmp :b NOPE) (inst jmp :ae YEP))
DONE))
;; TODOs:
;; * remember() could be rewritten into Lisp or Lisp assembly so that we don't
;; have to save/restore all floating-point registers.
;; * Probably want to add routines for:
;; - gc-barrier-store-car and gc-barrier-store-cdr which need no index
;; - gc-barrier-svset which takes the 2nd arg as a vector index
;; - gc-barrier-store-wordindexed which will take a constant 2nd arg
(symbol-macrolet ((c-arg1 rdi-tn) (c-arg2 rsi-tn) (c-arg3 rdx-tn) (c-arg4 rcx-tn))
(macrolet
((define (name c-name)
`(define-assembly-routine (,name (:return-style :none)) ()
#+smlgc-telemetry (inst inc :qword (thread-slot-ea thread-ct-store-barriers-slot))
;(inst ud1 rax-tn (ea rax-tn)) ; capture mcontext for debugging
(inst push rbp-tn) (inst mov rbp-tn rsp-tn) (regs-pushlist rax rcx)
;; Stack:
;; saved-RCX saved-RAX saved-RBP return-pc Object EA val
;; @ -16(rbp) -8(rbp) 0(rbp) 8(rbp) 16(rbp) 24(rbp) 32(rbp)
(inst mov rax-tn (ea 24 rbp-tn)) ; EA
;; Skip the barrier if storing into a dynamic-extent object by testing whether
;; EA is within the control stack.
;; untagged pointer case doesn't need to try to rule out stores to stack
;; since there is no case where we create a dynamic-extent object containing
;; a precise pointer that lacks tag bits.
;; TODO: skip also if storing to static or pseudostatic space
;; (basically the corefile-mapped ranges)
,@(when (eq name 'gc-barrier-store)
'((inst cmp rax-tn rsp-tn) ; can't be a stack address if below RSP
(inst jmp :b continue)
(inst cmp rax-tn (thread-slot-ea thread-control-stack-end-slot))
(inst jmp :ae continue)
;; Storing to a dynamic-extent object. No barrier needed
(inst mov rcx-tn (ea 32 rbp-tn)) ; newval into RCX
(inst mov (ea rax-tn) rcx-tn) ; newval to memory
(regs-poplist rbp rax rcx)
(inst ret 24))) ; remove 3 args
CONTINUE
;; The barrier routines must be pseudo-atomic because testing the GC phase
;; commits us to performing 0, 1, or 2 "remember" operations.
;; Any pending phase change must not be acknowledged by the mutator
;; until after the operations are done.
(pseudo-atomic ()
(inst mov rcx-tn (ea rax-tn)) ; get oldval into RCX
(inst mov rax-tn (ea 32 rbp-tn)) ; load newval
;; Return quickly when (EQ OLD NEW) - consider that sml_cmpswap performs
;; no barrier if its comparison failed.
(inst cmp rcx-tn rax-tn) ; old and new respectively
(inst jmp :e ELIDE)
;; For untagged pointers, assume that the barrier is always needed.
,@(unless (search "-UNTAGGED" (string name))
;; Consider whether 'oldval' needs the deletion barrier
'((test-pointer-interesting rcx-tn :yep BARRIER :nope :dropthru)
;; Comment from C:
;; /* In either SYNC1, PRESYNC2, or SYNC2 phase, snooping
;; * write barrier is required. */
(inst cmp :byte (thread-slot-ea thread-gc-phase-slot) GC-PHASE-MARK)
(inst jmp :e ELIDE)
;; reload newval. It's OK to use RCX because if 'oldval' needed
;; a deletion barrier, then we'd have already jumped to BARRIER.
(inst mov rcx-tn (ea 32 rbp-tn))
(test-pointer-interesting rcx-tn :yep :dropthru :nope ELIDE)
;; pass oldval as 0 when dropping through from here
(zeroize rcx-tn)))
BARRIER
(with-registers-preserved (c :frame-reg nil :except (rax rcx #|:fprs|#))
(inst mov c-arg1 rcx-tn) ; oldval
(inst mov c-arg2 (ea 32 rbp-tn)) ; newval
(inst mov c-arg3 (ea 16 rbp-tn)) ; object
(inst mov c-arg4 (ea 24 rbp-tn)) ; EA - for debugging only
(inst call (make-fixup ,c-name :foreign)))
(inst lea rsp-tn (ea -16 rbp-tn))
ELIDE
(inst mov rax-tn (ea 24 rbp-tn)) ; EA
(inst mov rcx-tn (ea 32 rbp-tn)) ; newval into RCX
(inst mov (ea rax-tn) rcx-tn))
(regs-poplist rbp rax rcx)
(inst ret 24)))) ; remove 3 args
(define gc-barrier-store "lisp_gcbar")
(define gc-barrier-store-untagged "lisp_gcbar_untagged"))
;; compare-and-swap to a stack address is rare, don't try to optimize for it.
;; Object, EA, newval are passed on stack; oldval in RAX.
;; Performing the barrier _before_ doing the compare-exchange is bad for performance
;; as it causes thousands of instructions to execute in between the user code's reading
;; of 'oldval' and using it as the operand.
;; it is necessary for correctness unless we can acquire the collector's spinlock
;; that guards the objects_from_mutators.
;; On the other hand, all threads suffer equally.
(macrolet
((define (name c-name)
`(define-assembly-routine (,name (:return-style :none)) ()
;; Stack:
;; saved-RAX saved-RCX saved-RDX saved-RBP return-pc Object EA val
;; @ -24(rbp) -16(rbp) -8(rbp) 0(rbp) 8(rbp) 16(rbp) 24(rbp) 32(rbp)
(inst push rbp-tn) (inst mov rbp-tn rsp-tn) (regs-pushlist rdx rcx rax)
(inst mov rcx-tn rax-tn) ; RCX gets oldval
(inst mov rdx-tn (ea 32 rbp-tn)) ; RDX gets newval
(pseudo-atomic ()
,@(unless (search "-UNTAGGED" (string name))
;; Consider whether 'oldval' needs the deletion barrier
'((test-pointer-interesting rcx-tn :yep BARRIER :nope :dropthru)
;; If strictly after SYNC2 phase then the insertion barrier is disabled
(inst cmp :byte (thread-slot-ea thread-gc-phase-slot) GC-PHASE-SYNC2)
(inst jmp :a ELIDE)
;; Consider whether 'newval' needs the insertion barrier
(test-pointer-interesting rdx-tn :yep :dropthru :nope ELIDE)))
BARRIER
(with-registers-preserved (c :frame-reg nil :except (rax rcx rdx #|:fprs|#))
(move c-arg1 rcx-tn) ; oldval
(move c-arg2 rdx-tn) ; newval
(inst mov c-arg3 (ea 16 rbp-tn)) ; object
(inst mov c-arg4 (ea 24 rbp-tn)) ; EA
(inst call (make-fixup ,c-name :foreign)))
(inst lea rsp-tn (ea -24 rbp-tn))
ELIDE
(inst pop rax-tn) ; oldval
(inst mov rcx-tn (ea 24 rbp-tn)) ; EA
(inst mov rdx-tn (ea 32 rbp-tn)) ; newval
(inst cmpxchg :lock (ea rcx-tn) rdx-tn)) ; actual oldval goes to RAX
(regs-poplist rbp rdx rcx)
(inst ret 24)))) ; remove 3 args
(define gc-barrier-cmpxchg "lisp_gcbar")
(define gc-barrier-cmpxchg-untagged "lisp_gcbar_untagged"))
(macrolet ((do-fprs (operation regset &aux (displacement 0)) (macrolet ((do-fprs (operation regset &aux (displacement 0))
;; The YMM case could be removed now I suppose, since we use XSAVE + XRSTOR ;; The YMM case could be removed now I suppose, since we use XSAVE + XRSTOR
(multiple-value-bind (mnemonic fpr-align) (multiple-value-bind (mnemonic fpr-align)
@ -83,11 +248,286 @@
(inst xrstor (ea 16 rsp-tn)) (inst xrstor (ea 16 rsp-tn))
(inst pop rdx-tn))) (inst pop rdx-tn)))
(define-assembly-routine (switch-to-arena (:return-style :raw)) () (macrolet
((choose-allocptr ()
'(progn
(inst cmp size smlgc-blocksize-max)
(inst jmp :a LARGE)
(inst lea :dword ap (ea -1 size)) ; size 16 becomes 15 etc
(inst bsr :dword ap ap) ; size 16 -> 3, 32 -> 4, etc
(inst shl :dword ap 5) ; ap *= sizeof (struct alloc_ptr)
(inst lea ap (ea (+ (ash thread-ap4-slot word-shift) (* -3 32))
thread-tn ap))))
(allocate ()
'(let ((ok (gen-label)) (skip (gen-label)) (continue (gen-label)))
(inst mov bmwordptr-reg (freebit.ptr))
(inst mov :dword mask (freebit.mask)) ; MASK has the bit we want next
(inst test :dword (ea bmwordptr-reg) mask) ; can we have it?
(inst jmp :z OK)
;; RDI holds the allocation pointer
(inst call (make-fixup 'bitmap-alloc-fallback :assembly-routine))
(inst jmp continue)
(emit-label OK)
;; advance the bit, w/wraparound
(inst rol :dword mask 1)
(inst mov :dword (freebit.mask) mask) ; writeback
;; possibly increment bitmap word ptr
(inst jmp :nc SKIP)
(inst add bmwordptr-reg 4)
(inst mov (freebit.ptr) bmwordptr-reg)
(emit-label SKIP)
(inst mov rax-tn (segment.blocksize))
(inst xadd (freeptr) rax-tn)
(emit-label CONTINUE)
(assert-word-unused (ea rax-tn)))))
(symbol-macrolet ((size rax-tn)
(bmwordptr-reg rax-tn) ; same physical reg as SIZE
(ap rdi-tn)
(mask rcx-tn)
(xmm-temp float7-tn)) ; declared as a vop temp
;;; For both of these routes: RAX recives SIZE and returns the result
(with-bitmap-ap (ap)
(define-assembly-routine (bitmap-vect-alloc (:return-style :none)) ()
(inst push rbp-tn) (inst mov rbp-tn rsp-tn) (regs-pushlist rcx rdi)
;; Stack:
;; saved-RDI saved-RCX saved-RBP return-pc WIDETAG LENGTH
;;; @ -16(rbp) -8(rbp) 0(rbp) 8(rbp) 16(rbp) 24(rbp)
(choose-allocptr)
(pseudo-atomic ()
(allocate)
;; Store the first 2 words, and OR in the lowtag
(inst movdqu xmm-temp (ea 16 rbp-tn)) ; load 2 lispwords
(inst movdqa (ea rax-tn) xmm-temp)
(inst or :byte rax-tn other-pointer-lowtag))
DONE
(regs-poplist rbp rcx rdi)
(inst ret 16) ; Clean args
LARGE
;; Pretty much just like VAR-ALLOC now
(move rdi-tn rax-tn)
(with-registers-preserved (lisp :except (rax rcx rdi) :frame-reg nil)
(inst lea rsi-tn (ea 16 rbp-tn)) ; 2nd C arg = pointer to header words
(pseudo-atomic ()
(inst call (make-fixup "vect_alloc_large" :foreign)))
(inst mov rcx-tn rax-tn)) ; protect from FPR-restore which clobbers RAX
(inst mov rax-tn rcx-tn) ; Restore the result
(inst lea rsp-tn (ea -16 rbp-tn)) ; Restore RSP to a known value
(inst jmp DONE))
(define-assembly-routine (bitmap-var-alloc (:return-style :none)) ()
(inst push rbp-tn) (inst mov rbp-tn rsp-tn) (regs-pushlist rcx rdi)
;; Stack:
;; saved-RDI saved-RCX saved-RBP return-pc HEADER LOWTAG
;;; @ -16(rbp) -8(rbp) 0(rbp) 8(rbp) 16(rbp) 24(rbp)
(choose-allocptr)
(pseudo-atomic ()
(allocate)
(inst mov rcx-tn (ea 16 rbp-tn)) ; load the header
(inst mov (ea rax-tn) rcx-tn) ; write the header into the new object
(inst or :byte rax-tn (ea 24 rbp-tn))) ; tag the pointer
DONE
(regs-poplist rbp rcx rdi)
(inst ret 16) ; Clean args
LARGE
;; This routine calls C and Lisp, so in theory we need only save C registers
;; because C's funcall would save the ones that Lisp may clobber, in order to
;; preserve C convention. But we in fact need to see all GPRs on the stack
;; in case GC requests a sync2.
;; So save all registers except RAX and the two already saved,
;; but first put RAX (the size) in the 1st C arg register
(move rdi-tn rax-tn)
(with-registers-preserved (lisp :except (rax rcx rdi) :frame-reg nil)
(inst mov rsi-tn (ea 16 rbp-tn)) ; 2nd C arg = header
(pseudo-atomic ()
(inst call (make-fixup "var_alloc_large" :foreign))
;; C call returned an untagged pointer to the large object in RAX.
(inst mov rcx-tn rax-tn) ; protect from FPR-restore which clobbers RAX
(inst or :byte rcx-tn (ea 24 rbp-tn)))) ; tag the pointer
(inst mov rax-tn rcx-tn) ; Restore the result
(inst lea rsp-tn (ea -16 rbp-tn)) ; Restore RSP to a known value
(inst jmp DONE)))))
;;; Take alloc-ptr in RDI, return a free block in RAX. Preserve all other registers.
(macrolet
((alignment-padding ()
;; after pushing the nonvolatile GPRs, subtract this much more from RSP
;; to preserve alignment as needed for FPR save/restore
(* 5 n-word-bytes))
(define-fallback (name c-fallback2)
`(define-assembly-routine (,name) ()
(inst push rbp-tn)
(inst mov rbp-tn rsp-tn)
;; align stack to required boundary in case we're going to save/restore FPRs later
(inst and rsp-tn -64)
;; save the volatile registers, preserving 64-byte stack alignment
(regs-pushlist rcx rdx rsi r8 r9 r10 r11 rdi)
;; 8 registers were pushed, so the stack is correctly aligned for C call
(inst call (make-fixup "smlgc_search_freebit" :foreign))
(inst test rax-tn rax-tn)
(inst jmp :nz success)
(inst mov rdi-tn (ea rsp-tn)) ; reload the allocation-ptr
;; Now even though the callee-saved ("nonvolatile") regs won't be clobbered by
;; the C call, they must be spilled to the stack so that a root scan sees them
;; above the stack pointer.
;; There is no interrupt context to scan if GC decides to run, and in fact
;; if this thread suspends itself waiting for GC, then GC may perform the sync2
;; action (graying of roots) on behalf of the lisp thread, in which case it has
;; no way to obtain the register contents. (And it would need libunwind to
;; get the register context of each frame. Way too complicated)
(regs-pushlist rbx r14 r15) ; 12 and 13 won't point to lisp objects
;; Inform the collector where the roots start.
(inst mov rsi-tn rsp-tn)
;; Create the FPR save area
(inst sub rsp-tn (+ xsave-area-size (alignment-padding)))
(call-fpr-save/restore-routine :save)
(inst call (make-fixup ,c-fallback2 :foreign))
(inst mov rcx-tn rax-tn) ; save a backup of RAX since FPR-restore clobbers it
(call-fpr-save/restore-routine :restore)
(inst add rsp-tn (+ xsave-area-size (alignment-padding))) ; Unallocate FPR save area
(inst mov rax-tn rcx-tn) ; restore RAX
(regs-poplist rbx r14 r15) ; 12 and 13 won't point to lisp objects
SUCCESS
(regs-poplist rcx rdx rsi r8 r9 r10 r11 rdi)
(assert-word-unused (ea rax-tn))
(inst leave))))
(define-fallback bitmap-alloc-fallback "sml_alloc_fallback2")
(define-fallback bitmap-cons-fallback "sml_cons_fallback2"))
;;; This attempts to claim N consecutive cons cells at the current index
;;; based on the number of bytes desired, supplied in RCX.
;;; On success, RAX is the result and ZF is clear.
;;; On failure, RAX is 0 and ZF is set.
(symbol-macrolet ((rax rax-tn) (rcx rcx-tn))
(with-bitmap-ap (:thread thread-ap4-slot)
(define-assembly-routine (bitmap-reserve-&rest) ()
;; Convert RCX back to a count of conses, not a byte count.
(inst shr :dword rcx (1+ word-shift))
;; Always take the slow path if the request exceeds 15 conses. The rationale is
;; that half the time, the next bit to be examined in the current mask is in the
;; upper 16 (assuming uniform distribution), which means that consecutive bits
;; would wrap to the next bitmap word, leaving aside the question of whether
;; those cells are actually available.
(inst cmp :dword rcx 16)
;; If NOT profiling &REST list sizes:
(inst jmp :ae FAIL)
;; If profiling &REST list sizes:
;(inst jmp :b SMALL)
;(inst inc :lock :dword (ea (cons-stats-v))) ; too big
;(inst jmp FAIL)
;SMALL
;(inst inc :lock :dword (ea (cons-stats-v) nil rcx 8))
(inst mov :dword rax (freebit.mask))
(let ((temp rdx-tn))
(inst push temp)
;; Calculate the full mask of bits that we'd like to see available
;; as (LOGXOR (1- (ASH MASK NBITS)) (1- MASK)). This sets all bits below
;; and including the highest desired, and clears below the current mask.
(inst lea :dword temp (ea -1 rax)) ; temp := (1- MASK)
(inst shl rax :cl) ; = (ASH MASK NBITS), won't overflow a :QWORD
(inst dec rax) ; = (1- (ASH MASK NBITS))
(inst xor rax temp)
;; See if all the bits in RAX are clear in the allocator bitmap
(inst mov temp (freebit.ptr))
(inst test :dword (ea temp) rax)
(inst pop temp)) ; done using TEMP
(inst jmp :nz FAIL)
;; Verify that none of the upper 32 bits of RAX are 1. If any are,
;; then the :DWORD-sized TEST just performed was not valid.
(inst shr rax 32)
(inst jmp :nz FAIL) ; oops, RAX had some upper bit on
;; success
(inst rol :dword (freebit.mask) :cl) ; update the mask
(inst sbb :dword rax rax)
(inst and :dword rax 4) ; = 0 or 4 depending on CF prior to SBB
(inst add :qword (freebit.ptr) rax)
(inst shl :dword rcx (1+ word-shift)) ; convert to byte count once again
(inst mov :dword rax rcx)
;; RAX gets the free pointer, and free pointer is bumped by N bytes
(inst xadd (freeptr) rax) ; ZF will be cleared
(inst ret)
FAIL
;; When exiting with failure, RCX is a count of elements, not bytes.
;; That's exactly what the fallback handler wants.
(zeroize rax)))) ; sets ZF
;;; These are the fallback routines for a single cons at a time.
;;; Registers are unaffected except for the destination.
(macrolet ((define-cons-fallbacks (&rest regs)
`(progn
,@(mapcar
(lambda (reg)
`(define-assembly-routine (,(symbolicate "BITMAP-CONS-TO-" reg "-FALLBACK")) ()
(count-alloc :cons1-slow)
,@(unless (eq reg 'rdi) '((inst push rdi-tn)))
,@(unless (eq reg 'rax) '((inst push rax-tn)))
(inst lea rdi-tn (thread-slot-ea thread-ap4-slot))
(inst call (make-fixup 'bitmap-alloc-fallback :assembly-routine))
,@(unless (eq reg 'rax)
`((inst mov ,(symbolicate reg "-TN") rax-tn)
(inst pop rax-tn)))
,@(unless (eq reg 'rdi) '((inst pop rdi-tn)))))
regs))))
(define-cons-fallbacks rax rcx rdx rbx rsi rdi r8 r9 r10 r11 r12 r13 r14 r15))
;;; For all these, the args were pushed left-to-right.
;;; Compute the address of the highest arg, load the byte count,
;;; call BITMAP-LISTIFY, then clean the stack.
(macrolet ((call (n routine)
`(progn
(inst push rcx-tn)
(inst mov rcx-tn ,n) ; how many conses to make (NOT how many args)
(inst call (make-fixup ',routine :assembly-routine))
(inst pop rcx-tn))))
(define-assembly-routine (bitmap-list2-fallback (:return-style :none)) () ; 2 conses
(count-alloc :cons2-slow)
(inst lea rax-tn (ea 16 rsp-tn))
(call 2 bitmap-listify)
(inst ret (* 2 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list3-fallback (:return-style :none)) () ; 3 conses
(count-alloc :cons3-slow)
(inst lea rax-tn (ea 24 rsp-tn))
(call 3 bitmap-listify)
(inst ret (* 3 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list4-fallback (:return-style :none)) () ; 4 conses
(count-alloc :cons4-slow)
(inst lea rax-tn (ea 32 rsp-tn))
(call 4 bitmap-listify)
(inst ret (* 4 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list5-fallback (:return-style :none)) () ; 5 conses
(count-alloc :cons5-slow)
(inst lea rax-tn (ea 40 rsp-tn))
(call 5 bitmap-listify)
(inst ret (* 5 n-word-bytes)))
(define-assembly-routine (bitmap-list*3-fallback (:return-style :none)) () ; 2 conses
(count-alloc :cons2-slow)
(inst lea rax-tn (ea 24 rsp-tn))
(call 2 bitmap-listify*)
(inst ret (* 3 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list*4-fallback (:return-style :none)) () ; 3 conses
(count-alloc :cons3-slow)
(inst lea rax-tn (ea 32 rsp-tn))
(call 3 bitmap-listify*)
(inst ret (* 4 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list*5-fallback (:return-style :none)) () ; 4 conses
(count-alloc :cons4-slow)
(inst lea rax-tn (ea 40 rsp-tn))
(call 4 bitmap-listify*)
(inst ret (* 5 n-word-bytes))) ; pop args
(define-assembly-routine (bitmap-list*6-fallback (:return-style :none)) () ; 5 conses
(count-alloc :cons5-slow)
(inst lea rax-tn (ea 48 rsp-tn))
(call 5 bitmap-listify*)
(inst ret (* 6 n-word-bytes))) ; pop args
) ; end MACROLET
;;;; End of bitmap allocator trampolines
(define-assembly-routine (switch-to-arena) ()
(inst mov rsi-tn (ea rsp-tn)) ; explicitly pass the return PC (inst mov rsi-tn (ea rsp-tn)) ; explicitly pass the return PC
;; RSI and RDI are vop temps, so don't bother preserving them ;; RSI and RDI are vop temps, so don't bother preserving them
(with-registers-preserved (c :except (rsi rdi)) (with-registers-preserved (c :except (rsi rdi))
(pseudo-atomic () (pseudo-atomic (:sml-check nil)
#-system-tlabs (inst break halt-trap) #-system-tlabs (inst break halt-trap)
#+system-tlabs (call-c (make-fixup "switch_to_arena" :foreign) #+win32 rdi-tn #+win32 rsi-tn)))) #+system-tlabs (call-c (make-fixup "switch_to_arena" :foreign) #+win32 rdi-tn #+win32 rsi-tn))))
@ -141,6 +581,11 @@
(call-c (make-fixup "alloc_funinstance" :foreign) (call-c (make-fixup "alloc_funinstance" :foreign)
(ea 16 rbp-tn)) (ea 16 rbp-tn))
(inst mov (ea 16 rbp-tn) rax-tn))) (inst mov (ea 16 rbp-tn) rax-tn)))
(define-assembly-routine (set-funinstance-ref) ()
(with-registers-preserved (c)
(pseudo-atomic ()
(call-c (make-fixup "set_funinstance_slot" :foreign)
(ea 16 rbp-tn) (ea 24 rbp-tn) (ea 32 rbp-tn)))))
;;; These routines are for the deterministic consing profiler. ;;; These routines are for the deterministic consing profiler.
;;; The C support routine's argument is the return PC. ;;; The C support routine's argument is the return PC.
@ -236,7 +681,7 @@
(loadw rax-tn rax-tn funcallable-instance-function-slot fun-pointer-lowtag) (loadw rax-tn rax-tn funcallable-instance-function-slot fun-pointer-lowtag)
(inst jmp (object-slot-ea rax-tn closure-fun-slot fun-pointer-lowtag))) (inst jmp (object-slot-ea rax-tn closure-fun-slot fun-pointer-lowtag)))
(define-assembly-routine (ensure-symbol-hash (:return-style :raw)) () (define-assembly-routine (ensure-symbol-hash) ()
(with-registers-preserved (lisp) (with-registers-preserved (lisp)
(inst mov rdx-tn (ea 16 rbp-tn)) ; arg (inst mov rdx-tn (ea 16 rbp-tn)) ; arg
(call-static-fun 'ensure-symbol-hash 1) (call-static-fun 'ensure-symbol-hash 1)
@ -254,16 +699,27 @@
;;; This is especially important for immobile space where ;;; This is especially important for immobile space where
;;; it is likely that new code will be co-located on a page ;;; it is likely that new code will be co-located on a page
;;; with old code due to the non-moving allocator. ;;; with old code due to the non-moving allocator.
;; stack: ret-pc, object, index, value-to-store
(symbol-macrolet ((object (ea 8 rsp-tn))
(word-index (ea 16 rsp-tn))
(newval (ea 24 rsp-tn))
;; these are declared as vop temporaries
(rax rax-tn)
(rdx rdx-tn)
(rdi rdi-tn))
(define-assembly-routine (code-header-set (:return-style :none)) () (define-assembly-routine (code-header-set (:return-style :none)) ()
;; stack: ret-pc, object, index, value-to-store (inst cmp :dword (thread-slot-ea (1+ thread-ap4-slot)) -1)
(symbol-macrolet ((object (ea 8 rsp-tn)) (inst jmp :e GENCGC-ALLOC)
(word-index (ea 16 rsp-tn)) ;; Just tail-call GC-BARRIER-STORE after changing the 2nd arg to an EA.
(newval (ea 24 rsp-tn)) ;; (Don't bother trying to optimize out the call)
;; these are declared as vop temporaries (inst mov rdi object)
(rax rax-tn) (inst mov rdx word-index)
(rdx rdx-tn) (inst lea rdx (ea (- other-pointer-lowtag) rdi rdx n-word-bytes))
(rdi rdi-tn)) (inst mov word-index rdx)
(pseudo-atomic () (inst jmp (make-fixup 'gc-barrier-store :assembly-routine))
GENCGC-ALLOC
(pseudo-atomic (:sml-check nil)
#+immobile-space #+immobile-space
(progn (progn
#-sb-thread #-sb-thread
@ -292,5 +748,56 @@
;; set 'written' flag in the code header ;; set 'written' flag in the code header
(inst or :byte :lock (ea (- 3 other-pointer-lowtag) rdi) #x40) (inst or :byte :lock (ea (- 3 other-pointer-lowtag) rdi) #x40)
;; store newval into object ;; store newval into object
(inst mov (ea (- other-pointer-lowtag) rdi rdx n-word-bytes) rax))) (inst mov (ea (- other-pointer-lowtag) rdi rdx n-word-bytes) rax))
(inst ret 24)) ; remove 3 stack args (inst ret 24))) ; remove 3 stack args
(define-assembly-routine (set-fdefn-fun (:return-style :none)) ()
;; Stack:
;; saved-RBP return-pc Fdefn Fun RawFun
;;; @ 0(rbp) 8(rbp) 16(rbp) 24(rbp) 32(rbp)
(with-registers-preserved (c)
(inst mov c-arg1 (ea 16 rbp-tn))
(inst mov c-arg2 (ea 24 rbp-tn))
(inst mov c-arg3 (ea 32 rbp-tn))
(pseudo-atomic (:sml-check nil)
(inst call (make-fixup "set_fdefn_fun" :foreign))))
(inst ret 24))
(define-assembly-routine (weak-pointer-ref (:return-style :none)) ()
;; weak pointer is on the stack
(with-registers-preserved (c)
(inst mov rax-tn (ea 16 rbp-tn))
(inst lea c-arg1 (object-slot-ea rax-tn weak-pointer-value-slot other-pointer-lowtag))
(inst call (make-fixup "weak_pointer_ref" :foreign)))
(inst ret 8))
(define-assembly-routine (weak-vector-ref (:return-style :none)) ()
;; weak vector was pushed first, then the index
(with-registers-preserved (c)
(inst mov rax-tn (ea 24 rbp-tn))
(inst mov rcx-tn (ea 16 rbp-tn))
(inst lea c-arg1 (ea (- (ash vector-data-offset word-shift) other-pointer-lowtag)
rax-tn rcx-tn (ash 1 (- word-shift n-fixnum-tag-bits))))
(inst call (make-fixup "weak_vector_ref" :foreign)))
(inst ret 16))
(define-assembly-routine (weak-vector-set (:return-style :none)) ()
;; stack: RA Obj EA Val
(with-registers-preserved (c)
(inst mov c-arg1 (ea 24 rbp-tn))
(inst mov c-arg2 (ea 32 rbp-tn))
;; For the non-weak store barrier we don't use the PSEUDO-ATOMIC macro,
;; but instead the instruction is "implicitly pseudo-atomic".
;; The weak barrier is not implicitly pseudo-atomic.
(pseudo-atomic () (inst call (make-fixup "weak_vector_set" :foreign))))
(inst ret 24))
(define-assembly-routine (gc-check) ()
(with-registers-preserved (lisp)
(inst mov c-arg1 rsp-tn)
(inst call (make-fixup "lisp_gc_check" :foreign))))
) ; end PROGN
(define-assembly-routine (release-malloc-segments) ()
(with-registers-preserved (lisp)
(call-static-fun 'release-malloc-segments 0)))

View file

@ -166,14 +166,14 @@
(values code-component (integer 0))) (values code-component (integer 0)))
allocate-code-object)) allocate-code-object))
(defun update-dynamic-space-code-tree (obj) (defun update-dynamic-space-code-tree (obj &optional generation-test)
(with-pinned-objects (obj) (with-pinned-objects (obj)
(let ((addr (logandc2 (get-lisp-obj-address obj) other-pointer-lowtag)) (let ((addr (logandc2 (get-lisp-obj-address obj) other-pointer-lowtag))
(tree *dynspace-codeblob-tree*)) (tree *dynspace-codeblob-tree*))
(loop (let ((newtree (sb-brothertree:insert addr tree))) (loop (let ((newtree (sb-brothertree:insert addr tree)))
;; check that it hasn't been promoted from gen0 -> gen1 already ;; check that it hasn't been promoted from gen0 -> gen1 already
;; (very unlikely, but certainly possible). ;; (very unlikely, but certainly possible).
(unless (eq (generation-of obj) 0) (return)) (when (and generation-test (not (eql (generation-of obj) 0))) (return))
(let ((oldval (cas *dynspace-codeblob-tree* tree newtree))) (let ((oldval (cas *dynspace-codeblob-tree* tree newtree)))
(if (eq oldval tree) (return) (setq tree oldval)))))))) (if (eq oldval tree) (return) (setq tree oldval))))))))
@ -225,6 +225,16 @@
(alien-funcall (extern-alien "alloc_code_object" (alien-funcall (extern-alien "alloc_code_object"
(function unsigned (unsigned 32) (unsigned 32))) (function unsigned (unsigned 32) (unsigned 32)))
total-words boxed))))) total-words boxed)))))
;; Small objects are always findable given an interior pointer,
;; but large objects require the tree.
(when (and (neq (heap-allocated-p code) :dynamic)
(> total-words (/ smlgc-blocksize-max n-word-bytes)))
(let ((tree *immobile-codeblob-tree*)
(addr (logandc2 (get-lisp-obj-address code) lowtag-mask)))
(loop (let ((newtree (sb-brothertree:insert addr tree)))
(when (eq tree (setf tree (cas *immobile-codeblob-tree* tree newtree)))
(return)))))
(return-from allocate-code-object (values code total-words)))
(update-dynamic-space-code-tree code) (update-dynamic-space-code-tree code)
;; FIXME: there may be random values in the unboxed payload and it's not obvious ;; FIXME: there may be random values in the unboxed payload and it's not obvious
;; that all callers of ALLOCATE-CODE-OBJECT always write all raw bytes. ;; that all callers of ALLOCATE-CODE-OBJECT always write all raw bytes.
@ -265,7 +275,7 @@
;; memory block at that address. Consider if we removed from the pool ;; memory block at that address. Consider if we removed from the pool
;; without removing from the tree, the block could be coalesced on either side ;; without removing from the tree, the block could be coalesced on either side
;; and there would not necessarily be a block where the tree says it is. ;; and there would not necessarily be a block where the tree says it is.
(let ((tree sb-vm::*immobile-codeblob-tree*)) (let ((tree *immobile-codeblob-tree*))
(loop (when (eq tree (setq tree (cas *immobile-codeblob-tree* tree (loop (when (eq tree (setq tree (cas *immobile-codeblob-tree* tree
(sb-brothertree:delete addr tree)))) (sb-brothertree:delete addr tree))))
(return)))) (return))))
@ -278,3 +288,197 @@
(alien-funcall tlsf-unalloc-codeblob tlsf-control addr) (alien-funcall tlsf-unalloc-codeblob tlsf-control addr)
(setf (car scratchpad) (pop-1))))) (setf (car scratchpad) (pop-1)))))
t)) t))
(define-alien-variable msegs-pending-free unsigned)
#|
(defglobal *large-object-hashset* nil)
(defun largeobj-hs-insert (node object)
;; NODE is an instance, OBJECT is anything.
(let ((addr (%make-lisp-obj (logandc2 (get-lisp-obj-address object) lowtag-mask))))
(sb-lockless:%so-eq-set-phase1-insert *large-object-hashset* node addr)))
(defun largeobj-hs-maybe-rehash (node)
(sb-lockless:%so-eq-set-phase2-insert *large-object-hashset* node))
|#
;; GC-freed malloc-segments are chained through word 0 which happens to correspond
;; to the 'as_list' slot in the C structure definition. The actual deallocation
;; is deferred to Lisp so that we can remove from the lookup tables.
(defun release-malloc-segments (&aux (count 0))
(flet ((segment-pop ()
(named-let retry ((mseg msegs-pending-free))
(if (zerop mseg)
0
(let* ((next (sap-ref-word (int-sap mseg) 0))
(actual (cas msegs-pending-free mseg next)))
(if (= actual mseg)
(%make-lisp-obj mseg)
(retry actual)))))))
(loop
(let ((mseg (truly-the fixnum (segment-pop))))
(when (zerop mseg)
#+nil
(let ((s "Released %d large code blocks"))
(alien-funcall (extern-alien "tprintf" (function void int system-area-pointer int))
1 (vector-sap s) count))
(return))
(let* ((mseg-sap (descriptor-sap mseg))
(object-sap (sap+ mseg-sap smlgc-mseg-overhead-bytes))
(addr (truly-the (unsigned-byte 56) (sap-int object-sap))))
;; the header was copied to wordindex 1 and then zeroized
(aver (= (widetag@baseptr (sap+ object-sap n-word-bytes))
code-header-widetag))
(let ((tree *immobile-codeblob-tree*))
(loop (when (eq tree (setq tree (cas *immobile-codeblob-tree* tree
(sb-brothertree:delete addr tree))))
(return))))
(sb-thread:barrier (:write))
(with-alien ((delete-code-mseg (function void system-area-pointer) :extern))
(alien-funcall delete-code-mseg mseg-sap))
(incf count))))))
;; Display but do not pop them off the list
(defun show-releasable-malloc-segments ()
(let ((seg msegs-pending-free))
(loop (when (zerop seg) (return))
;; show 4 words of the metadata, then the lispobj header word (which should be 0)
;; and the word after that (which gets the header word before clobbering)
(let ((sap (int-sap seg)))
(format t "@ ~X:~{ ~16X~}~%"
seg (loop for i below 6 collect (sap-ref-word sap (ash i word-shift))))
(setq seg (sap-ref-word sap 0))))))
#+nil
(defun call-with-sml#gc (which thunk)
(if (listp which) (apply #'use-smlgc which) (use-smlgc which))
(prog1 (funcall thunk)
(setq *use-smlgc* 0)))
#+nil
(defmacro with-sml#-allocator (&body body)
`(progn (setq *use-smlgc* -1)
(alien-funcall (extern-alien "enable_cms_cons" (function void)))
(multiple-value-prog1 (progn ,@body)
(alien-funcall (extern-alien "disable_cms_cons" (function void)))
(setq *use-smlgc* 0))))
#+nil(defun sml#-cons (a b) (with-sml#-allocator (cons a b)))
#+nil
(defun force-barrier-on ()
(let ((phaseptr (find-dynamic-foreign-symbol-address "fake_phaseptr")))
(let* ((vmthread (current-thread-offset-sap thread-this-slot)))
(setf (sap-ref-word vmthread (ash thread-gc-phaseptr-slot word-shift))
phaseptr))))
#+nil
(defun call-with-barrier-forced-on (thunk)
(let ((phaseptr (find-dynamic-foreign-symbol-address "fake_phaseptr")))
(let* ((vmthread (current-thread-offset-sap thread-this-slot))
(actual-phaseptr (sap-ref-word vmthread (ash thread-gc-phaseptr-slot word-shift))))
(setf (sap-ref-word vmthread (ash thread-gc-phaseptr-slot word-shift))
phaseptr)
(multiple-value-prog1
(funcall thunk)
(setf (sap-ref-word vmthread (ash thread-gc-phaseptr-slot word-shift))
actual-phaseptr)))))
(defun showlarge ()
; (sb-lockless::show-address-based-list sb-vm::*large-object-hashset*)
)
#+nil
(defun careful-obj-to-malloc-segment (addr)
(alien-funcall (extern-alien "careful_obj_to_malloc_segment"
(function system-area-pointer unsigned))
addr))
#+nil
(defun assert-all-large-simple-funs-findable ()
(let ((list (sb-brothertree::codeblob-tree-to-list sb-vm::*immobile-codeblob-tree*)))
(dolist (c list)
(let* ((code-base-addr (logandc2 (get-lisp-obj-address c) lowtag-mask))
(mseg (int-sap (- code-base-addr smlgc-mseg-overhead-bytes))))
(flet ((test (obj &aux (addr (logandc2 (get-lisp-obj-address obj)
lowtag-mask)))
;; every lowtag wouldn't be needed but it's an OK thing to test
(dotimes (lowtag 16)
(assert (sap= (careful-obj-to-malloc-segment (logior addr lowtag))
mseg)))))
(test c)
(dotimes (index (code-n-entries c))
(test (%code-entry-point c index))))))))
(defun show-bitmap-aps ()
(loop for log2size from 4 to 12
for slot from thread-ap4-slot by 4
do
(format t "~2d ~16x ~8x ~16x ~4x~%"
log2size
(sap-int (current-thread-offset-sap slot))
(sap-int (current-thread-offset-sap (+ 1 slot)))
(sap-int (current-thread-offset-sap (+ 2 slot)))
(sap-int (current-thread-offset-sap (+ 3 slot))))))
(defvar *stats*)
(defun get-consing-stats ()
(setq *stats* (sb-kernel:make-lisp-obj (sb-vm::static-data-collection-vector)))
;; element 0 : number of &REST lists of length 16 or more
;; elt 1..15 : packed integer
;; hi = number of &REST lists of length N using slow path
;; lo = number of &REST lists of length N
;; elt 16..25 : number of lists composed of N cons cells
(values (loop for i from 16 by 2 repeat 6
collect (let* ((total (aref *stats* i))
(slow (aref *stats* (1+ i)))
(fast (- total slow)))
(cons total fast)))
(append (loop for i from 1 to 15
collect
(let* ((element (aref *stats* i))
(slow (ldb (byte 32 32) element))
(total (ldb (byte 32 0) element))
(fast (- total slow)))
(cons total fast)))
(list (cons (aref *stats* 0) 0)))))
(defun print-consing-stats ()
(multiple-value-bind (lists rest-lists) (get-consing-stats)
(format t "~&List consing:~%")
(let ((sum (reduce #'+ (mapcar 'car lists))))
(loop for (count . n-fast) in lists
for label across #("1" "2" "3" "4" "5" ">")
do (format t " ~A | ~12d ~6,2,2f% ~6,2,2f%~%"
label
count
(/ count sum) ; percentage of total
(cond ((string= label ">") 0)
((plusp count) (/ n-fast count))))))
(format t "~&&REST consing:~%")
(let ((sum (reduce #'+ (mapcar 'car rest-lists))))
(loop for (count . n-fast) in rest-lists
for label across #(" 1" " 2" " 3" " 4" " 5" " 6" " 7" " 8" " 9"
"10" "11" "12" "13" "14" "15" " >")
when (plusp count)
do (format t "~A | ~12d ~6,2,2f% ~6,2,2f%~%"
label
count
(/ count sum) ; percentage of total
(cond ((string= label " >") 0)
((plusp count) (/ n-fast count))))))))
(defun sayhello ()
(let ((s #.(format nil "Hey there~%")))
(sb-sys:with-pinned-objects (s)
(alien-funcall (extern-alien "printf" (function void system-area-pointer))
(sb-sys:vector-sap s)))))
(defun hellothread ()
(sb-thread:make-thread #'sayhello))
#|
(defun allocate-smlgc-cons ()
(let ((sap (int-sap (SB-THREAD::THREAD-PRIMITIVE-THREAD sb-thread:*current-thread*))))
(setf (sap-ref-32 sap (ash (1+ thread-ap4-slot) word-shift))
|#

View file

@ -347,4 +347,45 @@
(t node)))) (t node))))
(unary-node (recurse (child node) best)) (unary-node (recurse (child node) best))
(t best)))) (t best))))
(eval-when (:compile-toplevel :load-toplevel :execute)
(export '(tree-count codeblob-tree-to-list)))
(defun codeblob-tree-to-list (tree)
(let (result)
(named-let visit ((node tree))
(typecase node
(unary-node
(visit (child node)))
(binary-node
(multiple-value-bind (left key right) (binary-node-parts node)
(visit right)
(push (sb-kernel:%make-lisp-obj (logior key sb-vm:other-pointer-lowtag))
result)
(visit left)))))
result))
(defun print-codeblob-tree (tree &aux (*print-pretty* nil))
(named-let visit ((node tree))
(typecase node
(unary-node
(visit (child node)))
(binary-node
(multiple-value-bind (left key right) (binary-node-parts node)
(visit left)
(if (= (sb-sys:sap-ref-8 (sb-sys:int-sap key) 0) sb-vm:code-header-widetag)
(let ((obj (sb-kernel:%make-lisp-obj (logior key sb-vm:other-pointer-lowtag))))
(format t "~10x ~6x ~a~%" key (primitive-object-size obj) obj))
(format t "~10x dead~%" key))
(visit right))))))
(defun tree-count (tree)
(named-let recurse ((node tree))
(typecase node
(binary-node
(multiple-value-bind (left key right) (binary-node-parts node)
(declare (ignore key))
(+ 1 (recurse left) (recurse right))))
(unary-node (recurse (child node)))
(t 0))))
) ; end PROGN ) ; end PROGN

View file

@ -221,6 +221,7 @@
(setq sb-pcl::*!docstrings* nil) ; needed before any documentation is set (setq sb-pcl::*!docstrings* nil) ; needed before any documentation is set
(setq sb-c::*queued-proclaims* nil) ; needed before any proclaims are run (setq sb-c::*queued-proclaims* nil) ; needed before any proclaims are run
(alien-funcall (extern-alien "enable_collector_thread" (function void)))
(/show0 "calling cold toplevel forms and fixups") (/show0 "calling cold toplevel forms and fixups")
(let ((*package* *package*)) ; rebind to self, as if by LOAD (let ((*package* *package*)) ; rebind to self, as if by LOAD
(dolist (toplevel-thing *!cold-toplevels*) (dolist (toplevel-thing *!cold-toplevels*)
@ -401,6 +402,7 @@ process to continue normally."
;;;; initialization functions ;;;; initialization functions
(defconstant malloc-object-offset 32)
(defun reinit (total) (defun reinit (total)
;; WITHOUT-GCING implies WITHOUT-INTERRUPTS. ;; WITHOUT-GCING implies WITHOUT-INTERRUPTS.
(without-gcing (without-gcing
@ -409,10 +411,26 @@ process to continue normally."
(when total ; newly started process, and not a failed save attempt (when total ; newly started process, and not a failed save attempt
(sb-thread::init-main-thread) (sb-thread::init-main-thread)
#+x86-64 (sb-vm::validate-asm-routine-vector) #+x86-64 (sb-vm::validate-asm-routine-vector)
(do ((mseg (alien-funcall (extern-alien "get_allocated_msegs"
(function system-area-pointer)))
(sap-ref-sap mseg 0)))
((= (sap-int mseg) 0))
(let* ((addr (sap+ mseg malloc-object-offset))
(widetag (logand (sap-ref-word addr 0) sb-vm:widetag-mask)))
(when (= widetag sb-vm:code-header-widetag)
(setf sb-vm::*immobile-codeblob-tree*
(sb-brothertree:insert (sap-int addr) sb-vm::*immobile-codeblob-tree*))
#+nil
(alien-funcall (extern-alien "printf" (function void system-area-pointer unsigned unsigned unsigned))
(vector-sap #.(format nil "initial mseg @ %p: %lx %lx~%"))
(sap-int addr)
(sap-ref-word addr 0)
(sap-ref-word addr 8)))))
(rebuild-package-vector)) (rebuild-package-vector))
;; Initialize streams next, so that any errors can be printed ;; Initialize streams next, so that any errors can be printed
(stream-reinit t) (stream-reinit t)
(rebuild-pathname-cache) (alien-funcall (extern-alien "enable_collector_thread" (function void)))
; (rebuild-pathname-cache)
(os-cold-init-or-reinit) (os-cold-init-or-reinit)
#-(and win32 (not sb-thread)) #-(and win32 (not sb-thread))
(signal-cold-init-or-reinit) (signal-cold-init-or-reinit)
@ -428,7 +446,8 @@ process to continue normally."
(sb-debug::disable-debugger)) (sb-debug::disable-debugger))
(call-hooks "initialization" *init-hooks*) (call-hooks "initialization" *init-hooks*)
#+sb-thread (finalizer-thread-start) #+sb-thread (finalizer-thread-start)
(sb-vm::!setup-cpu-specific-routines)) ;(sb-vm::!setup-cpu-specific-routines)
)
;;;; some support for any hapless wretches who end up debugging cold ;;;; some support for any hapless wretches who end up debugging cold
;;;; init code ;;;; init code
@ -484,3 +503,9 @@ process to continue normally."
#+sb-show () #+sb-show ()
#-sb-show (/noshow /noshow0 /show /show0)) #-sb-show (/noshow /noshow0 /show /show0))
*!removable-symbols*) *!removable-symbols*)
(defun test-cons-mkay () (cons 1 2))
(defun test-list-mkay () (list 1 2))
(defun test-2-vector-mkay () (vector 1 2))
(defun test-var-vector-mkay (n)
(make-array (the integer n)))

View file

@ -214,6 +214,7 @@
#+unwind-to-frame-and-call-vop bsp-save #+unwind-to-frame-and-call-vop bsp-save
#-fp-and-pc-standard-save lra-saved-pc #-fp-and-pc-standard-save lra-saved-pc
#-fp-and-pc-standard-save cfp-saved-pc) #-fp-and-pc-standard-save cfp-saved-pc)
(declare (fixnum offset elsewhere-pc form-number))
(dx-let ((bytes (make-array (* 8 4) :fill-pointer 0 (dx-let ((bytes (make-array (* 8 4) :fill-pointer 0
:element-type '(unsigned-byte 8)))) :element-type '(unsigned-byte 8))))
;; OFFSET and ELSEWHERE are encoded first so that the C backtrace logic ;; OFFSET and ELSEWHERE are encoded first so that the C backtrace logic
@ -480,7 +481,7 @@
(s stream :type t :identity t)))) (s stream :type t :identity t))))
(:copier nil)) (:copier nil))
;; the IRT that compilation started at ;; the IRT that compilation started at
(start-real-time (get-internal-real-time) :type unsigned-byte :read-only t) (start-real-time 42 #|(get-internal-real-time)|# :type unsigned-byte :read-only t)
;; the FILE-INFO structure for this compilation ;; the FILE-INFO structure for this compilation
(file-info nil :type (or file-info null) :read-only t) (file-info nil :type (or file-info null) :read-only t)
;; the stream that we are using to read the FILE-INFO, or NIL if ;; the stream that we are using to read the FILE-INFO, or NIL if

View file

@ -174,7 +174,7 @@ Other commands:
"Default number of frames to backtrace. Defaults to 1000.") "Default number of frames to backtrace. Defaults to 1000.")
(declaim (boolean *backtrace-print-pc*)) (declaim (boolean *backtrace-print-pc*))
(defvar *backtrace-print-pc* nil) (defvar *backtrace-print-pc* t)
(declaim (unsigned-byte *default-argument-limit*)) (declaim (unsigned-byte *default-argument-limit*))
(defvar *default-argument-limit* call-arguments-limit) (defvar *default-argument-limit* call-arguments-limit)

View file

@ -237,7 +237,7 @@ Examples:
;; Rule out immediate, stack, arena, readonly, and static objects. ;; Rule out immediate, stack, arena, readonly, and static objects.
;; (Is it really an error for a readonly? Maybe a warning? I'll leave it this way unless ;; (Is it really an error for a readonly? Maybe a warning? I'll leave it this way unless
;; users complain. Surely DX and arena are errors, and NIL was always an error.) ;; users complain. Surely DX and arena are errors, and NIL was always an error.)
(unless (member space '(:dynamic :immobile)) (unless (member space '(:dynamic :immobile :bitmapped :large-object))
(if (eq space :static) (if (eq space :static)
(error "Cannot finalize ~S." object) (error "Cannot finalize ~S." object)
;; silently discard finalizers on file streams in arenas I guess ;; silently discard finalizers on file streams in arenas I guess
@ -457,6 +457,7 @@ Examples:
(setf *finalizer-thread* sb-thread:*current-thread*) (setf *finalizer-thread* sb-thread:*current-thread*)
(loop (run-pending-finalizers) (loop (run-pending-finalizers)
(alien-funcall (extern-alien "finalizer_thread_wait" (function void))) (alien-funcall (extern-alien "finalizer_thread_wait" (function void)))
(sb-vm::release-malloc-segments)
(when (zerop finalizer-thread-runflag) (return))) (when (zerop finalizer-thread-runflag) (return)))
(setq *finalizer-thread* nil)) (setq *finalizer-thread* nil))
nil nil))) nil nil)))
@ -467,6 +468,10 @@ Examples:
;;; You should almost always invoke this with *MAKE-THREAD-LOCK* held. ;;; You should almost always invoke this with *MAKE-THREAD-LOCK* held.
;;; Some tests violate that, but they know what they're doing. ;;; Some tests violate that, but they know what they're doing.
(defun finalizer-thread-stop () (defun finalizer-thread-stop ()
(let ((phase (sb-sys:sap-int (sb-vm::current-thread-offset-sap sb-vm::thread-gc-phase-slot))))
(when (> phase 1)
;; TODO: figure this out
(error "Can not stop finalizer with GC in progress")))
#+(and unix sb-safepoint) #+(and unix sb-safepoint)
(let ((thread sb-unix::*sighandler-thread*)) (let ((thread sb-unix::*sighandler-thread*))
(aver (sb-thread::thread-p thread)) (aver (sb-thread::thread-p thread))

View file

@ -299,6 +299,8 @@ trigger a collection of one or more older generations as well. If FULL
is true, all generations are collected. If GEN is provided, it can be is true, all generations are collected. If GEN is provided, it can be
used to specify the oldest generation guaranteed to be collected." used to specify the oldest generation guaranteed to be collected."
(let ((gen (if full sb-vm:+pseudo-static-generation+ gen))) (let ((gen (if full sb-vm:+pseudo-static-generation+ gen)))
(when (/= (extern-alien "use_smlgc" int) 0)
(return-from gc))
(when (eq t (sub-gc gen)) (when (eq t (sub-gc gen))
(post-gc)))) (post-gc))))
@ -527,7 +529,16 @@ Experimental: interface subject to change."
:read-only) :read-only)
((< sb-vm:static-space-start addr ((< sb-vm:static-space-start addr
(sap-int sb-vm:*static-space-free-pointer*)) (sap-int sb-vm:*static-space-free-pointer*))
:static)))) :static)
((/= 0 (alien-funcall (extern-alien "in_bitmapped_subheap"
(function int unsigned))
addr))
:bitmapped)
((/= 0 (alien-funcall (extern-alien "careful_lispobj_stack_slot"
(function int unsigned))
addr))
:large-object))))
;;; Return true if X is in any non-stack GC-managed space. ;;; Return true if X is in any non-stack GC-managed space.
;;; (Non-stack implies not TLS nor binding stack) ;;; (Non-stack implies not TLS nor binding stack)
;;; There's a microscopic window of time in which next_free_page for dynamic space ;;; There's a microscopic window of time in which next_free_page for dynamic space

View file

@ -197,7 +197,8 @@
(smashed-cells nil) (smashed-cells nil)
;; This slot is used to link weak hash tables during GC. When the GC ;; This slot is used to link weak hash tables during GC. When the GC
;; isn't running it is always NIL. ;; isn't running it is always NIL.
(next-weak-hash-table nil :type null)) (next-weak-hash-table nil :type null)
(implied-edges-done nil :type null))
(defconstant hash-table-weak-flag 8) (defconstant hash-table-weak-flag 8)
;;; USERFUN-FLAG implies a nonstandard hash function. Such tables may also have ;;; USERFUN-FLAG implies a nonstandard hash function. Such tables may also have

View file

@ -111,6 +111,9 @@
(defconstant +hashset-unused-cell+ 0) (defconstant +hashset-unused-cell+ 0)
(defmacro hs-chain-terminator-p (val) `(eq ,val 0)) (defmacro hs-chain-terminator-p (val) `(eq ,val 0))
#+sb-xc-host
(defmacro add-weak-object (x) `(progn ,x))
(defun allocate-hashset-storage (capacity weakp) (defun allocate-hashset-storage (capacity weakp)
(declare (type (unsigned-byte 28) capacity)) ; 256M cells maximum (declare (type (unsigned-byte 28) capacity)) ; 256M cells maximum
(declare (ignorable weakp)) (declare (ignorable weakp))
@ -126,6 +129,8 @@
(psl-vector (make-array capacity :element-type '(unsigned-byte 8) (psl-vector (make-array capacity :element-type '(unsigned-byte 8)
:initial-element 0)) :initial-element 0))
(hash-vector (make-array capacity :element-type '(unsigned-byte 16)))) (hash-vector (make-array capacity :element-type '(unsigned-byte 16))))
(when weakp
(add-weak-object cells))
(setf (hs-cells-gc-epoch cells) sb-kernel::*gc-epoch*) (setf (hs-cells-gc-epoch cells) sb-kernel::*gc-epoch*)
(setf (hs-cells-max-psl cells) 0) (setf (hs-cells-max-psl cells) 0)
(setf (hs-cells-n-avail cells) capacity) (setf (hs-cells-n-avail cells) capacity)
@ -354,6 +359,7 @@
(n-live)) (n-live))
;; First decide if the table occupancy needs to be recomputed after GC ;; First decide if the table occupancy needs to be recomputed after GC
(unless (eq (hs-cells-gc-epoch cells) current-epoch) (unless (eq (hs-cells-gc-epoch cells) current-epoch)
;(format t "~&hashset GC epoch changed~%")
(setf n-live (hs-cells-occupancy cells capacity) (setf n-live (hs-cells-occupancy cells capacity)
(hs-cells-n-avail cells) (- capacity n-live) (hs-cells-n-avail cells) (- capacity n-live)
(hs-cells-gc-epoch cells) current-epoch)) (hs-cells-gc-epoch cells) current-epoch))
@ -568,3 +574,41 @@
(dotimes (i (weak-vector-len vector)) (dotimes (i (weak-vector-len vector))
(when (eq (weak-vector-ref vector i) key) (when (eq (weak-vector-ref vector i) key)
(return key))))) (return key)))))
(defun hashset-probing-sequence (hashset key)
(let* ((storage (hashset-storage hashset))
(cells (hss-cells storage))
(mask (hs-cells-mask cells))
(index (logand (funcall (hashset-hash-function hashset) key) mask))
(interval 1)
(sequence))
(loop
(push index sequence)
(let ((probed-key (hs-cell-ref cells index)))
(assert (not (hs-chain-terminator-p probed-key)))
(when (and probed-key (funcall (hashset-test-function hashset) probed-key key))
(return (nreverse sequence)))
(setq index (logand (+ index interval) mask))
(incf interval)))))
#-sb-xc-host
(defun dump-hashset (hashset)
(let* ((storage (hashset-storage hashset))
(cells (hss-cells storage))
(mutex (hashset-mutex hashset))
(held (and mutex (sb-thread:holding-mutex-p mutex)))
(maxpsl 0)
(n 0))
(declare (notinline sb-thread:holding-mutex-p))
;; to avoid recursive lock error. This is for debugging so it doesn't matter
(when held (sb-thread:release-mutex mutex))
(dotimes (i (hs-cells-capacity cells))
(let ((x (hs-cell-ref cells i)))
(when (and x (not (hs-chain-terminator-p x)))
(let ((seq (hashset-probing-sequence hashset x)))
(setq maxpsl (max (length seq) maxpsl))
(incf n)
(let ((*print-pretty* nil))
(format t "[~4d] = ~s ~s~%" i seq x))))))
(when held (sb-thread:grab-mutex mutex))
(format t ";~&Total entries: ~D, max-psl = ~D (stored=~D)~%"
n maxpsl (hs-cells-max-psl cells))))

View file

@ -2334,3 +2334,7 @@ Works on all CASable places."
(loop (let ((,new (cdr ,old))) (loop (let ((,new (cdr ,old)))
(when (eq ,old (setf ,old ,cas-form)) (when (eq ,old (setf ,old ,cas-form))
(return (car (truly-the list ,old))))))))) (return (car (truly-the list ,old)))))))))
(sb-xc:defmacro printf (x)
`(alien-funcall (extern-alien "printf" (function void system-area-pointer))
(vector-sap ,(format nil "~A~%" x))))

View file

@ -273,6 +273,88 @@
(multiple-value-bind (start end) (%space-bounds subspace) (multiple-value-bind (start end) (%space-bounds subspace)
(map-objects-in-range function start end))))) (map-objects-in-range function start end)))))
(define-alien-type nil
(struct segment-layout
(blocksize-bytes unsigned-int)
(bitmap-base (array unsigned-int 4))
(bitmap-sentinel (array unsigned-int 3))
(stack-offset unsigned-int)
(stack-limit unsigned-int)
(block-offset unsigned-int)
(num-blocks unsigned-int)
(block-limit unsigned-int)))
(define-alien-type nil
(struct segment
(as-list system-area-pointer)
(stack system-area-pointer)
(block-base system-area-pointer)
(snapshot-free system-area-pointer)
(layout (* (struct segment-layout)))
(blocksize-log2 unsigned-int)
(free-count int)))
(defun map-sml#gc-objects (function)
(break "don't use this yet")
(dx-let ((bounds (make-array 2 :element-type 'word)))
(alien-funcall (extern-alien "get_segment_pool_bounds" (function void system-area-pointer))
(vector-sap bounds))
(format t "~&Heap range ~x ~x~%" (aref bounds 0) (aref bounds 1))
(loop for addr from (aref bounds 0) below (aref bounds 1) by 32768
do
(let* ((segment (sap-alien (int-sap addr) (* (struct segment))))
(obj-addr (slot segment 'block-base))
(layout (slot segment 'layout))
(blocksize (slot layout 'blocksize-bytes))
(num-blocks (slot layout 'num-blocks)))
(unless (= (sap-int obj-addr) 0)
(do ((i 0 (1+ i))
(obj-addr obj-addr (sap+ obj-addr blocksize)))
((= i num-blocks))
(let* ((word (sap-ref-word obj-addr 0))
(widetag (logand word widetag-mask)))
(unless (= word #xffffffffdeadbeef)
(funcall function
(lispobj@baseptr obj-addr widetag)
segment)))))))))
#+nil
(defun collect-weak-objects ()
(nconc (collect ((result))
(map-sml#gc-objects
(lambda (x segment)
(declare (ignore segment))
(when (weak-pointer-p x)
(result x))))
(result))
(sb-vm:list-allocated-objects
:dynamic
:type sb-vm:weak-pointer-widetag)))
(defun check-oversized ()
(map-sml#gc-objects
(lambda (object segment)
(let* ((log2size (slot segment 'blocksize-log2))
(blocksize (ash 1 log2size))
(actual-size (primitive-object-size object))
(smaller-size (ash blocksize -1)))
(when (<= actual-size smaller-size)
(format t "~&object @ ~x ~s could have fit in smaller block~%"
(get-lisp-obj-address object)
(type-of object)))))))
(defun show-sml#gc-heap-objects (&aux prev-seg)
(map-sml#gc-objects
(lambda (x segment)
(unless (eq segment prev-seg)
(format t "-- segment @ ~x blocksize ~d --~%"
(sap-int (alien-sap segment))
(slot (slot segment 'layout) 'blocksize-bytes))
(setq prev-seg segment))
(format t " ~x: ~A~%"
(get-lisp-obj-address x)
(type-of x)))))
#| #|
MAP-ALLOCATED-OBJECTS is fundamentally unsafe to use if the user-supplied MAP-ALLOCATED-OBJECTS is fundamentally unsafe to use if the user-supplied
function allocates anything. Consider what can happens when NEXT-FREE-PAGE function allocates anything. Consider what can happens when NEXT-FREE-PAGE

View file

@ -231,7 +231,7 @@
(min 16 (ash (primitive-object-size thing) (- word-shift)))))))) (min 16 (ash (primitive-object-size thing) (- word-shift))))))))
;; For all objects except lists, there is 1 iteration showing NWORDS. ;; For all objects except lists, there is 1 iteration showing NWORDS.
;; Lists iterate up to COUNT times showing 2 words each time. ;; Lists iterate up to COUNT times showing 2 words each time.
(dotimes (iteration (if (and (consp thing) countp) count 1)) (dotimes (n-iterations (if (and (consp thing) countp) count 1))
(dotimes (i nwords) (dotimes (i nwords)
(let ((word (sap-ref-word (int-sap addr) (ash i word-shift)))) (let ((word (sap-ref-word (int-sap addr) (ash i word-shift))))
(multiple-value-bind (lispobj ok fmt) (multiple-value-bind (lispobj ok fmt)

View file

@ -420,6 +420,21 @@
(declare (type code-component code-obj)) (declare (type code-component code-obj))
(ash (code-fun-table-count code-obj) -5)) (ash (code-fun-table-count code-obj) -5))
(defun code-pseudo-atomic-locations (code-obj)
(declare (type code-component code-obj))
(let ((n-simple-funs (code-n-entries code-obj)))
(when (plusp n-simple-funs)
(let* ((trailer-index (truly-the fixnum (- (ash (+ n-simple-funs 2) 2))))
(count (code-trailer-ref code-obj trailer-index)))
(when (plusp count)
(let ((v (make-array count :element-type '(unsigned-byte 32))))
(dotimes (i count)
(setf (aref v (decf count))
(code-trailer-ref code-obj
(truly-the fixnum (decf trailer-index 4)))))
(return-from code-pseudo-atomic-locations v))))))
#.(sb-xc:make-array 0 :element-type '(unsigned-byte 32)))
;;; Start and count of fdefns used in #'F synax or normal named call ;;; Start and count of fdefns used in #'F synax or normal named call
;;; (i.e. at the head of an expression) ;;; (i.e. at the head of an expression)
(defun code-header-fdefn-range (code-obj) (defun code-header-fdefn-range (code-obj)

View file

@ -232,6 +232,12 @@
;; and pick an appropriate way to atomically update the node. ;; and pick an appropriate way to atomically update the node.
(aver (typep node 'so-key-node))) (aver (typep node 'so-key-node)))
((> (atomic-incf (so-count table)) (so-threshold table)) ((> (atomic-incf (so-count table)) (so-threshold table))
#+nil
(sb-alien:alien-funcall
(sb-alien:extern-alien "printf" (function sb-alien:void system-area-pointer sb-alien:int sb-alien:int))
(vector-sap #.(format nil "so-insert: Threshold reached %d %d~%"))
(so-count table)
(so-threshold table))
(so-expand-bins table bins))) (so-expand-bins table bins)))
(values node foundp)))) (values node foundp))))
@ -370,6 +376,7 @@
(defun make-so-set/string () (make nil)) (defun make-so-set/string () (make nil))
(defun make-so-map/string () (make t))) (defun make-so-map/string () (make t)))
(declaim (ftype (sfunction () t) make-so-set/addr))
(flet ((make (valuesp) (flet ((make (valuesp)
(%make-so-list (lambda (x) (multiplicative-hash (get-lisp-obj-address x))) (%make-so-list (lambda (x) (multiplicative-hash (get-lisp-obj-address x)))
#'%so-put/addr #'%so-delete/addr #'so-find/addr #'%so-put/addr #'%so-delete/addr #'so-find/addr
@ -377,39 +384,55 @@
(defun make-so-set/addr () (make nil)) (defun make-so-set/addr () (make nil))
(defun make-so-map/addr () (make t))) (defun make-so-map/addr () (make t)))
(macrolet ((with-start-node ((node) &body body)
`(let* ((hash (masked-hash (multiplicative-hash (get-lisp-obj-address key))))
(bins (so-bins (truly-the split-ordered-list table)))
(shift (bin-shift bins))
(bin-vector (car bins))
(index (ash hash (- shift)))
(,node (svref bin-vector index)))
(when (unbound-marker-p ,node)
;; Pick any nonempty bin to the left of the intended bin.
;; It's always OK to pick a suboptimal start bin, because there is no requirement
;; to observe the BINS vector in the most up-to-date state anyway.
(setf ,node (find-if-not #'unbound-marker-p bin-vector
:end index :from-end t)))
#+sb-devel (aver (dummy-node-p ,node))
,@body)))
;;; Like SO-FIND/ADDR but never initializing a bin
(defun %so-eq-set-find (table key)
(with-start-node (start-node)
(let ((node (%so-search/addr start-node hash key)))
(unless (or (endp node) (dummy-node-p node) (neq (so-key node) key))
node))))
;;; Like SO-DELETE but never initializing a bin
(defun %so-eq-set-delete (table key)
(with-start-node (start-node)
(let ((deleted (%so-delete/addr start-node hash key)))
(when deleted (atomic-decf (so-count table)))
deleted)))
;;; This special case can be used during allocation of new objects (and usually only then). ;;; This special case can be used during allocation of new objects (and usually only then).
;;; The address can not have previously existed in the table since it is fresh. ;;; The address can not have previously existed in the table since it is fresh.
;;; Additionally, this takes a pre-allocated NODE, does not perform INITIALIZE-BIN, ;;; Additionally, this takes a pre-allocated NODE, does not perform INITIALIZE-BIN,
;;; and does not increment the occupancy count. The latter two steps can be performed later. ;;; and does not increment the occupancy count. The latter two steps can be performed later.
;;; This can be run within a pseudo-atomic section, and the next step outside of it. ;;; This can be run within a pseudo-atomic section, and the next step outside of it.
(defun %so-eq-set-phase1-insert (table node key) (defun %so-eq-set-phase1-insert (table node key)
(let* ((hash (masked-hash (multiplicative-hash (get-lisp-obj-address key)))) (let ((node (truly-the so-key-node node)))
(bins (so-bins (truly-the split-ordered-list table))) (with-start-node (start-node)
(shift (bin-shift bins)) (aver (and (eq (%instance-layout node) #.(find-layout 'so-key-node))
(bin-vector (car bins)) (eq key (%instance-ref node (get-dsd-index so-key-node so-key)))
(index (ash hash (- shift))) (eq hash (%instance-ref node (get-dsd-index so-node node-hash)))))
(start-node (svref bin-vector index))) (loop
;; HASH and KEY of a node are not accessible as read/write slots to clients (multiple-value-bind (right left) (%so-search/addr start-node hash key)
;; of the table, but _can_ be written by this insert function only in as much as #+sb-devel
;; the caller has to preallocate the node, and we have to fill it in here. (when (and (not (endp right)) (not (dummy-node-p right)))
(setf (%instance-ref (truly-the instance node) (get-dsd-index so-node node-hash)) hash ;; The successor had better not be the droid you're looking for.
(%instance-ref node (get-dsd-index so-key-node so-key)) key) (aver (neq key (so-key right))))
(when (unbound-marker-p start-node) (setf (%node-next node) right)
;; Pick any nonempty bin to the left of the intended bin. (when (eq (cas (%node-next left) right node) right)
;; It's always OK to pick a suboptimal start bin, because there is no requirement (return node)))))))
;; to observe the BINS vector in the most up-to-date state anyway. ) ; end MACROLET
(setf start-node (find-if-not #'unbound-marker-p bin-vector
:end index :from-end t)))
#+sb-devel (aver (dummy-node-p start-node))
(loop
(multiple-value-bind (right left) (%so-search/addr start-node hash key)
#+sb-devel
(when (and (not (endp right)) (not (dummy-node-p right)))
;; The successor had better not be the droid you're looking for.
(aver (neq key (so-key right))))
(setf (%node-next node) right)
(when (eq (cas (%node-next left) right node) right)
(return t))))))
;;; Complete the insertion of a previously-known-not-to-exist key. ;;; Complete the insertion of a previously-known-not-to-exist key.
(defun %so-eq-set-phase2-insert (table node) (defun %so-eq-set-phase2-insert (table node)
@ -417,6 +440,13 @@
(declare (ignore hash start-node)) (declare (ignore hash start-node))
(when (> (atomic-incf (so-count table)) (so-threshold table)) (when (> (atomic-incf (so-count table)) (so-threshold table))
(so-expand-bins table bins))) (so-expand-bins table bins)))
#+nil
(let* ((addr (ash (sb-lockless:so-key node) 1))
(obj (sb-kernel:%make-lisp-obj (logior addr sb-vm:other-pointer-lowtag)))
(nbytes (sb-ext:primitive-object-size obj))
(msg (format nil "new large obj @ ~x .. ~x~%" addr (+ addr nbytes))))
(sb-sys:with-pinned-objects (msg)
(sb-unix:unix-write 2 msg 0 (length msg))))
node) node)
(defun c-so-find/addr (solist key) (defun c-so-find/addr (solist key)
@ -438,3 +468,30 @@
;; Would it better for tests to close the region? Maybe, ;; Would it better for tests to close the region? Maybe,
;; but we can't count on everybody doing that. ;; but we can't count on everybody doing that.
(%make-lisp-obj (logior result sb-vm:instance-pointer-lowtag))))))) (%make-lisp-obj (logior result sb-vm:instance-pointer-lowtag)))))))
(defun show-address-based-list (hashset &optional (sort :hash))
(declare (type (member :address :hash) sort))
(without-gcing
(collect ((items))
(let ((node (so-head hashset)))
(loop (when (endp node) (return))
(when (so-key-node-p node)
(let* ((sap (descriptor-sap (so-key node)))
(widetag #+little-endian (sap-ref-8 sap 0))
(lowtag
(case widetag
(#.sb-vm:instance-widetag sb-vm:instance-pointer-lowtag)
((#.sb-vm:funcallable-instance-widetag
#.sb-vm:closure-widetag) sb-vm:fun-pointer-lowtag)
(t sb-vm:other-pointer-lowtag)))
(key (%make-lisp-obj (logior (sap-int sap) lowtag))))
(items
(list (get-lisp-obj-address node) (node-hash node)
(sap-int sap)
(sb-ext:primitive-object-size key) (type-of key)))))
(setq node (get-next node))))
(let ((list (if (eq sort :hash)
(items)
(sort (items) #'< :key #'third))))
(let ((*print-pretty* nil))
(format t "~:{~x ~16,'0x ~16x ~6x ~s~%~}" list))))))

View file

@ -629,6 +629,7 @@ Examples:
sb-vm:vector-weak-flag))))) sb-vm:vector-weak-flag)))))
(when (logtest flags hash-table-synchronized-flag) (when (logtest flags hash-table-synchronized-flag)
(install-hash-table-lock table)) (install-hash-table-lock table))
(when weakp (add-weak-object table))
table)) table))
;;; a "plain" hash-table has nothing fancy: default size, default growth rate, ;;; a "plain" hash-table has nothing fancy: default size, default growth rate,
@ -1503,6 +1504,18 @@ nnnn 1_ any linear scan (don't try to read when rehash already in progr
;; are no concurrent readers to potentially mess up the chains. ;; are no concurrent readers to potentially mess up the chains.
(hash-search))))))) (hash-search)))))))
(defmacro with-global-rwlock (&body body)
`(with-alien ((rwlock (array unsigned 1) :extern "weakTableLock"))
(unwind-protect
(progn
;; This lock makes GC's mark phase termination simpler.
;; It seems feasible to eliminate the lock if reading, perhaps.
(alien-funcall (extern-alien "pthread_rwlock_rdlock" (function int system-area-pointer))
(alien-sap rwlock))
,@body)
(alien-funcall (extern-alien "pthread_rwlock_unlock" (function int system-area-pointer))
(alien-sap rwlock)))))
(defmacro with-weak-hash-table-entry (&body body) (defmacro with-weak-hash-table-entry (&body body)
`(with-pinned-objects (key) `(with-pinned-objects (key)
(binding* (((hash0 address-sensitive-p) (binding* (((hash0 address-sensitive-p)
@ -1517,16 +1530,17 @@ nnnn 1_ any linear scan (don't try to read when rehash already in progr
(kv-vector (hash-table-pairs hash-table))) (kv-vector (hash-table-pairs hash-table)))
(declare (index physical-index)) (declare (index physical-index))
,@body))) ,@body)))
;; It would be ideal if we were consistent about all tables NOT having (with-global-rwlock
;; synchronization unless created with ":SYNCHRONIZED T" ;; It would be ideal if we were consistent about all tables NOT having
;; but it looks tricky to support concurrent gethash on weak tables, ;; synchronization unless created with ":SYNCHRONIZED T"
;; so we mostly default to locking, except where there is an outer scope ;; but it looks tricky to support concurrent gethash on weak tables,
;; providing mutual exclusion such as WITH-FINALIZER-STORE. ;; so we mostly default to locking, except where there is an outer scope
(if (hash-table-synchronized-p hash-table) ;; providing mutual exclusion such as WITH-FINALIZER-STORE.
;; Use the private slot accessor for the lock because it's known (if (hash-table-synchronized-p hash-table)
;; to have a mutex. ;; Use the private slot accessor for the lock because it's known
(sb-thread::call-with-recursive-system-lock #'body (hash-table-%lock hash-table)) ;; to have a mutex.
(body)))))) (sb-thread::call-with-recursive-system-lock #'body (hash-table-%lock hash-table))
(body)))))))
(defun gethash/weak (key hash-table default) (defun gethash/weak (key hash-table default)
(declare (type hash-table hash-table) (optimize speed)) (declare (type hash-table hash-table) (optimize speed))
@ -2018,9 +2032,15 @@ table itself."
(setf (hash-table-smashed-cells hash-table) nil)) (setf (hash-table-smashed-cells hash-table) nil))
(setf (hash-table-next-free-kv hash-table) 1 (setf (hash-table-next-free-kv hash-table) 1
(kv-vector-high-water-mark kv-vector) 0)))) (kv-vector-high-water-mark kv-vector) 0))))
(if (hash-table-synchronized-p hash-table) ;; Silly me, this should be the CLRHASH-IMPL function for weak tables, no?
(sb-thread::call-with-recursive-system-lock #'clear (hash-table-%lock hash-table)) (cond ((and (hash-table-weak-p hash-table) (/= (extern-alien "use_smlgc" int) 0))
(clear)))) ;; FIXME: this could be done without the deletion barrier or weakTableLock
;; by just allocating a new pair vector instead of reusing the current one.
(with-global-rwlock
(sb-thread::call-with-recursive-system-lock #'clear (hash-table-%lock hash-table))))
((hash-table-synchronized-p hash-table)
(sb-thread::call-with-recursive-system-lock #'clear (hash-table-%lock hash-table)))
(t (clear)))))
hash-table) hash-table)

View file

@ -2190,6 +2190,7 @@ PACKAGE."
(let* ((string (car cell)) (let* ((string (car cell))
(pkg (,sub-finder string)) (pkg (,sub-finder string))
(new (sb-c::allocate-weak-vector 3))) (new (sb-c::allocate-weak-vector 3)))
(add-weak-object new)
(setf (weak-vector-ref new 0) *package-names-cookie* (setf (weak-vector-ref new 0) *package-names-cookie*
(weak-vector-ref new 1) (info-gethash string (car *package-nickname-ids*)) (weak-vector-ref new 1) (info-gethash string (car *package-nickname-ids*))
(weak-vector-ref new 2) pkg) (weak-vector-ref new 2) pkg)

View file

@ -275,6 +275,7 @@ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
(ash y -1) (aref state (logand y 1))))) (ash y -1) (aref state (logand y 1)))))
(values)) (values))
#+nil
(declaim (start-block random %random-single-float %random-double-float (declaim (start-block random %random-single-float %random-double-float
random-chunk big-random-chunk)) random-chunk big-random-chunk))

View file

@ -48,6 +48,12 @@
(and (simple-vector-p x) (and (simple-vector-p x)
(test-header-data-bit x (ash sb-vm:vector-weak-flag sb-vm:array-flags-data-position)))) (test-header-data-bit x (ash sb-vm:vector-weak-flag sb-vm:array-flags-data-position))))
(defun add-weak-object (x)
(alien-funcall (extern-alien "record_weak_object" (function void unsigned))
(get-lisp-obj-address x))
x)
(defun make-weak-vector (length &key (initial-contents nil contents-p) (defun make-weak-vector (length &key (initial-contents nil contents-p)
(initial-element nil element-p)) (initial-element nil element-p))
(declare (index length)) (declare (index length))
@ -62,6 +68,7 @@
(error "~S has ~D elements, vector length is ~D." (error "~S has ~D elements, vector length is ~D."
:initial-contents contents-length length)))) :initial-contents contents-length length))))
(let ((v (sb-c::allocate-weak-vector length))) (let ((v (sb-c::allocate-weak-vector length)))
(add-weak-object v)
(if initial-contents (if initial-contents
(dotimes (i length) (dotimes (i length)
(setf (weak-vector-ref v i) (elt initial-contents i))) (setf (weak-vector-ref v i) (elt initial-contents i)))
@ -71,7 +78,10 @@
v)) v))
(defun make-weak-pointer (object) (defun make-weak-pointer (object)
"Allocate and return a weak pointer which points to OBJECT." "Allocate and return a weak pointer which points to OBJECT."
(make-weak-pointer object)) (let ((wp (sb-vm::%make-weak-pointer object)))
(when (sb-vm:is-lisp-pointer (get-lisp-obj-address object))
(add-weak-object wp))
wp))
(declaim (inline weak-pointer-value)) (declaim (inline weak-pointer-value))
(defun weak-pointer-value (weak-pointer) (defun weak-pointer-value (weak-pointer)
@ -79,7 +89,7 @@
If the referent of WEAK-POINTER has been garbage collected, If the referent of WEAK-POINTER has been garbage collected,
returns the values NIL and NIL." returns the values NIL and NIL."
(declare (type weak-pointer weak-pointer)) (declare (type weak-pointer weak-pointer))
(let ((value (sb-vm::%weak-pointer-value weak-pointer))) (let ((value (%primitive sb-vm::%weak-pointer-value weak-pointer)))
(if (sb-vm::unbound-marker-p value) (if (sb-vm::unbound-marker-p value)
(values nil nil) (values nil nil)
(values value t)))) (values value t))))

View file

@ -328,6 +328,7 @@
("src/code/float" :not-host) ("src/code/float" :not-host)
("src/code/irrat" :not-host) ("src/code/irrat" :not-host)
("src/code/share-vm" :not-host)
("src/code/alloc" :not-host) ; for ALLOCATE-SYSTEM-MEMORY in make-buffer ("src/code/alloc" :not-host) ; for ALLOCATE-SYSTEM-MEMORY in make-buffer
("src/code/fd-stream" :not-host) ("src/code/fd-stream" :not-host)
("src/code/target-char" :not-host) ("src/code/target-char" :not-host)
@ -492,8 +493,6 @@
#+haiku ("src/code/haiku-os" :not-host) #+haiku ("src/code/haiku-os" :not-host)
#+win32 ("src/code/win32-os" :not-host) #+win32 ("src/code/win32-os" :not-host)
("src/code/share-vm" :not-host)
#+sparc ("src/code/sparc-vm" :not-host) #+sparc ("src/code/sparc-vm" :not-host)
#+x86 ("src/code/x86-vm" :not-host) #+x86 ("src/code/x86-vm" :not-host)
#+x86-64("src/code/x86-64-vm" :not-host) #+x86-64("src/code/x86-64-vm" :not-host)

View file

@ -2936,7 +2936,8 @@ the stuff in here originated in CMU CL's EXTENSIONS package and is retained,
possibly temporarily, because it might be used internally.") possibly temporarily, because it might be used internally.")
(:use "CL" "SB-ALIEN" "SB-GRAY" "SB-FASL" "SB-SYS") (:use "CL" "SB-ALIEN" "SB-GRAY" "SB-FASL" "SB-SYS")
(:export ;; lambda list keyword extensions (:export ;; lambda list keyword extensions
"&MORE" "&MORE"
"PRINTF"
;; utilities for floating point zero handling ;; utilities for floating point zero handling
@ -3608,6 +3609,7 @@ package is deprecated in favour of SB-MOP.")
"MAKE-SO-MAP/STRING" "MAKE-SO-MAP/FIXNUM" "MAKE-SO-MAP/ADDR" "MAKE-SO-MAP/STRING" "MAKE-SO-MAP/FIXNUM" "MAKE-SO-MAP/ADDR"
"MULTIPLICATIVE-HASH" "MULTIPLICATIVE-HASH"
"%SO-EQ-SET-PHASE1-INSERT" "%SO-EQ-SET-PHASE2-INSERT" "%SO-EQ-SET-PHASE1-INSERT" "%SO-EQ-SET-PHASE2-INSERT"
"%SO-EQ-SET-FIND" "%SO-EQ-SET-DELETE"
"SO-INSERT" "SO-DELETE" "SO-FIND" "SO-INSERT" "SO-DELETE" "SO-FIND"
"C-SO-FIND/ADDR" "C-SO-FIND/ADDR"
"SO-KEY" "SO-DATA" "SO-MAPLIST") "SO-KEY" "SO-DATA" "SO-MAPLIST")

View file

@ -319,6 +319,11 @@
;; into the data section ;; into the data section
(constant-table (make-hash-table :test #'equal) :read-only t) (constant-table (make-hash-table :test #'equal) :read-only t)
(constant-vector (make-array 16 :adjustable t :fill-pointer 0) :read-only t) (constant-vector (make-array 16 :adjustable t :fill-pointer 0) :read-only t)
;; for collecting barrier locations to be stored within the code trailer.
;; This facilitates a program-counter-based pseudo-atomic wherein the signal
;; handler see that it wants to stop within an uninterruptible sequence,
;; and can choose to rollback or roll forward.
(pseudo-atomic-locs)
;; for deterministic allocation profiler (or possibly other tooling) ;; for deterministic allocation profiler (or possibly other tooling)
;; that wants to monkey patch the instructions at runtime. ;; that wants to monkey patch the instructions at runtime.
(alloc-points) (alloc-points)
@ -1448,6 +1453,7 @@
(setf (section-tail first) last-stmt)) (setf (section-tail first) last-stmt))
first) first)
;(defvar *cur-stmt* nil)
;;; Combine INPUTS into one assembly stream and assemble into SEGMENT ;;; Combine INPUTS into one assembly stream and assemble into SEGMENT
(defun %assemble (segment section) (defun %assemble (segment section)
(let ((*current-vop* nil) (let ((*current-vop* nil)
@ -1475,6 +1481,7 @@
(dump-symbolic-asm section sb-c::*compiler-trace-output*)) (dump-symbolic-asm section sb-c::*compiler-trace-output*))
(do ((statement (stmt-next (section-start section)) (stmt-next statement))) (do ((statement (stmt-next (section-start section)) (stmt-next statement)))
((null statement)) ((null statement))
; (setq *cur-stmt* statement)
(awhen (stmt-vop statement) (setq *current-vop* it)) (awhen (stmt-vop statement) (setq *current-vop* it))
(dolist (label (ensure-list (stmt-labels statement))) (dolist (label (ensure-list (stmt-labels statement)))
(%emit-label segment *current-vop* label)) (%emit-label segment *current-vop* label))
@ -1510,7 +1517,11 @@
;;; The interface to %ASSEMBLE ;;; The interface to %ASSEMBLE
(defun assemble-sections (asmstream simple-fun-labels segment) (defun assemble-sections (asmstream simple-fun-labels segment)
(let* ((n-entries (length simple-fun-labels)) (let* ((n-entries (length simple-fun-labels))
(trailer-len (* (+ n-entries 1) 4)) (fun-offsets-len (* (1+ n-entries) 4))
;; Each pseudoatomic location is stored as a 4-byte quantity.
(n-pseudo-atomic-locs (length (asmstream-pseudo-atomic-locs asmstream)))
(trailer-len (+ (* (1+ n-pseudo-atomic-locs) 4)
fun-offsets-len))
(end-text (gen-label)) (end-text (gen-label))
(combined (combined
(append-sections (append-sections
@ -1544,7 +1555,6 @@
0 0
(- index trailer-len (label-position end-text))))) (- index trailer-len (label-position end-text)))))
(unless (and (typep trailer-len '(unsigned-byte 16)) (unless (and (typep trailer-len '(unsigned-byte 16))
(typep n-entries '(unsigned-byte 12))
;; Padding must be representable in 4 bits at assembly time, ;; Padding must be representable in 4 bits at assembly time,
;; but CODE-HEADER/TRAILER-ADJUST can increase the padding. ;; but CODE-HEADER/TRAILER-ADJUST can increase the padding.
(typep padding '(unsigned-byte 4))) (typep padding '(unsigned-byte 4)))
@ -1552,6 +1562,13 @@
(setf (sap-ref-16 sap (- index 2)) trailer-len) (setf (sap-ref-16 sap (- index 2)) trailer-len)
(setf (sap-ref-16 sap (- index 4)) (logior (ash n-entries 5) padding))) (setf (sap-ref-16 sap (- index 4)) (logior (ash n-entries 5) padding)))
(decf index trailer-len) (decf index trailer-len)
(dolist (label (sort (asmstream-pseudo-atomic-locs asmstream) #'<
:key #'label-posn))
(setf (sap-ref-32 sap index) (label-posn label))
(incf index 4))
(setf (sap-ref-32 sap index) n-pseudo-atomic-locs)
(incf index 4)
(aver (= index (- (length octets) fun-offsets-len)))
;; Iteration over label positions occurs from numerically highest ;; Iteration over label positions occurs from numerically highest
;; to lowest, which is right because the 0th indexed simple-fun ;; to lowest, which is right because the 0th indexed simple-fun
;; has the lowest entry offset, and is the last one written ;; has the lowest entry offset, and is the last one written
@ -1563,8 +1580,8 @@
(let ((val (label-position label))) (let ((val (label-position label)))
(push val fun-offsets) (push val fun-offsets)
(setf (sap-ref-32 sap index) val) (setf (sap-ref-32 sap index) val)
(incf index 4))))) (incf index 4)))
(aver (= index (- (length octets) 4))) (aver (= index (- (length octets) 4)))))
(values segment (values segment
(label-position end-text) (label-position end-text)
(segment-fixup-notes segment) (segment-fixup-notes segment)

View file

@ -21,7 +21,7 @@
;;; for SB-FASL:*ASSEMBLER-ROUTINES*. We have to return a fixed answer for that. ;;; for SB-FASL:*ASSEMBLER-ROUTINES*. We have to return a fixed answer for that.
(defun asm-routines-boxed-header-nwords () (defun asm-routines-boxed-header-nwords ()
(align-up (+ sb-vm:code-constants-offset (align-up (+ sb-vm:code-constants-offset
#+x86-64 1) ; KLUDGE: make room for 1 boxed constant #+x86-64 2) ; KLUDGE: make room for 1 boxed constant
2)) 2))
;;; the number of bytes used by the code object header ;;; the number of bytes used by the code object header
(defun component-header-length () (defun component-header-length ()

View file

@ -41,6 +41,7 @@
(eval-when (:compile-toplevel #-sb-xc :load-toplevel :execute) (eval-when (:compile-toplevel #-sb-xc :load-toplevel :execute)
(#-sb-xc defmacro #+sb-xc sb-xc:defmacro sb-vm::define-assembly-routine (#-sb-xc defmacro #+sb-xc sb-xc:defmacro sb-vm::define-assembly-routine
(name&options vars &body code) (name&options vars &body code)
(let ((expansion
(multiple-value-bind (name options) (multiple-value-bind (name options)
(if (atom name&options) (if (atom name&options)
(values name&options nil) (values name&options nil)
@ -50,3 +51,8 @@
(if (member :sb-assembling sb-xc:*features*) (if (member :sb-assembling sb-xc:*features*)
(sb-c::emit-assemble name options regs code) (sb-c::emit-assemble name options regs code)
(sb-c::emit-assemble-vop name options regs)))))) (sb-c::emit-assemble-vop name options regs))))))
#+nil
(let ((*print-level* nil)(*print-length* nil))
(format t "DEFINE-ASM-ROUTINE ~S ~S ->~%~S~%" (car (ensure-list name&options)) (if (member :sb-assembling sb-xc:*features*) "assembling" "compiling")
expansion))
expansion)))

View file

@ -1924,7 +1924,9 @@ core and return a descriptor to it."
(desired (sb-vm:static-fun-offset sym))) (desired (sb-vm:static-fun-offset sym)))
(unless (= offset desired) (unless (= offset desired)
(error "Offset from FDEFN ~S to ~S is ~W, not ~W." (error "Offset from FDEFN ~S to ~S is ~W, not ~W."
sym nil offset desired)))))) sym nil offset desired)))))
(let ((v (word-vector (make-list 200 :initial-element 0) *static*)))
(format t "data collection vector @ ~x~%" (descriptor-bits v))))
;;; Sort *COLD-LAYOUTS* to return them in a deterministic order. ;;; Sort *COLD-LAYOUTS* to return them in a deterministic order.
(defun sort-cold-layouts () (defun sort-cold-layouts ()
@ -1933,6 +1935,7 @@ core and return a descriptor to it."
;;; Establish initial values for magic symbols. ;;; Establish initial values for magic symbols.
;;; ;;;
(defvar *cold-assembler-obj*) ; a single code component
(defun finish-symbols () (defun finish-symbols ()
(cold-set 'sb-kernel::*!initial-layouts* (cold-set 'sb-kernel::*!initial-layouts*
(vector-in-core (vector-in-core

View file

@ -319,8 +319,8 @@ during backtrace.
(define-primitive-object (weak-pointer :type weak-pointer (define-primitive-object (weak-pointer :type weak-pointer
:lowtag other-pointer-lowtag :lowtag other-pointer-lowtag
:widetag weak-pointer-widetag :widetag weak-pointer-widetag
:alloc-trans make-weak-pointer) :alloc-trans %make-weak-pointer)
(value :ref-trans %weak-pointer-value :ref-known (flushable) (value #|:ref-trans %weak-pointer-value :ref-known (flushable)|#
:init :arg) :init :arg)
;; 64-bit uses spare header bytes to store the 'next' link ;; 64-bit uses spare header bytes to store the 'next' link
#-64-bit (next :c-type "struct weak_pointer *")) #-64-bit (next :c-type "struct weak_pointer *"))
@ -483,7 +483,11 @@ during backtrace.
(defconstant-eqx +thread-header-slot-names+ (defconstant-eqx +thread-header-slot-names+
`#(#+x86-64 `#(#+x86-64
,@'(t-nil-constants ,@'(t-nil-constants
stepping
alien-linkage-table-base alien-linkage-table-base
;allocptr16 ; for sml#gc
;allocptr32
;allocptr64
msan-xor-constant msan-xor-constant
;; The following slot's existence must NOT be conditional on #+msan ;; The following slot's existence must NOT be conditional on #+msan
msan-param-tls) ; = &__msan_param_tls msan-param-tls) ; = &__msan_param_tls
@ -525,7 +529,8 @@ during backtrace.
;; of a symbol is initialized to zero ;; of a symbol is initialized to zero
(no-tls-value-marker) (no-tls-value-marker)
(stepping) #-x86-64 (stepping)
(gc-phase :c-type "_Atomic(unsigned int)")
;; Keep this first bunch of slots from binding-stack-pointer through alloc-region ;; Keep this first bunch of slots from binding-stack-pointer through alloc-region
;; near the beginning of the structure so that x86[-64] assembly code ;; near the beginning of the structure so that x86[-64] assembly code
@ -546,13 +551,36 @@ during backtrace.
:c-type "pa_bits_t") :c-type "pa_bits_t")
(alien-stack-pointer :c-type "lispobj *" :pointer t (alien-stack-pointer :c-type "lispobj *" :pointer t
:special *alien-stack-pointer*) :special *alien-stack-pointer*)
(ap4 :c-type "struct alloc_ptr" :length 4)
(ap5 :c-type "struct alloc_ptr" :length 4)
(ap6 :c-type "struct alloc_ptr" :length 4)
(ap7 :c-type "struct alloc_ptr" :length 4)
(ap8 :c-type "struct alloc_ptr" :length 4)
(ap9 :c-type "struct alloc_ptr" :length 4)
(ap10 :c-type "struct alloc_ptr" :length 4)
(ap11 :c-type "struct alloc_ptr" :length 4)
(ap12 :c-type "struct alloc_ptr" :length 4)
;; Thread-local allocation buffers
;(boxed-tlab :c-type "struct alloc_region" :length 3)
;(cons-ap :c-type "struct alloc_ptr" :length 4)
;; Deterministic consing profile recording area. ;; Deterministic consing profile recording area.
(profile-data :c-type "uword_t *" :pointer t) (profile-data :c-type "uword_t *" :pointer t)
;; Thread-local allocation buffers ;; END of slots to keep near the beginning.
(boxed-tlab :c-type "struct alloc_region" :length 3)
;; allocation pointers by block size
;; todo: allocptr48
; (allocptr128)
; (allocptr256)
; (allocptr512)
; (allocptr1024)
; (allocptr2048)
; (allocptr4096)
(cons-tlab :c-type "struct alloc_region" :length 3) (cons-tlab :c-type "struct alloc_region" :length 3)
(mixed-tlab :c-type "struct alloc_region" :length 3) (mixed-tlab :c-type "struct alloc_region" :length 3)
;; END of slots to keep near the beginning.
;; This is the original address at which the memory was allocated, ;; This is the original address at which the memory was allocated,
;; which may have different alignment then what we prefer to use. ;; which may have different alignment then what we prefer to use.
@ -571,7 +599,9 @@ during backtrace.
(os-thread :c-type #+(or win32 (not sb-thread)) "lispobj" ; actually is HANDLE (os-thread :c-type #+(or win32 (not sb-thread)) "lispobj" ; actually is HANDLE
#-(or win32 (not sb-thread)) "pthread_t") #-(or win32 (not sb-thread)) "pthread_t")
(os-kernel-tid) ; the kernel's thread identifier, 32 bits on linux (os-kernel-tid) ; the kernel's thread identifier, 32 bits on linux
;; a small integer identifier starting from 1 for the first thread.
;; These are never recycled (but could wraparound in theory)
(serialno)
;; These aren't accessed (much) from Lisp, so don't really care ;; These aren't accessed (much) from Lisp, so don't really care
;; if it takes a 4-byte displacement. ;; if it takes a 4-byte displacement.
(alien-stack-start :c-type "lispobj *" :pointer t) (alien-stack-start :c-type "lispobj *" :pointer t)
@ -619,6 +649,13 @@ during backtrace.
(symbol-tlab :c-type "struct alloc_region" :length 3) (symbol-tlab :c-type "struct alloc_region" :length 3)
(sys-mixed-tlab :c-type "struct alloc_region" :length 3) (sys-mixed-tlab :c-type "struct alloc_region" :length 3)
(sys-cons-tlab :c-type "struct alloc_region" :length 3) (sys-cons-tlab :c-type "struct alloc_region" :length 3)
;; When the allocator fallback routine calls into C, it saves the stack pointer
;; in this slot, so that we don't need to scan everything above the current stack
;; pointer as it exists upon entry to C, if GC decides to run at that time.
;; This avoids scanning the approximately 100 words comprising the floating-point save
;; area. Because allocation is pseudo-atomic (i.e. nonreentrant), there is no chance
;; that two different allocation requests try to use this slot.
(stack-root-scan-start)
;; allocation instrumenting ;; allocation instrumenting
(tot-bytes-alloc-boxed) (tot-bytes-alloc-boxed)
(tot-bytes-alloc-unboxed) (tot-bytes-alloc-unboxed)
@ -632,6 +669,12 @@ during backtrace.
:length #.(+ (* 2 n-histogram-bins-large) :length #.(+ (* 2 n-histogram-bins-large)
n-histogram-bins-small)) n-histogram-bins-small))
(ct-new-objects)
(ct-stack-obj-stores)
(ct-heap-obj-stores)
(ct-store-barriers)
(ct-spinlock-yields)
;; The *current-thread* MUST be the last slot in the C thread structure. ;; The *current-thread* MUST be the last slot in the C thread structure.
;; It it the only slot that needs to be noticed by the garbage collector. ;; It it the only slot that needs to be noticed by the garbage collector.
(lisp-thread :pointer t :special sb-thread:*current-thread*)) (lisp-thread :pointer t :special sb-thread:*current-thread*))
@ -764,3 +807,38 @@ during backtrace.
(+ static-space-objects-offset (+ static-space-objects-offset
(* (length +static-symbols+) (ash (align-up symbol-size 2) word-shift)) (* (length +static-symbols+) (ash (align-up symbol-size 2) word-shift))
instance-pointer-lowtag)) instance-pointer-lowtag))
;(sb-ext:define-load-time-global *use-smlgc* 0)
;(sb-ext:define-load-time-global *testroot* 0)
;(declaim (fixnum *use-smlgc*))
;(defun type-to-bit (type)
; (cond ((or (typep type 'sb-kernel:defstruct-description)
; (eql type instance-widetag))
; 0)
; ((and (numberp type) (>= type 128)) 1)
; (t
; (let ((pos
; (position type
; `(,bignum-widetag
; ,list-pointer-lowtag
; ,funcallable-instance-widetag
; ,double-float-widetag ,ratio-widetag
; ,weak-pointer-widetag
; ,complex-single-float-widetag
; ,complex-double-float-widetag
; ,complex-widetag
; ,sap-widetag ,symbol-widetag
; ,closure-widetag
; ,code-header-widetag
; ,simple-vector-widetag
; ,value-cell-widetag
; ,simd-pack-widetag ,simd-pack-256-widetag ,fdefn-widetag
; vector
; :cons1 :cons2 :cons3+ :&rest :make-list :unknown-type :variable
; ))))
; (if pos (+ pos 2))))))
;
;(defun use-smlgc (&rest types)
; (setf sb-vm::*use-smlgc*
; (reduce #'+ (mapcar (lambda (x) (ash 1 (type-to-bit x)))
; types))))

View file

@ -163,6 +163,9 @@
'(sub-gc '(sub-gc
sb-kernel::post-gc sb-kernel::post-gc
internal-error internal-error
largeobj-hs-insert
largeobj-hs-maybe-rehash
release-malloc-segments
sb-kernel::control-stack-exhausted-error sb-kernel::control-stack-exhausted-error
sb-kernel::binding-stack-exhausted-error sb-kernel::binding-stack-exhausted-error
sb-kernel::alien-stack-exhausted-error sb-kernel::alien-stack-exhausted-error
@ -213,6 +216,9 @@
;; never the symbol-value slot ;; never the symbol-value slot
#-sb-thread ,@(mapcar (lambda (x) (car (ensure-list x))) #-sb-thread ,@(mapcar (lambda (x) (car (ensure-list x)))
per-thread-c-interface-symbols) per-thread-c-interface-symbols)
bitmap-heap-base
bitmap-heap-size
;; NLX variables are thread slots on x86-64 and RISC-V. A static sym is needed ;; NLX variables are thread slots on x86-64 and RISC-V. A static sym is needed
;; for arm64, ppc, and x86 because we haven't implemented TLS index fixups, ;; for arm64, ppc, and x86 because we haven't implemented TLS index fixups,
;; so must lookup the TLS index given the symbol. ;; so must lookup the TLS index given the symbol.
@ -413,5 +419,15 @@
(defconstant double-float-digits 53) (defconstant double-float-digits 53)
) )
;;; see smlsharp.h
(defconstant gc-phase-async 1)
(defconstant gc-phase-sync1 3)
(defconstant gc-phase-sync2 5)
(defconstant gc-phase-mark 7)
(defconstant smlgc-blocksize-max 4096)
;; an "mseg" wraps a malloc()'ed memory block. In addition to any malloc
;; overhead there is some GC overhead on top of that.
(defconstant smlgc-mseg-overhead-bytes 32)
(push '("SB-VM" +c-callable-fdefns+ +common-static-symbols+) (push '("SB-VM" +c-callable-fdefns+ +common-static-symbols+)
*!removable-symbols*) *!removable-symbols*)

View file

@ -94,6 +94,13 @@
(+ (static-fdefn-offset name) (+ (static-fdefn-offset name)
(- other-pointer-lowtag) (- other-pointer-lowtag)
(* fdefn-raw-addr-slot n-word-bytes))) (* fdefn-raw-addr-slot n-word-bytes)))
(export 'static-data-collection-vector)
(defun static-data-collection-vector ()
(+ nil-value
(static-fdefn-offset (elt +static-fdefns+
(1- (length +static-fdefns+))))
(ash fdefn-size word-shift)))
;;;; interfaces to IR2 conversion ;;;; interfaces to IR2 conversion
@ -318,6 +325,25 @@
(return-from stack-consed-p t))))) (return-from stack-consed-p t)))))
nil) nil)
(defun slot-type-requires-gcbarrier (obj-ref index)
#-compact-instance-header
(when (eql index 0) ; layout is index 0 (physical word 1)
(return-from slot-type-requires-gcbarrier t))
(let ((obj-type (tn-ref-type obj-ref)))
(when (structure-classoid-p obj-type)
(when (and (csubtypep obj-type (specifier-type 'sb-lockless::list-node))
(eq index instance-data-start))
(return-from slot-type-requires-gcbarrier :untagged))
(let* ((dd (layout-dd (classoid-layout obj-type)))
(slot (find index (dd-slots dd) :key #'dsd-index)))
(unless slot ; constant index may access slots beyond the defined slots
(return-from slot-type-requires-gcbarrier t))
(let ((slot-type (specifier-type (dsd-type slot))))
(when (csubtypep slot-type (specifier-type
'(or boolean fixnum character #+64-bit single-float)))
(return-from slot-type-requires-gcbarrier nil))))))
t)
;;; Just gathering some data to see where we can improve ;;; Just gathering some data to see where we can improve
(define-load-time-global *store-barriers-potentially-emitted* 0) (define-load-time-global *store-barriers-potentially-emitted* 0)
(define-load-time-global *store-barriers-emitted* 0) (define-load-time-global *store-barriers-emitted* 0)

View file

@ -318,7 +318,7 @@
(defknown make-array-header* (&rest t) array (flushable movable)) (defknown make-array-header* (&rest t) array (flushable movable))
(defknown make-weak-pointer (t) weak-pointer (defknown (make-weak-pointer sb-vm::%make-weak-pointer) (t) weak-pointer
(flushable)) (flushable))
;; This used to have a :derive-type but it can't now. Even though a weak vector ;; This used to have a :derive-type but it can't now. Even though a weak vector
@ -681,6 +681,9 @@
;;; The index should be strictly negative and a multiple of 4. ;;; The index should be strictly negative and a multiple of 4.
(defknown code-trailer-ref (code-component fixnum) (unsigned-byte 32) (defknown code-trailer-ref (code-component fixnum) (unsigned-byte 32)
(flushable #-(or sparc ppc64) always-translatable)) (flushable #-(or sparc ppc64) always-translatable))
;; knownfun and vop not needed post-build, so name using #\! convention.
;; Takes header word and malloc-segment pointer
(defknown sb-vm::!manage-large-codeblob (sb-vm:word system-area-pointer) code-component ())
(defknown %fun-pointer-widetag (function) (member . #.sb-vm::+function-widetags+) (defknown %fun-pointer-widetag (function) (member . #.sb-vm::+function-widetags+)
(flushable)) (flushable))

View file

@ -412,7 +412,7 @@
t) t)
;;; MAKE-LIST optimizations ;;; MAKE-LIST optimizations
#+x86-64 #+nil ; x86-64
(progn (progn
(defoptimizer (%make-list stack-allocate-result) ((length element) node) (defoptimizer (%make-list stack-allocate-result) ((length element) node)
t) t)

View file

@ -83,7 +83,9 @@
(defun proper-list (form) (defun proper-list (form)
(if (proper-list-p form) (if (proper-list-p form)
form form
(compiler-error "~@<~S is not a proper list.~@:>" form))) (progn
(write-string "CRAPOLA: ")(write form)(terpri)
(compiler-error "~@<~S is not a proper list.~@:>" form))))
;;; *CURRENT-COMPONENT* is the COMPONENT structure which we link ;;; *CURRENT-COMPONENT* is the COMPONENT structure which we link
;;; blocks into as we generate them. This just serves to glue the ;;; blocks into as we generate them. This just serves to glue the

View file

@ -1510,7 +1510,7 @@
(already-done (eq (fourth (vop-codegen-info vop)) :pseudo-atomic))) (already-done (eq (fourth (vop-codegen-info vop)) :pseudo-atomic)))
(unless (or dx already-done) (unless (or dx already-done)
(process-closure-inits vop)))) (process-closure-inits vop))))
((fixed-alloc var-alloc) ((fixed-alloc #|var-alloc|#)
(let ((last (car (last (vop-codegen-info vop))))) (let ((last (car (last (vop-codegen-info vop)))))
(when (vop-p last) (when (vop-p last)
(process-general-inits vop last))))) (process-general-inits vop last)))))

View file

@ -858,7 +858,7 @@
,@(loop for i below (car (array-type-dimensions type)) ,@(loop for i below (car (array-type-dimensions type))
collect `(setf (aref seq ,i) item)) collect `(setf (aref seq ,i) item))
seq)) seq))
#+x86-64 #+nil ; x86-64 ; disabled for concurrent GC
((and (type= element-ctype *universal-type*) ((and (type= element-ctype *universal-type*)
(csubtypep (lvar-type seq) (specifier-type '(simple-array * (*)))) (csubtypep (lvar-type seq) (specifier-type '(simple-array * (*))))
;; FIXME: why can't this work with arbitrary START and END? ;; FIXME: why can't this work with arbitrary START and END?
@ -928,7 +928,7 @@
;; Force bounds-checks to 0 even if local policy had it >0. ;; Force bounds-checks to 0 even if local policy had it >0.
(declare (optimize (safety 0) (speed 3) (declare (optimize (safety 0) (speed 3)
(insert-array-bounds-checks 0))) (insert-array-bounds-checks 0)))
,(cond #+x86-64 ,(cond #+nil ; #+x86-64
((type= element-ctype *universal-type*) ((type= element-ctype *universal-type*)
'(vector-fill/t data item start end)) '(vector-fill/t data item start end))
(t (t

View file

@ -618,6 +618,12 @@
(setf (dstate-next-offs dstate) (dstate-cur-offs dstate)) (setf (dstate-next-offs dstate) (dstate-cur-offs dstate))
(when (and stream
(eql (car (seg-pseudo-atomic-locations segment))
(dstate-next-offs dstate)))
(format stream "; GC barrier:")
(pop (seg-pseudo-atomic-locations segment)))
(call-offs-hooks t stream dstate) (call-offs-hooks t stream dstate)
(unless (or prefix-p (null stream)) (unless (or prefix-p (null stream))
(print-current-address stream dstate)) (print-current-address stream dstate))
@ -1506,6 +1512,13 @@
:code code :code code
:initial-offset initial-offset ; an offset into CODE :initial-offset initial-offset ; an offset into CODE
:debug-fun debug-fun))) :debug-fun debug-fun)))
(when code
(let (locs)
(dovector (x (sb-impl::code-pseudo-atomic-locations code))
(let ((loc (code-insts-offs-to-segment-offs x segment)))
(when (plusp loc)
(push loc locs))))
(setf (seg-pseudo-atomic-locations segment) (nreverse locs))))
(add-debugging-hooks segment debug-fun source-form-cache) (add-debugging-hooks segment debug-fun source-form-cache)
(when code (when code
(add-fun-header-hooks segment)) (add-fun-header-hooks segment))

View file

@ -42,6 +42,7 @@
(code nil :type (or null code-component)) (code nil :type (or null code-component))
;; list of function and fdefn constants extracted from code header ;; list of function and fdefn constants extracted from code header
(code-callables :?) (code-callables :?)
(pseudo-atomic-locations nil)
;; the byte offset beyond CODE-INSTRUCTIONS of CODE which ;; the byte offset beyond CODE-INSTRUCTIONS of CODE which
;; corresponds to offset 0 in this segment ;; corresponds to offset 0 in this segment
(initial-offset 0 :type index) (initial-offset 0 :type index)

View file

@ -17,6 +17,22 @@
;;; from the C alloc() function by way of the alloc-tramp ;;; from the C alloc() function by way of the alloc-tramp
;;; assembly routine. ;;; assembly routine.
#+nil
(defun dontuse-test-should-use-smlgc (type)
(let ((bit (type-to-bit type)))
(unless bit
(error "No bit for ~x~%" type))
(inst test :dword (static-symbol-value-ea '*use-smlgc*) (ash 1 (1+ bit)))))
(defmacro jump-if-not-bitmap-alloc (label)
`(progn
(inst cmp :dword (thread-slot-ea (1+ thread-ap4-slot)) -1)
(inst jmp :e ,label)))
(defmacro jump-if-bitmap-alloc (label)
`(progn
(inst cmp :dword (thread-slot-ea (1+ thread-ap4-slot)) -1)
(inst jmp :ne ,label)))
(defun tagify (result base lowtag) (defun tagify (result base lowtag)
(if (eql lowtag 0) (if (eql lowtag 0)
(inst mov result base) (inst mov result base)
@ -74,6 +90,7 @@
;; so we may as well take advantage of this fact to load the temp reg ;; so we may as well take advantage of this fact to load the temp reg
;; here, if provided, rather than spewing more #+gs-seg tests around. ;; here, if provided, rather than spewing more #+gs-seg tests around.
#+gs-seg (when thread-temp (inst rdgsbase thread-temp)) #+gs-seg (when thread-temp (inst rdgsbase thread-temp))
#+smlgc-telemetry (inst inc :qword (thread-slot-ea thread-ct-new-objects-slot)) ; TELEMETRY
(when (member :allocation-size-histogram sb-xc:*features*) (when (member :allocation-size-histogram sb-xc:*features*)
(let ((use-size-temp (not (typep size '(or (signed-byte 32) tn))))) (let ((use-size-temp (not (typep size '(or (signed-byte 32) tn)))))
;; Sum up the sizes of boxed vs unboxed allocations. ;; Sum up the sizes of boxed vs unboxed allocations.
@ -184,6 +201,213 @@
;;; with the CONS-TYPE in our type-algebraic sense. Mostly just informs ;;; with the CONS-TYPE in our type-algebraic sense. Mostly just informs
;;; the allocator to use cons_tlab. ;;; the allocator to use cons_tlab.
(defconstant +cons-primtype+ list-pointer-lowtag) (defconstant +cons-primtype+ list-pointer-lowtag)
;(defvar *cons-n-histo* #x501009B0)
;;; For async GC based on SML#
;;; See 'struct alloc_ptr' in heap_concurrent
;;; and 'struct sml_bitptr' in smlsharp.h
(defmacro with-bitmap-ap ((allocptr &optional thread-slot) &body body)
(cond ((eq allocptr :thread)
(aver thread-slot)
;; DISP has to remain as an expression so that the returned form
;; is toplevelish.
(let ((disp `(ash ,thread-slot word-shift)))
`(macrolet ((freebit.ptr () '(ea ,disp thread-tn))
(freebit.mask () '(ea (+ ,disp 8) thread-tn))
;; the most-significant byte of the mask for CONS special cases
(freebit.mask-byte3 () '(ea (+ ,disp 11) thread-tn))
(freeptr () '(ea (+ ,disp 16) thread-tn))
(freeptr-fetch-and-add (result nbytes scratch)
`(progn
;; XADD would do this, but C compilers don't use it
;; except for an atomic fetch-and-add.
;; the Agner Fog instruction latency tables seem to support that.
(inst mov ,result (freeptr))
(inst lea ,scratch (ea ,nbytes ,result))
(inst mov (freeptr) ,scratch))))
,@body)))
(t
(aver (not thread-slot))
`(macrolet ((freebit.ptr () '(ea 0 ,allocptr))
(freebit.mask () '(ea 8 ,allocptr))
(freeptr () '(ea 16 ,allocptr))
(segment.blocksize () '(ea 24 ,allocptr)))
,@body))))
(defconstant unused-word-pattern #xffffffffdeadbeef)
(defun assert-word-unused (ea &aux (ok (gen-label)))
(declare (ignore ea ok))
#+nil
(when t
(inst cmp :qword ea unused-word-pattern)
(inst jmp :eq OK)
(inst break halt-trap)
(emit-label OK)))
(defun check-alivep (obj lowtag &aux (ok (gen-label)))
(declare (ignore obj lowtag ok))
#+nil
(unless (constant-tn-p obj)
(inst cmp :qword (ea (- lowtag) obj) unused-word-pattern)
(inst jmp :ne OK)
(inst break halt-trap)
(emit-label OK)))
(defmacro count-alloc (name)
(declare (ignorable name))
#+nil
(let ((index
(the (not null)
(position name
'(:cons1 :cons1-slow
:cons2 :cons2-slow
:cons3 :cons3-slow
:cons4 :cons4-slow
:cons5 :cons5-slow
:cons6+ :dummy ; always "slow"
:barrier-store :barrier-cmpxchg
)))))
;; The first 16 elements are taken up by a histogram of lengths
;; of &REST args that we see - count and count-slow for each length
;; 1 through 15 and a catch-all bin.
;; But to make things more confusing, this is a vector of SB-VM:WORD
;; though &REST lists treat it as a vector of UNSIGNED-BYTE-32
`(inst inc :lock :qword
(ea ,(+ (cons-stats-v) (ash 16 word-shift) (* index 8))))))
(with-bitmap-ap (:thread thread-ap4-slot)
(defun bitmap-inline-alloc-list (num-conses alloc-tn temp done-label fallback)
#+nil
(inst inc :lock :qword
(ea (+ (cons-stats-v) (ash 16 word-shift)
;; Use pairs of elements for consN, consN-slow. See above
(* (1- num-conses) 16))))
(let ((bmwordptr alloc-tn)
(mask temp)
(unavailable (gen-label)))
;; >1 cons has a pre-test for UNAVAILABLE in case the current mask + NUM-CONSES
;; would exceed the current bitmap word
(ecase num-conses
(1
(inst mov bmwordptr (freebit.ptr))
(inst mov :dword mask (freebit.mask))) ; MASK has the bit we want next
(2
(inst mov bmwordptr (freebit.ptr))
(inst mov :dword mask (freebit.mask))
(inst test :dword mask mask)
;; Fail if mask bit 31 is 1. We'd need to test bit 0 of the next bitmap word too,
;; so just use the slow path. Fast path occurs 96% of the time if bitmap word = 0.
(inst jmp :s UNAVAILABLE) ; MASK*3 will overflow a :DWORD is sign bit is on
(inst lea :dword mask (ea mask mask 2))) ; MASK = MASK * 3 (setting 2 bits)
(3
(inst mov :dword bmwordptr (freebit.mask))
;; Fail if bit 30 or 31 is 1. Fast path occurs 93% of the time if bitmap word = 0.
(inst test :dword bmwordptr #xC0000000)
(inst jmp :nz UNAVAILABLE) ; MASK*7 would overflow :DWORD
;; The highest bit that could be on is bit index 29. The highest affected bit
;; could be index 32 after multiplying by 8. (Thus we need :QWORD operations)
(inst lea mask (ea nil bmwordptr 8))
(inst sub mask bmwordptr)) ; Set 3 bits by computing mask*7 as (8 x mask) - mask
(4
(inst mov :dword mask (freebit.mask))
;; Fail if bit 29, 30 or 31 is 1. Fast path occurs 90% of the time if bitmap word = 0.
(inst test :dword mask #xE0000000)
(inst jmp :nz UNAVAILABLE) ; MASK*15 would overflow :DWORD
;; The highest bit that could be on is bit index 28
(inst mov :dword bmwordptr mask)
(inst shl mask 4)
(inst sub mask bmwordptr)) ; Set 4 bits by computing mask*15 as (16 x mask) - mask
(5
(inst mov :dword mask (freebit.mask))
(inst test :dword mask #xF0000000)
(inst jmp :nz UNAVAILABLE) ; MASK*31 would overflow :DWORD
;; The highest bit that could be on is bit index 27
(inst mov :dword bmwordptr mask)
(inst shl mask 5)
(inst sub mask bmwordptr))) ; Set 5 bits by computing mask*31 as (32 x mask) - mask
;;
(when (> num-conses 2) (inst mov bmwordptr (freebit.ptr)))
(inst test :dword (ea bmwordptr) mask)
(inst jmp :nz UNAVAILABLE)
;; advance the bit
(case num-conses
(1
(inst rol :dword mask 1)
(inst mov :dword (freebit.mask) mask)) ; writeback
(t ; DO NOT write back from the mask register, as it contains > 1 set bit
(inst rol :dword (freebit.mask) num-conses)))
;; I tested whether branchless logic for incrementing freebit.ptr is preferable
;; to jumping over an add if the carry is clear, and it definitely is,
;; because the branch is not very predictable.
(inst sbb :dword temp temp) ; broadcast the carry into low 32 bits
(inst and :dword temp 4) ; = 0 or 4 depending on CF prior to SBB
(inst add :qword (freebit.ptr) temp)
#|
;; Jmp over add is worse
(inst jmp :nc done-label)
(inst add :qword (freebit.ptr) 4)
|#
(freeptr-fetch-and-add alloc-tn (* num-conses 16) temp)
(dotimes (i num-conses) (assert-word-unused (ea (ash i word-shift) alloc-tn)))
(inst jmp DONE-LABEL) ; success
(emit-label unavailable)
(inst cmp :dword (if (= num-conses 1) mask (freebit.mask)) -1)
(inst jmp :ne FALLBACK) ; call into C for help with bitmap allocation
;; falllthrough goes to the gencgc allocator
)))
(defun bitmap-call-alloc-list (things alloc-tn node num-conses star)
(macrolet ((pop-arg () `(prog1 (tn-ref-tn things) (setf things (tn-ref-across things)))))
(loop (list-ctor-push-elt (pop-arg) alloc-tn)
(unless things (return))))
(cond ((<= num-conses 5)
(let ((fallback
(aref (if star
#(bitmap-list*4-fallback bitmap-list*5-fallback bitmap-list*6-fallback)
#(bitmap-list3-fallback bitmap-list4-fallback bitmap-list5-fallback))
(- num-conses 3))))
(invoke-asm-routine 'call fallback node)))
(t
(inst lea rax-tn (ea (* (+ num-conses (if star 0 -1)) n-word-bytes) rsp-tn))
(inst mov rcx-tn num-conses)
(invoke-asm-routine 'call (if star 'bitmap-listify* 'bitmap-listify) node)
(inst add rsp-tn (* (+ num-conses (if star 1 0)) n-word-bytes))))
(move alloc-tn rax-tn))
(defun bitmap-alloc (nbytes result-tn temps node done-label)
(aver (<= 1 (length temps) 2))
(let* ((log2size (the (integer 4 12) (integer-length (1- nbytes))))
;; each alloc_ptr consumes 4 words consecutively from the AP4 slot
(thread-slot (+ thread-ap4-slot (* 4 (- log2size 4))))
(fallback (gen-label))
(unavailable (gen-label))
(temp (car temps)))
(with-bitmap-ap (:thread thread-slot)
(inst mov temp (freebit.ptr))
(inst mov :dword result-tn (freebit.mask))
(inst test :dword (ea temp) result-tn) ; result-tn holds the desired bit
(inst jmp :nz UNAVAILABLE) ; block is not available
(inst rol :dword (freebit.mask) 1)
(inst sbb :dword result-tn result-tn) ; broadcast the carry into low 32 bits
(inst and :dword result-tn 4) ; = 0 or 4 depending on CF prior to SBB
(inst add (freebit.ptr) result-tn)
(freeptr-fetch-and-add result-tn (ash 1 log2size) temp)
(inst jmp DONE-LABEL)
(emit-label UNAVAILABLE)
;; if mask is all 1s then we are NOT using the bitmap allocator
;; if mask is NOT all 1s then we ARE using the bitmap allocator
(inst cmp :dword result-tn -1) ; freebit.mask is in RESULT-TN
(inst jmp :ne fallback))
;; fallthrough
(assemble (:elsewhere)
(emit-label fallback)
(case log2size
(4 (call-reg-specific-asm-routine node "" result-tn "-ALLOC16-FALLBACK"))
(5 (call-reg-specific-asm-routine node "" result-tn "-ALLOC32-FALLBACK"))
(6 (call-reg-specific-asm-routine node "" result-tn "-ALLOC64-FALLBACK"))
;; other sizes use a generalized fallback
(t (inst lea result-tn (thread-slot-ea thread-slot))
(call-reg-specific-asm-routine node "" result-tn "-ALLOC-FALLBACK")))
(inst jmp DONE-LABEL))))
(define-vop (sb-c::end-pseudo-atomic) (define-vop (sb-c::end-pseudo-atomic)
(:generator 1 (emit-end-pseudo-atomic))) (:generator 1 (emit-end-pseudo-atomic)))
@ -198,10 +422,12 @@
;;; 1. what to allocate: type, size, lowtag describe the object ;;; 1. what to allocate: type, size, lowtag describe the object
;;; 2. where to put the result ;;; 2. where to put the result
;;; 3. node (for determining immobile-space-p) and a scratch register or two ;;; 3. node (for determining immobile-space-p) and a scratch register or two
(defun allocation (type size lowtag alloc-tn node temp thread-temp (defun gengc-alloc (type size lowtag alloc-tn node temp thread-temp
&key overflow &key overflow
&aux (systemp (system-tlab-p type node))) &aux (systemp (system-tlab-p type node)))
(declare (ignorable thread-temp)) (declare (ignorable thread-temp))
(when overflow
(aver (eql type +cons-primtype+)))
(flet ((fallback (size) (flet ((fallback (size)
;; Call an allocator trampoline and get the result in the proper register. ;; Call an allocator trampoline and get the result in the proper register.
;; There are 2 choices of trampoline to invoke alloc() or alloc_list() ;; There are 2 choices of trampoline to invoke alloc() or alloc_list()
@ -295,8 +521,32 @@
(when (and (/= lowtag 0) (not temp) (not (tn-p size))) (when (and (/= lowtag 0) (not temp) (not (tn-p size)))
(inst or :byte alloc-tn lowtag)) (inst or :byte alloc-tn lowtag))
(inst jmp DONE)))))))) (inst jmp DONE))))))))
t) t)
(defun allocation (type size lowtag alloc-tn node temps thread-temp
&key (try-bitmap-alloc t)
&aux (saved-reg
(when (and try-bitmap-alloc (not temps))
(if (location= alloc-tn rax-tn) rcx-tn rax-tn)))
(temps (ensure-list temps)))
;; Conses and variable-sized or large allocations are
;; handled outside of this function if using SML# allocator
(when saved-reg
(inst push saved-reg))
(assemble ()
(when try-bitmap-alloc
(bitmap-alloc size alloc-tn (or temps (list saved-reg)) node DONE))
;; fallthrough
(gengc-alloc type size lowtag alloc-tn node (or (first temps) saved-reg)
thread-temp)
DONE
;; FIXME: there's redundancy here, because a single OR instruction
;; should be part of both code flows.
(unless (= lowtag 0) (inst or :byte alloc-tn lowtag)))
(when saved-reg
(inst pop saved-reg)))
;;; Allocate an other-pointer object of fixed NWORDS with a single-word ;;; Allocate an other-pointer object of fixed NWORDS with a single-word
;;; header having the specified WIDETAG value. The result is placed in ;;; header having the specified WIDETAG value. The result is placed in
;;; RESULT-TN. NWORDS counts the header word. ;;; RESULT-TN. NWORDS counts the header word.
@ -307,17 +557,19 @@
(declare (dynamic-extent init)) (declare (dynamic-extent init))
#+bignum-assertions #+bignum-assertions
(when (= widetag bignum-widetag) (setq bytes (* bytes 2))) ; use 2x the space (when (= widetag bignum-widetag) (setq bytes (* bytes 2))) ; use 2x the space
(aver (<= bytes smlgc-blocksize-max))
(instrument-alloc widetag bytes node (cons result-tn (ensure-list alloc-temps)) thread-temp) (instrument-alloc widetag bytes node (cons result-tn (ensure-list alloc-temps)) thread-temp)
(let ((header (compute-object-header nwords widetag)) (let ((header (compute-object-header nwords widetag)))
(alloc-temp (if (listp alloc-temps) (car alloc-temps) alloc-temps))) ;; if the object is cons-sized (2 words) then the page fill byte is 0xff
;; so we have to write the entire header word. If more than 2 words, the fill byte is 0.
(pseudo-atomic () (pseudo-atomic ()
(cond (alloc-temp (cond (alloc-temps
(allocation widetag bytes 0 result-tn node alloc-temp thread-temp) (allocation widetag bytes 0 result-tn node alloc-temps thread-temp)
(storew* header result-tn 0 0 t) (storew* header result-tn 0 0 (> nwords 2))
(inst or :byte result-tn other-pointer-lowtag)) (inst or :byte result-tn other-pointer-lowtag))
(t (t
(allocation widetag bytes other-pointer-lowtag result-tn node nil thread-temp) (allocation widetag bytes other-pointer-lowtag result-tn node nil thread-temp)
(storew* header result-tn 0 other-pointer-lowtag t))) (storew* header result-tn 0 other-pointer-lowtag (> nwords 2))))
(when init (when init
(funcall init))))) (funcall init)))))
@ -365,6 +617,8 @@
(:args (car :scs (any-reg descriptor-reg constant immediate control-stack)) (:args (car :scs (any-reg descriptor-reg constant immediate control-stack))
(cdr :scs (any-reg descriptor-reg constant immediate control-stack))) (cdr :scs (any-reg descriptor-reg constant immediate control-stack)))
(:temporary (:sc unsigned-reg :to (:result 0) :target result) alloc) (:temporary (:sc unsigned-reg :to (:result 0) :target result) alloc)
;; TEMP does not have to be wired, because the fallback does not destroy
;; any registers
(:temporary (:sc unsigned-reg :to (:result 0) (:temporary (:sc unsigned-reg :to (:result 0)
:unused-if (node-stack-allocate-p (sb-c::vop-node vop))) :unused-if (node-stack-allocate-p (sb-c::vop-node vop)))
temp) temp)
@ -393,16 +647,25 @@
(inst lea result (ea list-pointer-lowtag rsp-tn))) (inst lea result (ea list-pointer-lowtag rsp-tn)))
(t (t
(let ((nbytes (* cons-size n-word-bytes)) (let ((nbytes (* cons-size n-word-bytes))
(prev-constant temp)) ;; a non-eq initial value (prev-constant temp) ;; a non-eq initial value
(fallback (gen-label)))
(instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn) (instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn)
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation +cons-primtype+ nbytes 0 alloc node temp thread-tn) (bitmap-inline-alloc-list 1 alloc temp CONTINUE FALLBACK)
(assemble (:elsewhere)
(emit-label fallback)
(call-reg-specific-asm-routine node "BITMAP-CONS-TO-" alloc "-FALLBACK")
(inst jmp CONTINUE))
;; pointer bump allocation
(gengc-alloc +cons-primtype+ nbytes 0 alloc node temp thread-tn)
CONTINUE
(store-slot car alloc cons-car-slot 0) (store-slot car alloc cons-car-slot 0)
(store-slot cdr alloc cons-cdr-slot 0) (store-slot cdr alloc cons-cdr-slot 0)
(if (location= alloc result) (if (location= alloc result)
(inst or :byte alloc list-pointer-lowtag) (inst or :byte alloc list-pointer-lowtag)
(inst lea result (ea list-pointer-lowtag alloc))))))))) (inst lea result (ea list-pointer-lowtag alloc)))))))))
#+nil
(define-vop (acons) (define-vop (acons)
(:args (key :scs (any-reg descriptor-reg constant immediate control-stack)) (:args (key :scs (any-reg descriptor-reg constant immediate control-stack))
(val :scs (any-reg descriptor-reg constant immediate control-stack)) (val :scs (any-reg descriptor-reg constant immediate control-stack))
@ -419,7 +682,7 @@
(prev-constant temp)) (prev-constant temp))
(instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn) (instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn)
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation +cons-primtype+ nbytes 0 alloc node temp thread-tn) (gengc-alloc +cons-primtype+ nbytes 0 alloc node temp thread-tn)
(store-slot tail alloc cons-cdr-slot 0) (store-slot tail alloc cons-cdr-slot 0)
(inst lea temp (ea (+ 16 list-pointer-lowtag) alloc)) (inst lea temp (ea (+ 16 list-pointer-lowtag) alloc))
(store-slot temp alloc cons-car-slot 0) (store-slot temp alloc cons-car-slot 0)
@ -440,7 +703,9 @@
(cadr :scs (any-reg descriptor-reg constant immediate control-stack)) (cadr :scs (any-reg descriptor-reg constant immediate control-stack))
(cddr :scs (any-reg descriptor-reg constant immediate control-stack))) (cddr :scs (any-reg descriptor-reg constant immediate control-stack)))
(:temporary (:sc unsigned-reg :to (:result 0) :target result) alloc) (:temporary (:sc unsigned-reg :to (:result 0) :target result) alloc)
(:temporary (:sc unsigned-reg :to (:result 0) ;; TEMP has to be wired because the fallback returns a value in RAX
;; (can we return it on the stack instead?)
(:temporary (:sc unsigned-reg :to (:result 0) :offset rax-offset
:unused-if (node-stack-allocate-p (sb-c::vop-node vop))) :unused-if (node-stack-allocate-p (sb-c::vop-node vop)))
temp) temp)
(:results (result :scs (descriptor-reg))) (:results (result :scs (descriptor-reg)))
@ -458,11 +723,26 @@
(list-ctor-push-elt car alloc) (list-ctor-push-elt car alloc)
(inst lea result (ea list-pointer-lowtag rsp-tn))) (inst lea result (ea list-pointer-lowtag rsp-tn)))
(t (t
(let ((nbytes (* cons-size 2 n-word-bytes)) (let ((nbytes (* 2 cons-size n-word-bytes))
(prev-constant temp)) (prev-constant temp)
(fallback (gen-label)))
(instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn) (instrument-alloc +cons-primtype+ nbytes node (list temp alloc) thread-tn)
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation +cons-primtype+ nbytes 0 alloc node temp thread-tn) (bitmap-inline-alloc-list 2 alloc temp CONTINUE FALLBACK)
(assemble (:elsewhere)
(emit-label fallback)
(list-ctor-push-elt car temp)
(list-ctor-push-elt cadr temp)
(cond ((and (constant-tn-p cddr) (eq (tn-value cddr) nil))
(invoke-asm-routine 'call 'bitmap-list2-fallback node))
(t
(list-ctor-push-elt cddr temp)
(invoke-asm-routine 'call 'bitmap-list*3-fallback node)))
(move result temp)
(inst jmp done))
;; pointer bump allocation
(gengc-alloc +cons-primtype+ nbytes 0 alloc node temp thread-tn)
CONTINUE
(store-slot car alloc cons-car-slot 0) (store-slot car alloc cons-car-slot 0)
(store-slot cadr alloc (+ 2 cons-car-slot) 0) (store-slot cadr alloc (+ 2 cons-car-slot) 0)
(store-slot cddr alloc (+ 2 cons-cdr-slot) 0) (store-slot cddr alloc (+ 2 cons-cdr-slot) 0)
@ -470,11 +750,13 @@
(store-slot temp alloc cons-cdr-slot 0) (store-slot temp alloc cons-cdr-slot 0)
(if (location= alloc result) (if (location= alloc result)
(inst or :byte alloc list-pointer-lowtag) (inst or :byte alloc list-pointer-lowtag)
(inst lea result (ea list-pointer-lowtag alloc))))))))) (inst lea result (ea list-pointer-lowtag alloc)))
DONE))))))
(define-vop (list) (define-vop (list)
(:args (things :more t :scs (descriptor-reg any-reg constant immediate))) (:args (things :more t :scs (descriptor-reg any-reg constant immediate)))
(:temporary (:sc unsigned-reg) ptr temp) (:temporary (:sc unsigned-reg :offset rcx-offset) ptr)
(:temporary (:sc unsigned-reg :offset rax-offset) temp)
(:temporary (:sc unsigned-reg :to (:result 0) :target result) res) (:temporary (:sc unsigned-reg :to (:result 0) :target result) res)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:info star cons-cells) (:info star cons-cells)
@ -482,15 +764,35 @@
(:node-var node) (:node-var node)
(:generator 0 (:generator 0
(aver (>= cons-cells 3)) ; prevent regressions in ir2tran's vop selection (aver (>= cons-cells 3)) ; prevent regressions in ir2tran's vop selection
(let ((stack-allocate-p (node-stack-allocate-p node)) (let* ((stack-allocate-p (node-stack-allocate-p node))
(size (* (pad-data-block cons-size) cons-cells)) (size (* (pad-data-block cons-size) cons-cells))
(prev-constant temp)) (prev-constant temp))
(unless stack-allocate-p (unless stack-allocate-p
(instrument-alloc +cons-primtype+ size node (list ptr temp) thread-tn)) (instrument-alloc +cons-primtype+ size node (list ptr temp) thread-tn))
(pseudo-atomic (:elide-if stack-allocate-p :thread-tn thread-tn) (pseudo-atomic (:elide-if stack-allocate-p :thread-tn thread-tn)
(if stack-allocate-p (cond
(stack-allocation size list-pointer-lowtag res) (stack-allocate-p
(allocation +cons-primtype+ size list-pointer-lowtag res node temp thread-tn)) (stack-allocation size list-pointer-lowtag res))
(t
(let ((linear-alloc (gen-label)) (fallback (gen-label)))
(cond
((> cons-cells 5)
(jump-if-not-bitmap-alloc LINEAR-ALLOC)
;; Don't bother trying to get contiguous bits- success rate is too low
(count-alloc :cons6+)
(bitmap-call-alloc-list things res node cons-cells star)
(inst jmp DONE))
(t
;; emit the fallback first because THINGS gets popped later
(assemble (:elsewhere)
(emit-label fallback)
(bitmap-call-alloc-list things res node cons-cells star)
(inst jmp DONE))
(bitmap-inline-alloc-list cons-cells res temp CONTINUE FALLBACK)))
(emit-label LINEAR-ALLOC)
(gengc-alloc +cons-primtype+ size 0 res node temp thread-tn))))
CONTINUE
(unless stack-allocate-p (inst or :byte res list-pointer-lowtag))
(move ptr res) (move ptr res)
(dotimes (i (1- cons-cells)) (dotimes (i (1- cons-cells))
(store-slot (pop-arg things) ptr) (store-slot (pop-arg things) ptr)
@ -499,17 +801,19 @@
(store-slot (pop-arg things) ptr cons-car-slot list-pointer-lowtag) (store-slot (pop-arg things) ptr cons-car-slot list-pointer-lowtag)
(if star (if star
(store-slot (pop-arg things) ptr cons-cdr-slot list-pointer-lowtag) (store-slot (pop-arg things) ptr cons-cdr-slot list-pointer-lowtag)
(storew nil-value ptr cons-cdr-slot list-pointer-lowtag)))) (storew nil-value ptr cons-cdr-slot list-pointer-lowtag))
DONE))
(aver (null things)) (aver (null things))
(move result res))) (move result res)))
) )
;;;; special-purpose inline allocators ;;;; special-purpose inline allocators
;;; Special variant of 'storew' which might have a shorter encoding ;;; Zeroed storew - possibly use a shorter encoding if storing
;;; when storing to the heap (which starts out zero-filled). ;;; to a word that was initialized with 0-fill.
;;; This will always write 8 bytes if WORD is a negative number. ;;; This will always write 8 bytes if WORD is a negative number.
(defun storew* (word object slot lowtag zeroed &optional temp) (defun storew* (word object slot lowtag zeroed &optional temp)
(setq zeroed nil)
(cond (cond
((or (not zeroed) (not (typep word '(unsigned-byte 31)))) ((or (not zeroed) (not (typep word '(unsigned-byte 31))))
;; Will use temp reg if WORD can't be encoded as an imm32 ;; Will use temp reg if WORD can't be encoded as an imm32
@ -582,12 +886,13 @@
(inst and ,size-tn (lognot lowtag-mask)) (inst and ,size-tn (lognot lowtag-mask))
,size-tn))) ,size-tn)))
(put-header (vector-tn lowtag type len zeroed temp) (put-header (vector-tn lowtag type len zeroed temp)
(declare (ignore zeroed))
`(let ((len (if (sc-is ,len immediate) (fixnumize (tn-value ,len)) ,len)) `(let ((len (if (sc-is ,len immediate) (fixnumize (tn-value ,len)) ,len))
(type (if (sc-is ,type immediate) (tn-value ,type) ,type))) (type (if (sc-is ,type immediate) (tn-value ,type) ,type)))
(storew* type ,vector-tn 0 ,lowtag ,zeroed ,temp) (storew* type ,vector-tn 0 ,lowtag #|,zeroed|# nil ,temp)
#+ubsan (inst mov :dword (vector-len-ea ,vector-tn ,lowtag) len) #+ubsan (inst mov :dword (vector-len-ea ,vector-tn ,lowtag) len)
#-ubsan (storew* len ,vector-tn vector-length-slot #-ubsan (storew* len ,vector-tn vector-length-slot
,lowtag ,zeroed ,temp))) ,lowtag #|,zeroed|# nil ,temp)))
(want-shadow-bits () (want-shadow-bits ()
`(and poisoned `(and poisoned
(if (sc-is type immediate) (if (sc-is type immediate)
@ -625,10 +930,14 @@
(:results (result :scs (descriptor-reg) :from :load)) (:results (result :scs (descriptor-reg) :from :load))
(:arg-types #+ubsan (:constant t) (:arg-types #+ubsan (:constant t)
positive-fixnum positive-fixnum positive-fixnum) positive-fixnum positive-fixnum positive-fixnum)
(:temporary (:sc unsigned-reg) temp) ;; Wiring RAX as the temp is for the bitmap allocator call
(:temporary (:sc unsigned-reg :offset rax-offset) temp)
(:temporary (:sc complex-double-reg :offset 7) header-temp)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:policy :fast-safe) (:policy :fast-safe)
(:node-var node) (:node-var node)
(:arg-refs args-ref)
(:ignore header-temp)
(:generator 100 (:generator 100
#+ubsan #+ubsan
(when (want-shadow-bits) (when (want-shadow-bits)
@ -660,10 +969,15 @@
;; * If WORDS is not immediate and ALLOC-TEMP is not R12, then compute size ;; * If WORDS is not immediate and ALLOC-TEMP is not R12, then compute size
;; into RESULT, use ALLOC-TEMP as the instrumentation temp. ;; into RESULT, use ALLOC-TEMP as the instrumentation temp.
;; ALLOCATION receives: input = RESULT, output = RESULT, temp = ALLOC-TEMP. ;; ALLOCATION receives: input = RESULT, output = RESULT, temp = ALLOC-TEMP.
(multiple-value-bind (size-tn instrumentation-temp alloc-temp) (binding*
(cond ((sc-is words immediate) ((fixed-size (if (sc-is words immediate)
(pad-data-block (+ (tn-value words) vector-data-offset))))
(maybe-large (or (not fixed-size) (> fixed-size smlgc-blocksize-max)))
((size-tn instrumentation-temp alloc-temp)
(cond
(fixed-size
;; If WORDS is immediate, then let INSTRUMENT-ALLOC choose its temp ;; If WORDS is immediate, then let INSTRUMENT-ALLOC choose its temp
(values (calc-size-in-bytes words nil) (list result temp) temp)) (values fixed-size (list result temp) temp))
((location= temp r12-tn) ((location= temp r12-tn)
;; Compute the size into TEMP, use RESULT for instrumentation. ;; Compute the size into TEMP, use RESULT for instrumentation.
;; Don't give another temp to ALLOCATION, because its SIZE and temp ;; Don't give another temp to ALLOCATION, because its SIZE and temp
@ -673,17 +987,34 @@
;; Compute the size into RESULT, use TEMP for instrumentation. ;; Compute the size into RESULT, use TEMP for instrumentation.
;; ALLOCATION needs the temp register in this case, ;; ALLOCATION needs the temp register in this case,
;; because input and output are in the same register. ;; because input and output are in the same register.
(values (calc-size-in-bytes words result) temp temp))) (values (calc-size-in-bytes words result) temp temp)))))
(instrument-alloc (if (sc-is type immediate) (instrument-alloc (if (sc-is type immediate)
(case (tn-value type) (case (tn-value type)
(#.simple-vector-widetag 'simple-vector) (#.simple-vector-widetag 'simple-vector)
(t 'unboxed-array)) (t 'unboxed-array))
type) type)
size-tn node instrumentation-temp thread-tn) size-tn node instrumentation-temp thread-tn)
;; Same concept as in var-alloc
(when maybe-large
(assemble ()
(jump-if-not-bitmap-alloc LINEAR-ALLOC)
;; MOVDQU can load and store the object header from these 2 words
;; I pity the fool whose uses an immediate so large
;; that's it not encodable as a PUSH operand.
(inst push (encode-value-if-immediate length))
(inst push (encode-value-if-immediate type nil)) ; untagged
(inst mov temp size-tn) ; TEMP = RAX
(invoke-asm-routine 'call 'bitmap-vect-alloc node)
(move result temp)
(inst jmp DONE)
LINEAR-ALLOC))
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation type size-tn 0 result node alloc-temp thread-tn) (allocation (if (sc-is type immediate) (tn-value type) 'vector)
size-tn 0 result node (list alloc-temp) thread-tn
:try-bitmap-alloc (not maybe-large))
(put-header result 0 type length t alloc-temp) (put-header result 0 type length t alloc-temp)
(inst or :byte result other-pointer-lowtag))) (inst or :byte result other-pointer-lowtag)))
DONE
#+ubsan #+ubsan
(cond ((want-shadow-bits) (cond ((want-shadow-bits)
(inst pop temp-reg-tn) ; restore shadow bits (inst pop temp-reg-tn) ; restore shadow bits
@ -786,6 +1117,7 @@
(inst stos :qword))))) (inst stos :qword)))))
;;; ALLOCATE-LIST ;;; ALLOCATE-LIST
#+nil
(macrolet ((calc-size-in-bytes (length answer) (macrolet ((calc-size-in-bytes (length answer)
`(cond ((sc-is ,length immediate) `(cond ((sc-is ,length immediate)
(aver (/= (tn-value ,length) 0)) (aver (/= (tn-value ,length) 0))
@ -841,6 +1173,7 @@
(storew nil-value tail cons-cdr-slot list-pointer-lowtag)) (storew nil-value tail cons-cdr-slot list-pointer-lowtag))
done)) done))
#+nil ; FIXME
(define-vop (allocate-list-on-heap) (define-vop (allocate-list-on-heap)
(:args (length :scs (any-reg immediate)) (:args (length :scs (any-reg immediate))
;; Too bad we don't have an SC that implies actually a CPU immediate ;; Too bad we don't have an SC that implies actually a CPU immediate
@ -850,16 +1183,40 @@
(:arg-types positive-fixnum *) (:arg-types positive-fixnum *)
(:policy :fast-safe) (:policy :fast-safe)
(:node-var node) (:node-var node)
;; These are need for the bitmap allocator
(:temporary (:sc unsigned-reg :offset rcx-offset :from (:argument 0)) rcx)
(:temporary (:sc unsigned-reg :offset rax-offset :from (:argument 1) :to :result) rax)
;; Too many temps. Oh well.
(:temporary (:sc descriptor-reg) tail next limit) (:temporary (:sc descriptor-reg) tail next limit)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:generator 20 (:generator 20
(unless (sc-is length immediate)
(inst test length length)
(inst jmp :nz continue)
(inst mov result nil-value)
(inst jmp out))
continue
(unless (sc-is length immediate) (move rcx length))
(move rax element)
(let ((nbytes (cond ((sc-is length immediate)
(* (tn-value ,length) n-word-bytes 2))
(t
(inst shl rcx (1+ (- word-shift n-fixnum-tag-bits)))
rcx))))
(instrument-alloc +cons-primtype+ nbytes node (list next limit) thread-tn))
(let ((size (calc-size-in-bytes length tail)) (let ((size (calc-size-in-bytes length tail))
(entry (gen-label)) (entry (gen-label))
(loop (gen-label)) (loop (gen-label))
(native (gen-label))
(leave-pa (gen-label))) (leave-pa (gen-label)))
(instrument-alloc +cons-primtype+ size node (list next limit) thread-tn) (instrument-alloc +cons-primtype+ size node (list next limit) thread-tn)
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation +cons-primtype+ size list-pointer-lowtag result node limit thread-tn (test-should-use-smlgc :make-list)
(inst jmp :z native)
;; SML# allocator, cons-at-a-time
(emit-label native)
(gengc-alloc +cons-primtype+ size list-pointer-lowtag result node
limit thread-tn
:overflow :overflow
(lambda () (lambda ()
;; Push C call args right-to-left ;; Push C call args right-to-left
@ -893,8 +1250,9 @@
(:results (result :scs (descriptor-reg) :from :argument)) (:results (result :scs (descriptor-reg) :from :argument))
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 37 (:generator 37
(alloc-other fdefn-widetag fdefn-size result node nil thread-tn (alloc-other fdefn-widetag fdefn-size result node (list temp temp2) thread-tn
(lambda () (lambda ()
(storew name result fdefn-name-slot other-pointer-lowtag) (storew name result fdefn-name-slot other-pointer-lowtag)
(storew nil-value result fdefn-fun-slot other-pointer-lowtag) (storew nil-value result fdefn-fun-slot other-pointer-lowtag)
@ -909,6 +1267,7 @@
(:node-var node) (:node-var node)
(:vop-var vop) (:vop-var vop)
(:generator 10 (:generator 10
(aver (>= length 1))
(let* ((words (+ length closure-info-offset)) ; including header (let* ((words (+ length closure-info-offset)) ; including header
(bytes (pad-data-block words)) (bytes (pad-data-block words))
(header (logior (ash (1- words) n-widetag-bits) closure-widetag)) (header (logior (ash (1- words) n-widetag-bits) closure-widetag))
@ -920,7 +1279,7 @@
:elide-if stack-allocate-p :thread-tn thread-tn) :elide-if stack-allocate-p :thread-tn thread-tn)
(if stack-allocate-p (if stack-allocate-p
(stack-allocation bytes fun-pointer-lowtag result) (stack-allocation bytes fun-pointer-lowtag result)
(allocation closure-widetag bytes fun-pointer-lowtag result node temp thread-tn)) (allocation closure-widetag bytes fun-pointer-lowtag result node (list temp) thread-tn))
(storew* #-compact-instance-header header ; write the widetag and size (storew* #-compact-instance-header header ; write the widetag and size
#+compact-instance-header ; ... plus the layout pointer #+compact-instance-header ; ... plus the layout pointer
(let ((layout #-sb-thread (static-symbol-value-ea 'function-layout) (let ((layout #-sb-thread (static-symbol-value-ea 'function-layout)
@ -944,6 +1303,7 @@
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:info stack-allocate-p) (:info stack-allocate-p)
(:node-var node) (:node-var node)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 10 (:generator 10
(let ((data (if (sc-is value immediate) (let ((data (if (sc-is value immediate)
(let ((bits (encode-value-if-immediate value))) (let ((bits (encode-value-if-immediate value)))
@ -960,8 +1320,11 @@
(inst push (compute-object-header value-cell-size value-cell-widetag)) (inst push (compute-object-header value-cell-size value-cell-widetag))
(inst lea result (ea other-pointer-lowtag rsp-tn))) (inst lea result (ea other-pointer-lowtag rsp-tn)))
(t (t
(alloc-other value-cell-widetag value-cell-size result node nil thread-tn (alloc-other
value-cell-widetag value-cell-size result node
(list temp temp2) thread-tn
(lambda () (lambda ()
;; FIXME: use the temp instead of push/pop
(if (sc-case value (if (sc-case value
(immediate (immediate
(unless (integerp data) (inst push data) t)) (unless (integerp data) (inst push data) t))
@ -984,15 +1347,16 @@
(flet (flet
((alloc (vop name words type lowtag stack-allocate-p result ((alloc (vop name words type lowtag stack-allocate-p result
&optional alloc-temp node &optional temps node
&aux (bytes (pad-data-block words)) &aux (bytes (pad-data-block words))
(remain-pseudo-atomic (remain-pseudo-atomic
(eq (car (last (vop-codegen-info vop))) :pseudo-atomic))) (eq (car (last (vop-codegen-info vop))) :pseudo-atomic)))
#+bignum-assertions #+bignum-assertions
(when (eq type bignum-widetag) (setq bytes (* bytes 2))) ; use 2x the space (when (eq type bignum-widetag) (setq bytes (* bytes 2))) ; use 2x the space
(aver (<= bytes smlgc-blocksize-max))
(progn name) ; possibly not used (progn name) ; possibly not used
(unless stack-allocate-p (unless stack-allocate-p
(instrument-alloc type bytes node (list result alloc-temp) thread-tn)) (instrument-alloc type bytes node (cons result temps) thread-tn))
(pseudo-atomic (:default-exit (not remain-pseudo-atomic) (pseudo-atomic (:default-exit (not remain-pseudo-atomic)
:elide-if stack-allocate-p :thread-tn thread-tn) :elide-if stack-allocate-p :thread-tn thread-tn)
;; If storing a header word, defer ORing in the lowtag until after ;; If storing a header word, defer ORing in the lowtag until after
@ -1000,11 +1364,13 @@
(cond (stack-allocate-p (cond (stack-allocate-p
(stack-allocation bytes (if type 0 lowtag) result)) (stack-allocation bytes (if type 0 lowtag) result))
((eql type funcallable-instance-widetag) ((eql type funcallable-instance-widetag)
(bitmap-alloc bytes result temps node ALLOCATED)
(inst push bytes) (inst push bytes)
(invoke-asm-routine 'call 'alloc-funinstance vop) (invoke-asm-routine 'call 'alloc-funinstance vop)
(inst pop result)) (inst pop result))
(t (t
(allocation type bytes (if type 0 lowtag) result node alloc-temp thread-tn))) (allocation type bytes (if type 0 lowtag) result node temps thread-tn)))
ALLOCATED
(let ((header (compute-object-header words type))) (let ((header (compute-object-header words type)))
(cond #+compact-instance-header (cond #+compact-instance-header
((and (eq name '%make-structure-instance) stack-allocate-p) ((and (eq name '%make-structure-instance) stack-allocate-p)
@ -1014,7 +1380,8 @@
;; where this instruction must write exactly 4 bytes. ;; where this instruction must write exactly 4 bytes.
(inst mov :dword (ea 0 result) header)) (inst mov :dword (ea 0 result) header))
(t (t
(storew* header result 0 0 (not stack-allocate-p))))) (storew* header result 0 0 (and (not stack-allocate-p)
(> words 2))))))
;; GC can make the best choice about placement if it has a layout. ;; GC can make the best choice about placement if it has a layout.
;; Of course with conservative GC the object will be pinned anyway, ;; Of course with conservative GC the object will be pinned anyway,
;; but still, always having a layout is a good thing. ;; but still, always having a layout is a good thing.
@ -1026,11 +1393,11 @@
(define-vop (fixed-alloc) (define-vop (fixed-alloc)
(:info name words type lowtag dx) (:info name words type lowtag dx)
(:results (result :scs (descriptor-reg))) (:results (result :scs (descriptor-reg)))
(:temporary (:sc unsigned-reg) alloc-temp) (:temporary (:sc unsigned-reg) temp1 temp2)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:vop-var vop) (:vop-var vop)
(:node-var node) (:node-var node)
(:generator 50 (alloc vop name words type lowtag dx result alloc-temp node))) (:generator 50 (alloc vop name words type lowtag dx result (list temp1 temp2) node)))
(define-vop (sb-c::fixed-alloc-to-stack) (define-vop (sb-c::fixed-alloc-to-stack)
(:info name words type lowtag dx) (:info name words type lowtag dx)
(:results (result :scs (descriptor-reg))) (:results (result :scs (descriptor-reg)))
@ -1052,8 +1419,8 @@
(:results (result :scs (descriptor-reg) :from (:eval 1))) (:results (result :scs (descriptor-reg) :from (:eval 1)))
(:temporary (:sc unsigned-reg :from :eval :to (:eval 1)) bytes) (:temporary (:sc unsigned-reg :from :eval :to (:eval 1)) bytes)
(:temporary (:sc unsigned-reg :from :eval :to :result) header) (:temporary (:sc unsigned-reg :from :eval :to :result) header)
;; KLUDGE: wire to RAX so that it doesn't get R12 ;; alloc-temp is a passing register for the ASM routine
(:temporary (:sc unsigned-reg :offset 0) alloc-temp) (:temporary (:sc unsigned-reg :offset rax-offset) alloc-temp)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:vop-var vop) (:vop-var vop)
@ -1063,7 +1430,8 @@
;; which it failed to do if the var-alloc translation was invoked. ;; which it failed to do if the var-alloc translation was invoked.
;; But it seems we never need this! (so is it FIXME or isn't it?) ;; But it seems we never need this! (so is it FIXME or isn't it?)
(error "can't %MAKE-FUNCALLABLE-INSTANCE of unknown length")) (error "can't %MAKE-FUNCALLABLE-INSTANCE of unknown length"))
(let ((remain-pseudo-atomic (eq (car (last (vop-codegen-info vop))) :pseudo-atomic))) (progn ; let ((remain-pseudo-atomic (eq (car (last (vop-codegen-info vop))) :pseudo-atomic)))
; (declare (ignore remain-pseudo-atomic))
;; With the exception of bignums, these objects have effectively ;; With the exception of bignums, these objects have effectively
;; 32-bit headers because the high 4 byes contain a layout pointer. ;; 32-bit headers because the high 4 byes contain a layout pointer.
(let ((operand-size (if (= type bignum-widetag) :qword :dword))) (let ((operand-size (if (= type bignum-widetag) :qword :dword)))
@ -1081,15 +1449,102 @@
(stack-allocation bytes lowtag result) (stack-allocation bytes lowtag result)
(storew header result 0 lowtag)) (storew header result 0 lowtag))
(t (t
(assemble ()
;; can't pass RESULT as a possible choice of scratch register ;; can't pass RESULT as a possible choice of scratch register
;; because it might be in the same physical reg as BYTES. ;; because it might be in the same physical reg as BYTES.
;; Yup, the lifetime specs in this vop are pretty confusing. ;; Yup, the lifetime specs in this vop are pretty confusing.
(instrument-alloc type bytes node alloc-temp thread-tn) (instrument-alloc type bytes node alloc-temp thread-tn)
(pseudo-atomic (:default-exit (not remain-pseudo-atomic) (jump-if-not-bitmap-alloc LINEAR-ALLOC)
:thread-tn thread-tn) (inst push lowtag)
(allocation type bytes lowtag result node alloc-temp thread-tn) (inst push header)
(storew header result 0 lowtag))))))) (move alloc-temp bytes) ; RAX passes the size
(invoke-asm-routine 'call 'bitmap-var-alloc node)
(move result alloc-temp)
(inst jmp done)
LINEAR-ALLOC
(pseudo-atomic (:thread-tn thread-tn)
(gengc-alloc type bytes lowtag result node alloc-temp thread-tn)
(storew header result 0 lowtag))
DONE))))))
#+sb-xc-host
(progn
(define-vop (alloc-code)
(:args (total-words :scs (unsigned-reg))
(boxed-words :scs (unsigned-reg)))
(:temporary (:sc unsigned-reg :offset rdi-offset :from (:argument 0)) rdi)
(:temporary (:sc unsigned-reg :offset rsi-offset) rsi)
(:temporary (:sc unsigned-reg :offset rax-offset) rax)
(:temporary (:sc unsigned-reg :offset r15-offset) frame)
(:results (res :scs (descriptor-reg)))
(:node-var node)
(:ignore frame)
(:generator 1
(move rdi total-words) ; C arg 1
(move rsi boxed-words) ; C arg 2
(jump-if-not-bitmap-alloc LINEAR-ALLOC)
;; bitmap allocator
(inst lea rax (ea nil rdi n-word-bytes)) ; RAX = nbytes
;(inst cmp rax smlgc-blocksize-max)
;(inst jmp :be OK)
;(inst break halt-trap) ; can't use this vop for large code
;OK
(inst shl rdi 32) ; CODE-HEADER-SIZE-SHIFT
(inst or :byte rdi code-header-widetag)
(inst push other-pointer-lowtag)
(inst push rdi) ; header word
(invoke-asm-routine 'call 'bitmap-var-alloc node)
;; store the boxed size in bytes. BITMAP-VAR-ALLOC preserves RSI
(inst shl rsi word-shift) ; words to bytes
(move res rax)
(storew rsi res code-boxed-size-slot other-pointer-lowtag)
(inst jmp done)
LINEAR-ALLOC
(with-registers-preserved (c :except rsi :frame-reg r15)
(pseudo-atomic ()
(inst call (ea (make-fixup "alloc_code_object" :foreign 8))))
(move rsi rax-tn))
(move res rsi)
DONE))
;;; Place a large codeblob under GC control.
;;; The header words haven't been filled in yet because we have to deal with a subtle
;;; timing issue: what happens if, one instruction after exiting pseudo-atomic, this
;;; thread is asked by GC to publish roots. Do we see a properly tagged pointer to a
;;; known good object; while prior to calling into C we do NOT see it as being a good
;;; object? So the header can be filled in only while pseudo-atomic.
;;; Other threads may observe the object in the balanced binary tree but ignore it
;;; as if it were filler. (See also alloc_large in "lispobj.c")
#+nil
(define-vop (manage-large-codeblob)
(:policy :fast-safe)
(:arg-types positive-fixnum t)
(:args (header :scs (unsigned-reg) :target rbx)
(mseg :scs (sap-reg) :target rdi))
(:temporary (:sc unsigned-reg :offset rbx-offset :from (:argument 0)) rbx)
(:temporary (:sc unsigned-reg :offset rdi-offset :from (:argument 1) :to :result) rdi)
(:results (res :scs (descriptor-reg)))
(:generator 1
(move rbx header)
(move rdi mseg) ; C call arg
;; Don't need to save any FPRs because this vop is invoked only by a Lisp function
;; that does not use floating-point. Even if the C side clobbers every FPR
;; (which it doesn't) the Lisp call convention has no callee-saved FPRs.
(with-registers-preserved (c :except (rdi :fprs))
(pseudo-atomic ()
(inst call (ea (make-fixup "manage_large_object" :foreign 8)))
;; Compute the tagged pointer to the code blob given the 'mseg' pointer
;; which was conveniently returned from C.
(inst lea rdi (ea (+ other-pointer-lowtag smlgc-mseg-overhead-bytes) rax-tn))
;; Store the header word
(storew header rdi 0 other-pointer-lowtag)))
(move res rdi)))
) ; end PROGN
#|
#+sb-xc-host #+sb-xc-host
(define-vop (alloc-code) (define-vop (alloc-code)
(:args (total-words :scs (unsigned-reg) :target c-arg-1) (:args (total-words :scs (unsigned-reg) :target c-arg-1)
@ -1113,6 +1568,7 @@
#+immobile-code (make-fixup "alloc_code_object" :foreign))) #+immobile-code (make-fixup "alloc_code_object" :foreign)))
(move c-arg-1 rax-tn)) (move c-arg-1 rax-tn))
(move res c-arg-1))) (move res c-arg-1)))
|#
#+immobile-space #+immobile-space
(macrolet ((c-fun (name) (macrolet ((c-fun (name)
@ -1212,3 +1668,21 @@
OUT))) OUT)))
) ; end MACROLET ) ; end MACROLET
(define-vop (new-make-list)
(:args (length :scs (signed-reg))
(element :scs (any-reg descriptor-reg)))
(:temporary (:sc unsigned-reg :offset rcx-offset :from (:argument 0)) rcx)
(:temporary (:sc unsigned-reg :offset rax-offset :from (:argument 1) :to :result) rax)
(:results (res :scs (descriptor-reg)))
(:generator 10
(inst test length length)
(inst jmp :nz callout)
(inst mov res nil-value)
(inst jmp done)
callout
(move rcx length)
(move rax element)
(inst call (ea (make-fixup 'make-list-helper :assembly-routine*)))
(move res rax)
done))

View file

@ -69,7 +69,7 @@
(:arg-types positive-fixnum positive-fixnum) (:arg-types positive-fixnum positive-fixnum)
(:temporary (:sc any-reg :to :eval) bytes) (:temporary (:sc any-reg :to :eval) bytes)
(:temporary (:sc any-reg :to :result) header) (:temporary (:sc any-reg :to :result) header)
(:temporary (:sc unsigned-reg) temp) (:temporary (:sc unsigned-reg :offset rax-offset) temp)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:results (result :scs (descriptor-reg) :from :eval)) (:results (result :scs (descriptor-reg) :from :eval))
(:node-var node) (:node-var node)
@ -85,10 +85,22 @@
(inst or :dword header type) (inst or :dword header type)
(inst shr :dword header n-fixnum-tag-bits) (inst shr :dword header n-fixnum-tag-bits)
(instrument-alloc nil bytes node temp thread-tn) (instrument-alloc nil bytes node temp thread-tn)
(jump-if-not-bitmap-alloc LINEAR-ALLOC)
;; TODO: inline the code to choose an allocation pointer
;; and then use the fast path for bitmap allocation.
;; (it won't be an oversized object)
(inst push 0)
(inst push header)
(move temp bytes) ; RAX
(invoke-asm-routine 'call 'bitmap-vect-alloc node)
(move result temp)
(inst jmp DONE)
LINEAR-ALLOC
(pseudo-atomic (:thread-tn thread-tn) (pseudo-atomic (:thread-tn thread-tn)
(allocation type bytes 0 result node temp thread-tn) (gengc-alloc type bytes 0 result node temp thread-tn)
(storew header result 0 0) (storew header result 0 0)
(inst or :byte result other-pointer-lowtag)))) (inst or :byte result other-pointer-lowtag))
DONE))
;;;; additional accessors and setters for the array header ;;;; additional accessors and setters for the array header
(define-full-reffer %array-dimension * (define-full-reffer %array-dimension *
@ -339,7 +351,7 @@
object index (index-scale n-word-bytes index)))) object index (index-scale n-word-bytes index))))
,@(when (eq type 'simple-vector) ,@(when (eq type 'simple-vector)
'((emit-gengc-barrier object ea val-temp (vop-nth-arg 2 vop) value))) '((emit-gengc-barrier object ea val-temp (vop-nth-arg 2 vop) value)))
(emit-store ea value val-temp)))) (emit-store vop ,(eq type 'simple-vector) object ea value val-temp))))
(define-vop (,(symbolicate name "-C") dvset) (define-vop (,(symbolicate name "-C") dvset)
(:args (object :scs (descriptor-reg)) (:args (object :scs (descriptor-reg))
(value :scs ,scs)) (value :scs ,scs))
@ -359,7 +371,7 @@
(let ((ea (ea (- (* (+ ,offset index addend) n-word-bytes) ,lowtag) object))) (let ((ea (ea (- (* (+ ,offset index addend) n-word-bytes) ,lowtag) object)))
,@(when (eq type 'simple-vector) ,@(when (eq type 'simple-vector)
'((emit-gengc-barrier object ea val-temp (vop-nth-arg 1 vop) value))) '((emit-gengc-barrier object ea val-temp (vop-nth-arg 1 vop) value)))
(emit-store ea value val-temp)))))) (emit-store vop ,(eq type 'simple-vector) object ea value val-temp))))))
(defmacro def-full-data-vector-frobs (type element-type &rest scs) (defmacro def-full-data-vector-frobs (type element-type &rest scs)
`(progn `(progn
(define-full-reffer+addend ,(symbolicate "DATA-VECTOR-REF-WITH-OFFSET/" type) (define-full-reffer+addend ,(symbolicate "DATA-VECTOR-REF-WITH-OFFSET/" type)
@ -1067,10 +1079,12 @@
(unsigned-reg) unsigned-num %set-vector-raw-bits) (unsigned-reg) unsigned-num %set-vector-raw-bits)
;;; Weak vectors ;;; Weak vectors
#-weak-vector-readbarrier
(progn
(define-full-reffer %weakvec-ref * vector-data-offset other-pointer-lowtag (define-full-reffer %weakvec-ref * vector-data-offset other-pointer-lowtag
(any-reg descriptor-reg) * %weakvec-ref) (any-reg descriptor-reg) * %weakvec-ref)
(define-full-setter %weakvec-set * vector-data-offset other-pointer-lowtag (define-full-setter %weakvec-set * vector-data-offset other-pointer-lowtag
(any-reg descriptor-reg) * %weakvec-set) (any-reg descriptor-reg) * %weakvec-set))
;;;; ATOMIC-INCF for arrays ;;;; ATOMIC-INCF for arrays

View file

@ -162,7 +162,13 @@
(inst .skip (* (1- simple-fun-insts-offset) n-word-bytes)) (inst .skip (* (1- simple-fun-insts-offset) n-word-bytes))
;; The start of the actual code. ;; The start of the actual code.
;; Save the return-pc. ;; Save the return-pc.
(popw rbp-tn (frame-word-offset return-pc-save-offset)))) (popw rbp-tn (frame-word-offset return-pc-save-offset))
;;
;(inst test :byte (static-symbol-value-ea '*sml-check-flag*) 2)
;(inst jmp :z SKIP)
;(inst call (ea (make-fixup 'gc-check :assembly-routine*)))
SKIP))
(defun emit-lea (target source disp) (defun emit-lea (target source disp)
(if (eql disp 0) (if (eql disp 0)
@ -1286,7 +1292,8 @@
;; Not much of an advantage, but why not. ;; Not much of an advantage, but why not.
(:temporary (:sc unsigned-reg :offset rcx-offset :from (:argument 1)) rcx) (:temporary (:sc unsigned-reg :offset rcx-offset :from (:argument 1)) rcx)
;; Note that DST conflicts with RESULT because we use both as temps ;; Note that DST conflicts with RESULT because we use both as temps
(:temporary (:sc unsigned-reg) value dst) (:temporary (:sc unsigned-reg :offset rax-offset) dst)
(:temporary (:sc unsigned-reg) value)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:results (result :scs (descriptor-reg))) (:results (result :scs (descriptor-reg)))
(:node-var node) (:node-var node)
@ -1307,9 +1314,28 @@
(instrument-alloc +cons-primtype+ rcx node (list value dst) thread-tn)) (instrument-alloc +cons-primtype+ rcx node (list value dst) thread-tn))
(pseudo-atomic (:elide-if (node-stack-allocate-p node) :thread-tn thread-tn) (pseudo-atomic (:elide-if (node-stack-allocate-p node) :thread-tn thread-tn)
;; Produce an untagged pointer into DST ;; Produce an untagged pointer into DST
(if (node-stack-allocate-p node) (cond
(stack-allocation rcx 0 dst) ((node-stack-allocate-p node)
(allocation +cons-primtype+ rcx 0 dst node value thread-tn (stack-allocation rcx 0 dst))
(t
;; TODO: There could be no reason not to stuff this entire thing
;; into an assembly routine. "unfolding" it into its constituent
;; steps as done here may not be a win.
(let ((fallback (gen-label)) (linear (gen-label)))
;; Try to get space for contiguous conses, the number of bytes being in RCX.
(jump-if-not-bitmap-alloc LINEAR)
(invoke-asm-routine 'call 'bitmap-reserve-&rest node)
;; Result is in RAX, with ZF set on failure.
(inst jmp :nz CONTINUE)
(jump-if-bitmap-alloc FALLBACK)
(assemble (:elsewhere)
(emit-label fallback)
(move dst context) ; dst = RAX
(invoke-asm-routine 'call 'bitmap-listify node)
(move result dst)
(inst jmp LEAVE-PA))
(emit-label linear)
(gengc-alloc +cons-primtype+ rcx 0 dst node value thread-tn
:overflow :overflow
(lambda () (lambda ()
(inst push rcx) (inst push rcx)
@ -1318,7 +1344,8 @@
'call (if (system-tlab-p 0 node) 'sys-listify-&rest 'listify-&rest) 'call (if (system-tlab-p 0 node) 'sys-listify-&rest 'listify-&rest)
node) node)
(inst pop result) (inst pop result)
(inst jmp leave-pa)))) (inst jmp LEAVE-PA))))))
CONTINUE
;; Recalculate DST as a tagged pointer to the last cons ;; Recalculate DST as a tagged pointer to the last cons
(inst lea dst (ea (- list-pointer-lowtag (* cons-size n-word-bytes)) dst rcx)) (inst lea dst (ea (- list-pointer-lowtag (* cons-size n-word-bytes)) dst rcx))
(inst shr :dword rcx (1+ word-shift)) ; convert bytes to number of cells (inst shr :dword rcx (1+ word-shift)) ; convert bytes to number of cells
@ -1341,6 +1368,8 @@
(inst inc rcx) ; :QWORD because it's a signed number (inst inc rcx) ; :QWORD because it's a signed number
(inst jmp :nz loop) (inst jmp :nz loop)
LEAVE-PA) LEAVE-PA)
;; This label is branched to only if there were no &REST args
;; and we never entered a pseudo-atomic section.
DONE)) DONE))
;;; Return the location and size of the &MORE arg glob created by ;;; Return the location and size of the &MORE arg glob created by
@ -1382,6 +1411,15 @@
(:vop-var vop) (:vop-var vop)
(:save-p :compute-only) (:save-p :compute-only)
(:generator 3 (:generator 3
#|
;; See if SML# collector has asked us to run a little bit
;; (to be replaced by POSIX signal, but for now, no signals)
(inst mov temp (ea (make-fixup "sml_check_flag" :foreign-dataref)))
(inst test :dword (ea temp) -1)
(inst jmp :z SKIP)
(invoke-asm-routine 'call 'gc-cooperate vop)
SKIP
|#
;; NOTE: copy-more-arg expects this to issue a CMP for min > 1 ;; NOTE: copy-more-arg expects this to issue a CMP for min > 1
(let ((err-lab (let ((err-lab
(generate-error-code vop 'invalid-arg-count-error nargs))) (generate-error-code vop 'invalid-arg-count-error nargs)))

View file

@ -24,6 +24,7 @@
#-ubsan (:ignore name) #-ubsan (:ignore name)
(:results (result :scs (descriptor-reg any-reg))) (:results (result :scs (descriptor-reg any-reg)))
(:generator 1 (:generator 1
(check-alivep object lowtag)
(cond #+ubsan (cond #+ubsan
((member name '(sb-c::vector-length %array-fill-pointer)) ; half-sized slot ((member name '(sb-c::vector-length %array-fill-pointer)) ; half-sized slot
(inst mov :dword result (vector-len-ea object))) (inst mov :dword result (vector-len-ea object)))
@ -42,6 +43,7 @@
(:arg-refs args) (:arg-refs args)
(:temporary (:sc unsigned-reg) val-temp) (:temporary (:sc unsigned-reg) val-temp)
(:generator 1 (:generator 1
(check-alivep object lowtag)
(cond #+ubsan (cond #+ubsan
((and (eql offset sb-vm:array-fill-pointer-slot) ; half-sized slot ((and (eql offset sb-vm:array-fill-pointer-slot) ; half-sized slot
(or (eq name 'make-array) (or (eq name 'make-array)
@ -56,9 +58,15 @@
;; But funcallable-instances are on PAGE_TYPE_CODE, and code pages do not use ;; But funcallable-instances are on PAGE_TYPE_CODE, and code pages do not use
;; MMU-based protection regardless of this feature. ;; MMU-based protection regardless of this feature.
;; So we have to alter the card mark differently. ;; So we have to alter the card mark differently.
(inst lea val-temp (object-slot-ea object offset lowtag))
(inst push value)
(inst push val-temp)
(inst push object)
(invoke-asm-routine 'call 'set-funinstance-ref vop)
#+nil
(pseudo-atomic () (pseudo-atomic ()
(emit-code-page-gengc-barrier object val-temp) (emit-code-page-gengc-barrier object val-temp)
(emit-store (object-slot-ea object offset lowtag) value val-temp))) (emit-store vop t object (object-slot-ea object offset lowtag) value val-temp)))
(t (t
(let* ((value-tn (tn-ref-tn (tn-ref-across args))) (let* ((value-tn (tn-ref-tn (tn-ref-across args)))
(prim-type (sb-c::tn-primitive-type value-tn)) (prim-type (sb-c::tn-primitive-type value-tn))
@ -73,7 +81,10 @@
(eq name :allocator) (eq name :allocator)
(sb-c::set-slot-old-p node))) (sb-c::set-slot-old-p node)))
(emit-gengc-barrier object nil val-temp (vop-nth-arg 1 vop) value))) (emit-gengc-barrier object nil val-temp (vop-nth-arg 1 vop) value)))
(emit-store (object-slot-ea object offset lowtag) value val-temp))))) (emit-store vop
(neq (fourth (vop-codegen-info vop)) :pseudo-atomic)
object (object-slot-ea object offset lowtag)
value val-temp)))))
(define-vop (compare-and-swap-slot) (define-vop (compare-and-swap-slot)
(:args (object :scs (descriptor-reg) :to :eval) (:args (object :scs (descriptor-reg) :to :eval)
@ -89,9 +100,10 @@
(:results (result :scs (descriptor-reg any-reg))) (:results (result :scs (descriptor-reg any-reg)))
(:vop-var vop) (:vop-var vop)
(:generator 5 (:generator 5
(check-alivep object lowtag)
(emit-gengc-barrier object nil rax (vop-nth-arg 2 vop) new) (emit-gengc-barrier object nil rax (vop-nth-arg 2 vop) new)
(move rax old) (emit-cmpxchg vop t object (ea (- (* offset n-word-bytes) lowtag) object)
(inst cmpxchg :lock (ea (- (* offset n-word-bytes) lowtag) object) new) old new rax)
(move result rax))) (move result rax)))
;;;; symbol hacking VOPs ;;;; symbol hacking VOPs
@ -114,11 +126,18 @@
(:temporary (:sc unsigned-reg) val-temp) (:temporary (:sc unsigned-reg) val-temp)
(:vop-var vop) (:vop-var vop)
(:generator 4 (:generator 4
(check-alivep symbol other-pointer-lowtag)
(emit-symbol-write-barrier symbol nil val-temp (vop-nth-arg 1 vop) value) (emit-symbol-write-barrier symbol nil val-temp (vop-nth-arg 1 vop) value)
(emit-store (if (sc-is symbol immediate) (let* ((imm-value-p
(if (sc-is symbol immediate)
(csubtypep (info :variable :type (tn-value symbol))
(specifier-type '(or fixnum boolean)))))
(possible-pointerp (not imm-value-p)))
(emit-store vop possible-pointerp symbol
(if (sc-is symbol immediate)
(symbol-slot-ea (tn-value symbol) symbol-value-slot) (symbol-slot-ea (tn-value symbol) symbol-value-slot)
(object-slot-ea symbol symbol-value-slot other-pointer-lowtag)) (object-slot-ea symbol symbol-value-slot other-pointer-lowtag))
value val-temp))) value val-temp))))
;;; This does not resolve the TLS-INDEX at load-time, because we don't want to ;;; This does not resolve the TLS-INDEX at load-time, because we don't want to
;;; waste TLS indices for symbols that may never get thread-locally bound. ;;; waste TLS indices for symbols that may never get thread-locally bound.
@ -161,34 +180,40 @@
#+gs-seg (:temporary (:sc unsigned-reg) thread-temp) #+gs-seg (:temporary (:sc unsigned-reg) thread-temp)
(:vop-var vop) (:vop-var vop)
(:generator 4 (:generator 4
(if (and (sc-is symbol immediate) (when (and (sc-is symbol immediate)
(eq (info :variable :wired-tls (tn-value symbol)) :always-thread-local)) (eq (info :variable :wired-tls (tn-value symbol)) :always-thread-local))
;; We never need the GC barrier for TLS. I think it would be preferable ;; We never need the generational GC barrier or CMS GC barrier for TLS stores.
;; to resolve this in IR1, maybe turning it into SET-TLS-VALUE. ;; I think it would be preferable to resolve this in IR1, maybe turning it into SET-TLS-VALUE.
(emit-store (ea (make-fixup (tn-value symbol) :symbol-tls-index) thread-tn) (emit-store vop nil symbol
value val-temp) (ea (make-fixup (tn-value symbol) :symbol-tls-index) thread-tn)
(let ((store (gen-label))) value val-temp)
(cond ((and (sc-is symbol immediate) (return-from set))
(info :variable :wired-tls (tn-value symbol))) (cond ((and (sc-is symbol immediate)
;; The TLS index is arbitrary but known to be nonzero, (info :variable :wired-tls (tn-value symbol)))
;; so we can resolve the displacement of the thread-local value ;; The TLS index is arbitrary but known to be nonzero,
;; at load-time, saving one instruction over the general case. ;; so we can resolve the displacement of the thread-local value
(inst lea cell (ea (make-fixup (tn-value symbol) :symbol-tls-index) ;; at load-time, saving one instruction over the general case.
thread-tn))) (inst lea cell (ea (make-fixup (tn-value symbol) :symbol-tls-index)
(t thread-tn)))
;; These MOVs look the same, but when the symbol is immediate, this is (t
;; a load from an absolute address. Needless to say, the names of these ;; These MOVs look the same, but when the symbol is immediate, this is
;; accessor macros are arbitrary - the difference is not very apparent. ;; a load from an absolute address. Needless to say, the names of these
(inst mov :dword cell (if (sc-is symbol immediate) ;; accessor macros are arbitrary - the difference is not very apparent.
(symbol-tls-index-ea symbol) (inst mov :dword cell (if (sc-is symbol immediate)
(tls-index-of symbol))) (symbol-tls-index-ea symbol)
(inst add cell thread-tn))) (tls-index-of symbol)))
(inst cmp :qword (ea cell) no-tls-value-marker) (inst add cell thread-tn)))
(inst jmp :ne STORE) (inst cmp :qword (ea cell) no-tls-value-marker)
(emit-symbol-write-barrier symbol nil val-temp (vop-nth-arg 1 vop) value) (inst jmp :e GLOBAL)
(get-symbol-value-slot-ea cell symbol) (emit-store vop nil symbol (ea cell) value val-temp)
(emit-label STORE) (inst jmp DONE)
(emit-store (ea cell) value val-temp))))) GLOBAL
;; gencgc barrier
(emit-symbol-write-barrier symbol nil val-temp (vop-nth-arg 1 vop) value)
;; concurrent barrier store
(emit-store vop t symbol (object-slot-ea symbol symbol-value-slot other-pointer-lowtag)
value val-temp)
DONE))
(define-vop (fast-symbol-global-value) (define-vop (fast-symbol-global-value)
(:args (object :scs (descriptor-reg immediate))) (:args (object :scs (descriptor-reg immediate)))
@ -230,11 +255,7 @@
`(thread-tls-ea (load-time-tls-offset ,sym))) `(thread-tls-ea (load-time-tls-offset ,sym)))
(symbol-value-slot-ea (sym) ; SYM is a TN (symbol-value-slot-ea (sym) ; SYM is a TN
`(ea (- (* symbol-value-slot n-word-bytes) other-pointer-lowtag) `(ea (- (* symbol-value-slot n-word-bytes) other-pointer-lowtag)
,sym)) ,sym)))
(load-oldval ()
`(if (sc-is old immediate)
(inst mov rax (encode-value-if-immediate old))
(move rax old))))
(define-vop (%cas-symbol-global-value) (define-vop (%cas-symbol-global-value)
(:translate %cas-symbol-global-value) (:translate %cas-symbol-global-value)
@ -248,12 +269,13 @@
(:policy :fast-safe) (:policy :fast-safe)
(:vop-var vop) (:vop-var vop)
(:generator 10 (:generator 10
(check-alivep symbol other-pointer-lowtag)
(emit-symbol-write-barrier symbol nil rax (vop-nth-arg 2 vop) new) (emit-symbol-write-barrier symbol nil rax (vop-nth-arg 2 vop) new)
(load-oldval) (emit-cmpxchg vop t symbol
(inst cmpxchg :lock (if (sc-is symbol immediate) (if (sc-is symbol immediate)
(symbol-slot-ea (tn-value symbol) symbol-value-slot) (symbol-slot-ea (tn-value symbol) symbol-value-slot)
(symbol-value-slot-ea symbol)) (symbol-value-slot-ea symbol))
new) old new rax)
(move result rax))) (move result rax)))
(define-vop (%compare-and-swap-symbol-value) (define-vop (%compare-and-swap-symbol-value)
@ -276,6 +298,7 @@
;; Even worse: don't supply old=NO-TLS-VALUE with a symbol whose ;; Even worse: don't supply old=NO-TLS-VALUE with a symbol whose
;; tls-index=0, because that would succeed, assigning NEW to each ;; tls-index=0, because that would succeed, assigning NEW to each
;; symbol in existence having otherwise no thread-local value. ;; symbol in existence having otherwise no thread-local value.
(check-alivep symbol other-pointer-lowtag)
#+sb-thread (progn #+sb-thread (progn
(inst mov :dword cell (if (sc-is symbol immediate) (inst mov :dword cell (if (sc-is symbol immediate)
(symbol-tls-index-ea symbol) (symbol-tls-index-ea symbol)
@ -287,8 +310,7 @@
(emit-symbol-write-barrier symbol nil cell (vop-nth-arg 2 vop) new) (emit-symbol-write-barrier symbol nil cell (vop-nth-arg 2 vop) new)
(get-symbol-value-slot-ea cell symbol) (get-symbol-value-slot-ea cell symbol)
CAS CAS
(load-oldval) (emit-cmpxchg vop t symbol (ea cell) old new rax)
(inst cmpxchg :lock (ea cell) new)
;; FIXME: if :ALWAYS-BOUND then elide the BOUNDP check. ;; FIXME: if :ALWAYS-BOUND then elide the BOUNDP check.
;; But we don't accept a constant or immediate, so how to know ;; But we don't accept a constant or immediate, so how to know
;; what symbol is being CASed? I kind of feel like whether to perform ;; what symbol is being CASed? I kind of feel like whether to perform
@ -544,17 +566,24 @@
(:args (function :scs (descriptor-reg) :target result) (:args (function :scs (descriptor-reg) :target result)
(fdefn :scs (descriptor-reg))) (fdefn :scs (descriptor-reg)))
(:temporary (:sc unsigned-reg) raw) (:temporary (:sc unsigned-reg) raw)
;(:temporary (:sc unsigned-reg) temp)
(:results (result :scs (descriptor-reg))) (:results (result :scs (descriptor-reg)))
(:vop-var vop)
(:generator 38 (:generator 38
(emit-gengc-barrier fdefn nil raw)
(inst mov raw (make-fixup 'closure-tramp :assembly-routine)) (inst mov raw (make-fixup 'closure-tramp :assembly-routine))
(inst cmp :byte (ea (- fun-pointer-lowtag) function) (inst cmp :byte (ea (- fun-pointer-lowtag) function) simple-fun-widetag)
simple-fun-widetag)
(inst cmov :e raw (inst cmov :e raw
(ea (- (* simple-fun-self-slot n-word-bytes) fun-pointer-lowtag) function)) (ea (- (* simple-fun-self-slot n-word-bytes) fun-pointer-lowtag) function))
(storew function fdefn fdefn-fun-slot other-pointer-lowtag) (inst push raw)
(storew raw fdefn fdefn-raw-addr-slot other-pointer-lowtag) (inst push function)
(inst push fdefn)
(invoke-asm-routine 'call 'set-fdefn-fun vop)
;(emit-store vop t fdefn (object-slot-ea fdefn fdefn-fun-slot other-pointer-lowtag)
; function temp)
;(emit-store vop t fdefn (object-slot-ea fdefn fdefn-raw-addr-slot other-pointer-lowtag)
; raw temp 'raw-addr)
(move result function))) (move result function)))
#+immobile-code #+immobile-code
(progn (progn
(define-vop (set-direct-callable-fdefn-fun) (define-vop (set-direct-callable-fdefn-fun)
@ -777,12 +806,19 @@
(value :scs (any-reg descriptor-reg))) (value :scs (any-reg descriptor-reg)))
(:arg-types * tagged-num *) (:arg-types * tagged-num *)
(:temporary (:sc unsigned-reg) val-temp) (:temporary (:sc unsigned-reg) val-temp)
(:vop-var vop)
(:generator 4 (:generator 4
(let ((ea (ea (- (* funcallable-instance-info-offset n-word-bytes) fun-pointer-lowtag) (let ((ea (ea (- (* funcallable-instance-info-offset n-word-bytes) fun-pointer-lowtag)
object index (index-scale n-word-bytes index)))) object index (index-scale n-word-bytes index))))
(inst lea val-temp ea)
(inst push value)
(inst push val-temp)
(inst push object)
(invoke-asm-routine 'call 'set-funinstance-ref vop)
#+nil
(pseudo-atomic () (pseudo-atomic ()
(emit-code-page-gengc-barrier object val-temp) (emit-code-page-gengc-barrier object val-temp)
(emit-store ea value val-temp)))))) (emit-store vop t object ea value val-temp))))))
(define-vop (closure-ref) (define-vop (closure-ref)
(:args (object :scs (descriptor-reg))) (:args (object :scs (descriptor-reg)))
@ -796,7 +832,6 @@
(value :scs (descriptor-reg any-reg))) (value :scs (descriptor-reg any-reg)))
(:info offset dx) (:info offset dx)
(:vop-var vop) (:vop-var vop)
;; temp is wasted if we don't need a barrier, which we almost never do
(:temporary (:sc unsigned-reg) temp) (:temporary (:sc unsigned-reg) temp)
(:generator 4 (:generator 4
(unless dx (unless dx
@ -806,7 +841,11 @@
(when (and (not (singleton-p scs)) (when (and (not (singleton-p scs))
(member descriptor-reg-sc-number scs)) (member descriptor-reg-sc-number scs))
(emit-gengc-barrier object nil temp (vop-nth-arg 1 vop) value)))) (emit-gengc-barrier object nil temp (vop-nth-arg 1 vop) value))))
(storew value object (+ closure-info-offset offset) fun-pointer-lowtag))) (emit-store vop
(and (not dx) (neq (third (vop-codegen-info vop)) :pseudo-atomic))
object
(object-slot-ea object (+ closure-info-offset offset) fun-pointer-lowtag)
value temp)))
(define-vop (closure-init-from-fp) (define-vop (closure-init-from-fp)
(:args (object :scs (descriptor-reg))) (:args (object :scs (descriptor-reg)))
@ -858,14 +897,24 @@
;;; all the more so if we are permitted to optimize the slot order of the defstruct ;;; all the more so if we are permitted to optimize the slot order of the defstruct
;;; by putting all tagged slots together, then all raw slots together. ;;; by putting all tagged slots together, then all raw slots together.
;;; ;;;
(define-vop (instance-set-multiple) ;;; TODO: for cmsgc emit:
;;; begin pseudo-atomic
;;; if barrier on go slow-path
;;; emit-store ...
;;; end-pseudo-atomic
;;; slow-path:
;;; end-pseudo-atomic
;;; emit-store-slow ...
#+nil (define-vop (instance-set-multiple)
(:args (instance :scs (descriptor-reg)) (:args (instance :scs (descriptor-reg))
(values :more t :scs (descriptor-reg constant immediate))) (values :more t :scs (descriptor-reg constant immediate)))
(:temporary (:sc unsigned-reg) val-temp) (:temporary (:sc unsigned-reg) val-temp)
;; Would like to try to store adjacent 0s (and/or NILs) using 16 byte stores. ;; Would like to try to store adjacent 0s (and/or NILs) using 16 byte stores.
(:temporary (:sc int-sse-reg) xmm-temp) (:temporary (:sc int-sse-reg) xmm-temp)
(:info indices) (:info indices)
(:vop-var vop)
(:generator 1 (:generator 1
(error "Should not get here")
(let* ((max-index (reduce #'max indices)) (let* ((max-index (reduce #'max indices))
;;(min-index (reduce #'min indices)) ;;(min-index (reduce #'min indices))
;;(count (length indices)) ;;(count (length indices))
@ -917,7 +966,7 @@
(inst mov val-temp val) (inst mov val-temp val)
(inst mov ea val-temp)) (inst mov ea val-temp))
(t (t
(emit-store ea val val-temp))) (emit-store vop t instance ea val val-temp)))
(unless indices (return))))) (unless indices (return)))))
(aver (not values)))) (aver (not values))))
@ -1061,6 +1110,7 @@
(move rdx old-hi) (move rdx old-hi)
(move rbx new-lo) (move rbx new-lo)
(move rcx new-hi) (move rcx new-hi)
;; FIXME: needs help for concurrent GC
(inst cmpxchg16b :lock memory-operand) (inst cmpxchg16b :lock memory-operand)
;; RDX:RAX hold the actual old contents of memory. ;; RDX:RAX hold the actual old contents of memory.
;; Manually analyze result lifetimes to avoid clobbering. ;; Manually analyze result lifetimes to avoid clobbering.

View file

@ -174,8 +174,9 @@
(:node-var node) (:node-var node)
(:note "float to pointer coercion") (:note "float to pointer coercion")
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other double-float-widetag double-float-size y node nil thread-tn) (alloc-other double-float-widetag double-float-size y node (list temp temp2) thread-tn)
(inst movsd (ea-for-df-desc y) x))) (inst movsd (ea-for-df-desc y) x)))
(define-move-vop move-from-double :move (define-move-vop move-from-double :move
(double-reg) (descriptor-reg)) (double-reg) (descriptor-reg))
@ -185,8 +186,9 @@
(:temporary (:sc unsigned-reg) bits) (:temporary (:sc unsigned-reg) bits)
(:node-var node) (:node-var node)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 3 (:generator 3
(alloc-other double-float-widetag double-float-size copy node nil thread-tn) (alloc-other double-float-widetag double-float-size copy node (list temp temp2) thread-tn)
(loadw bits x double-float-value-slot other-pointer-lowtag) (loadw bits x double-float-value-slot other-pointer-lowtag)
(storew bits copy double-float-value-slot other-pointer-lowtag))) (storew bits copy double-float-value-slot other-pointer-lowtag)))
@ -239,8 +241,10 @@
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:note "complex float to pointer coercion") (:note "complex float to pointer coercion")
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other complex-single-float-widetag complex-single-float-size y node nil thread-tn) (alloc-other complex-single-float-widetag complex-single-float-size y node
(list temp temp2) thread-tn)
(inst movlps (ea-for-csf-data-desc y) x))) (inst movlps (ea-for-csf-data-desc y) x)))
(define-move-vop move-from-complex-single :move (define-move-vop move-from-complex-single :move
(complex-single-reg) (descriptor-reg)) (complex-single-reg) (descriptor-reg))
@ -251,8 +255,10 @@
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:note "complex float to pointer coercion") (:note "complex float to pointer coercion")
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other complex-double-float-widetag complex-double-float-size y node nil thread-tn) (alloc-other complex-double-float-widetag complex-double-float-size y node
(list temp temp2) thread-tn)
(inst movapd (ea-for-cdf-data-desc y) x))) (inst movapd (ea-for-cdf-data-desc y) x)))
(define-move-vop move-from-complex-double :move (define-move-vop move-from-complex-double :move
(complex-double-reg) (descriptor-reg)) (complex-double-reg) (descriptor-reg))

View file

@ -23,7 +23,7 @@
#+sb-simd-pack-256 #+sb-simd-pack-256
(import '(sb-vm::int-avx2-reg sb-vm::double-avx2-reg sb-vm::single-avx2-reg)) (import '(sb-vm::int-avx2-reg sb-vm::double-avx2-reg sb-vm::single-avx2-reg))
(import '(sb-vm::tn-byte-offset sb-vm::tn-reg sb-vm::reg-name (import '(sb-vm::tn-byte-offset sb-vm::tn-reg sb-vm::reg-name
sb-vm::frame-byte-offset sb-vm::rip-tn sb-vm::rbp-tn sb-vm::frame-byte-offset sb-vm::rip-tn sb-vm::rbp-tn sb-vm::rsp-tn
sb-vm::gpr-tn-p sb-vm::stack-tn-p sb-c::tn-reads sb-c::tn-writes sb-vm::gpr-tn-p sb-vm::stack-tn-p sb-c::tn-reads sb-c::tn-writes
sb-vm::ymm-reg sb-vm::ymm-reg
sb-vm::registers sb-vm::float-registers sb-vm::stack))) ; SB names sb-vm::registers sb-vm::float-registers sb-vm::stack))) ; SB names

View file

@ -13,6 +13,11 @@
;;;; instruction-like macros ;;;; instruction-like macros
(defun cons-stats-v ()
(+ (static-data-collection-vector)
(ash vector-data-offset word-shift)
(- other-pointer-lowtag)))
;;; This used to be a macro (and still is on the other platforms) but ;;; This used to be a macro (and still is on the other platforms) but
;;; the support for SC-dependent move instructions needed here makes ;;; the support for SC-dependent move instructions needed here makes
;;; that expand into so large an expression that the resulting code ;;; that expand into so large an expression that the resulting code
@ -107,8 +112,8 @@
;;; assert that alloc-region->free_pointer and ->end_addr can be accessed ;;; assert that alloc-region->free_pointer and ->end_addr can be accessed
;;; using a single byte displacement from thread-tn ;;; using a single byte displacement from thread-tn
(eval-when (:compile-toplevel) #+nil(eval-when (:compile-toplevel)
(aver (<= (1+ thread-boxed-tlab-slot) 15)) ;(aver (<= (1+ thread-boxed-tlab-slot) 15))
(aver (<= (1+ thread-mixed-tlab-slot) 15)) (aver (<= (1+ thread-mixed-tlab-slot) 15))
(aver (<= (1+ thread-cons-tlab-slot) 15))) (aver (<= (1+ thread-cons-tlab-slot) 15)))
@ -217,31 +222,53 @@
#+(and sb-thread (not gs-seg)) 'thread-tn #+(and sb-thread (not gs-seg)) 'thread-tn
#-(and sb-thread (not gs-seg)) 'rbp-tn)) #-(and sb-thread (not gs-seg)) 'rbp-tn))
(defun emit-begin-pseudo-atomic () (defun emit-begin-pseudo-atomic ()
#-sb-safepoint (inst mov (pa-bits-ea) (nonzero-bits))) #-sb-safepoint (inst mov (pa-bits-ea) (nonzero-bits))
#+nil
(progn (let ((foo (gen-label)))
(inst mov (pa-bits-ea) (nonzero-bits))
(inst call foo)
(emit-label foo)
(inst pop (thread-slot-ea thread-et-bzeroing-slot)))))
(defun emit-end-pseudo-atomic () (defun emit-end-pseudo-atomic ()
#+sb-safepoint (emit-safepoint) #+sb-safepoint (emit-safepoint)
#-sb-safepoint #-sb-safepoint
(assemble () (assemble ()
;(inst mov :qword (thread-slot-ea thread-et-bzeroing-slot) 0)
(inst xor (pa-bits-ea) (nonzero-bits)) (inst xor (pa-bits-ea) (nonzero-bits))
(inst jmp :z OUT) (inst jmp :z OUT)
;; if PAI was set, interrupts were disabled at the same time ;; if PAI was set, interrupts were disabled at the same time
;; using the process signal mask. ;; using the process signal mask.
#+int1-breakpoints (inst icebp) #+int1-breakpoints (inst icebp)
#-int1-breakpoints (inst break pending-interrupt-trap) #-int1-breakpoints (inst break pending-interrupt-trap)
OUT))) OUT
)))
;;; This macro is purposely unhygienic with respect to THREAD-TN, ;;; This macro is purposely unhygienic with respect to THREAD-TN,
;;; which is either a global symbol macro, or a LET-bound variable, ;;; which is either a global symbol macro, or a LET-bound variable,
;;; depending on #+gs-seg. ;;; depending on #+gs-seg.
(defmacro pseudo-atomic ((&key ((:thread-tn thread)) elide-if (default-exit t)) (defvar *in-pseudoatomic* nil)
(defmacro pseudo-atomic ((&key ((:thread-tn thread)) elide-if (default-exit t) (sml-check t))
&body forms) &body forms)
(declare (ignorable thread)) (declare (ignorable thread sml-check))
`(macrolet ((exit-pseudo-atomic () '(emit-end-pseudo-atomic))) `(macrolet ((exit-pseudo-atomic () '(emit-end-pseudo-atomic)))
(unless ,elide-if (unless ,elide-if
(emit-begin-pseudo-atomic)) (emit-begin-pseudo-atomic))
(assemble () ,@forms) (let ((*in-pseudoatomic* t)) (assemble () ,@forms))
(when (and ,default-exit (not ,elide-if)) (when (and ,default-exit (not ,elide-if))
(exit-pseudo-atomic)))) (exit-pseudo-atomic)
#+nil
(when ,sml-check
(assemble ()
(inst test :byte (static-symbol-value-ea '*sml-check-flag*) 2)
(inst jmp :z NO-PHASE-CHANGE)
(inst call (ea (make-fixup 'gc-check :assembly-routine*)))
NO-PHASE-CHANGE
(inst cmp :qword (static-symbol-value-ea '*n-malloc-segments-to-release*) 0)
(inst jmp :z NO-RELEASE-MEM)
(inst call (ea (make-fixup 'release-malloc-segments :assembly-routine*)))
NO-RELEASE-MEM
)))))
;;;; indexed references ;;;; indexed references
@ -261,10 +288,63 @@
(* max-offset sb-vm:n-word-bytes)) (* max-offset sb-vm:n-word-bytes))
scale))) scale)))
(defun bignum-index-check (bignum index addend vop)
(declare (ignore bignum index addend vop))
;; Conditionally compile this in to sanity-check the bignum logic
#+nil
(let ((ok (gen-label)))
(cond ((and (tn-p index) (not (constant-tn-p index)))
(aver (sc-is index any-reg))
(inst lea :dword temp-reg-tn (ea (fixnumize addend) index))
(inst shr :dword temp-reg-tn n-fixnum-tag-bits))
(t
(inst mov temp-reg-tn (+ (if (tn-p index) (tn-value index) index) addend))))
(inst cmp :dword temp-reg-tn (ea (- 1 other-pointer-lowtag) bignum))
(inst jmp :b ok)
(inst break halt-trap)
(emit-label ok)))
;;; used for: INSTANCE-INDEX-SET %CLOSURE-INDEX-SET
;;; SB-BIGNUM:%BIGNUM-SET %SET-ARRAY-DIMENSION %SET-VECTOR-RAW-BITS
(defmacro define-full-setter (name type offset lowtag scs el-type translate)
(let ((barrierp (case name
(%closure-index-set '(not (sc-is value any-reg)))
((instance-index-set #|%weakvec-set|#)
'(or (not (sc-is index immediate))
(slot-type-requires-gcbarrier args (tn-value index))))
(%weakvec-set :weak))))
`(define-vop (,name)
(:translate ,translate)
(:arg-refs args)
(:policy :fast-safe)
(:args (object :scs (descriptor-reg))
(index :scs (any-reg immediate signed-reg unsigned-reg))
(value :scs ,scs))
(:arg-types ,type tagged-num ,el-type)
(:vop-var vop)
(:temporary (:sc unsigned-reg) val-temp)
(:arg-refs args)
(:generator 4
,@(when (eq translate 'sb-bignum:%bignum-set)
'((bignum-index-check object index 0 vop)))
(let ((ea (if (sc-is index immediate)
(ea (- (* (+ ,offset (tn-value index)) n-word-bytes) ,lowtag)
object)
(ea (- (* ,offset n-word-bytes) ,lowtag)
object index (index-scale n-word-bytes index)))))
,@(when (member name '(instance-index-set %closure-index-set %weakvec-set))
'((emit-gengc-barrier object nil val-temp (vop-nth-arg 2 vop) value)))
(emit-store vop ,barrierp object ea value val-temp))))))
(defmacro define-full-compare-and-swap (defmacro define-full-compare-and-swap
(name type offset lowtag scs el-type &optional translate) (name type offset lowtag scs el-type &optional translate)
`(progn (let ((barrierp (case name
(define-vop (,name) (%instance-cas
'(or (not (sc-is index immediate))
(slot-type-requires-gcbarrier args (tn-value index))))
(%compare-and-swap-svref t)
(t nil))))
`(define-vop (,name)
(:translate ,translate) (:translate ,translate)
(:policy :fast-safe) (:policy :fast-safe)
(:args (object :scs (descriptor-reg) :to :eval) (:args (object :scs (descriptor-reg) :to :eval)
@ -282,6 +362,7 @@
#|:from (:argument 2)|# :to :result :target value) rax) #|:from (:argument 2)|# :to :result :target value) rax)
(:results (value :scs ,scs)) (:results (value :scs ,scs))
(:result-types ,el-type) (:result-types ,el-type)
(:arg-refs args)
(:generator 5 (:generator 5
(let ((ea (ea (- (* (+ (if (sc-is index immediate) (tn-value index) 0) ,offset) (let ((ea (ea (- (* (+ (if (sc-is index immediate) (tn-value index) 0) ,offset)
n-word-bytes) n-word-bytes)
@ -297,26 +378,14 @@
;; store barrier affects only the object's base address ;; store barrier affects only the object's base address
'((emit-gengc-barrier object nil rax (vop-nth-arg 3 vop) new-value))) '((emit-gengc-barrier object nil rax (vop-nth-arg 3 vop) new-value)))
((%raw-instance-cas/word %raw-instance-cas/signed-word))) ((%raw-instance-cas/word %raw-instance-cas/signed-word)))
(move-immediate rax (encode-value-if-immediate old-value ,(and (memq 'any-reg scs) t))) ,(if barrierp ; the s-expression is non-nil though may eval to nil
(inst cmpxchg :lock ea new-value) `(emit-cmpxchg vop ,barrierp object ea old-value new-value rax)
`(progn
(move-immediate rax
(encode-value-if-immediate old-value ,(and (memq 'any-reg scs) t)))
(inst cmpxchg :lock ea new-value)))
(move value rax)))))) (move value rax))))))
(defun bignum-index-check (bignum index addend vop)
(declare (ignore bignum index addend vop))
;; Conditionally compile this in to sanity-check the bignum logic
#+nil
(let ((ok (gen-label)))
(cond ((and (tn-p index) (not (constant-tn-p index)))
(aver (sc-is index any-reg))
(inst lea :dword temp-reg-tn (ea (fixnumize addend) index))
(inst shr :dword temp-reg-tn n-fixnum-tag-bits))
(t
(inst mov temp-reg-tn (+ (if (tn-p index) (tn-value index) index) addend))))
(inst cmp :dword temp-reg-tn (ea (- 1 other-pointer-lowtag) bignum))
(inst jmp :b ok)
(inst break halt-trap)
(emit-label ok)))
(defmacro define-full-reffer (name type offset lowtag scs el-type &optional translate) (defmacro define-full-reffer (name type offset lowtag scs el-type &optional translate)
`(progn `(progn
(define-vop (,name) (define-vop (,name)
@ -408,27 +477,3 @@
(let ((ea (ea (- (* (+ ,offset index addend) n-word-bytes) ,lowtag) object))) (let ((ea (ea (- (* (+ ,offset index addend) n-word-bytes) ,lowtag) object)))
,@(trap '(emit-constant (+ index addend))) ,@(trap '(emit-constant (+ index addend)))
(inst mov value ea))))))) (inst mov value ea)))))))
;;; used for: INSTANCE-INDEX-SET %CLOSURE-INDEX-SET
;;; SB-BIGNUM:%BIGNUM-SET %SET-ARRAY-DIMENSION %SET-VECTOR-RAW-BITS
(defmacro define-full-setter (name type offset lowtag scs el-type translate)
`(define-vop (,name)
(:translate ,translate)
(:policy :fast-safe)
(:args (object :scs (descriptor-reg))
(index :scs (any-reg immediate signed-reg unsigned-reg))
(value :scs ,scs))
(:arg-types ,type tagged-num ,el-type)
(:vop-var vop)
(:temporary (:sc unsigned-reg) val-temp)
(:generator 4
,@(when (eq translate 'sb-bignum:%bignum-set)
'((bignum-index-check object index 0 vop)))
(let ((ea (if (sc-is index immediate)
(ea (- (* (+ ,offset (tn-value index)) n-word-bytes) ,lowtag)
object)
(ea (- (* ,offset n-word-bytes) ,lowtag)
object index (index-scale n-word-bytes index)))))
,@(when (member name '(instance-index-set %closure-index-set %weakvec-set))
'((emit-gengc-barrier object nil val-temp (vop-nth-arg 2 vop) value)))
(emit-store ea value val-temp)))))

View file

@ -63,28 +63,140 @@
(inst and :dword scratch-reg card-index-mask) (inst and :dword scratch-reg card-index-mask)
(inst mov :byte (ea gc-card-table-reg-tn scratch-reg) CARD-MARKED)) (inst mov :byte (ea gc-card-table-reg-tn scratch-reg) CARD-MARKED))
(defun emit-store (ea value val-temp) (defun branch-if-barrier-on (where)
(sc-case value (when *in-pseudoatomic* (error "can't emit CMS barrier if already pseudoatomic"))
(immediate (inst cmp :byte (thread-slot-ea thread-gc-phase-slot) GC-PHASE-SYNC1)
(let ((bits (encode-value-if-immediate value))) (let ((label (gen-label)))
;; Try to move imm-to-mem if BITS fits (emit-label label)
(acond ((or (and (fixup-p bits) (push label (sb-assem::asmstream-pseudo-atomic-locs sb-assem:*asmstream*)))
;; immobile-object fixups must fit in 32 bits (inst jmp :ae where))
(eq (fixup-flavor bits) :immobile-symbol)
bits)
(plausible-signed-imm32-operand-p bits))
(inst mov :qword ea it))
(t
(inst mov val-temp bits)
(inst mov ea val-temp)))))
(constant
(inst mov val-temp value)
(inst mov :qword ea val-temp))
(t
(inst mov :qword ea value))))
;; This vop's sole purpose is to provide the implementation of value-cell-set. ;;; FIXME: if and when I implement SIGUSR2 as the signal to cooperate
;; It could be removed, for x86-64 anyway. ;;; with GC - so that we don't switch over to safepoints -
;;; then the only instruction sequences which will be _implicitly_ pseudo-atomic
;;; will be:
;;; inst cmp [phase], 2
;;; inst jmp :a elsewhere
;;; inst mov [ea], something # any number of stores
;;; so if the interrupt handler sees that it has been interrupted in lisp code
;;; at a mov to memory, it will emulate the mov without allowing a phase change.
;;; It will NOT work to have either of the following:
;;; inst cmp [phase], 2 | inst cmp [phase], 2
;;; inst jmp :a elsewhere | inst jmp :a elsewhere
;;; inst mov temp, [rip-n] | inst mov temp, imm <-- interrupted here
;;; inst mov [ea], temp | inst mov [ea], temp
;;; In this instruction sequence, if we allow the interrupt to take place,
;;; and it changes the GC phase, then the preceding comparison answer can not be
;;; taken as correct. Perhaps we should have branched to the fallback code to
;;; perform a barrier, but we did not, because the phase looked like it was ASYNC.
;;; Somehow the GC phase has to be examined just before performing the store.
;;;
(defun emit-store (vop barrierp object ea value val-temp
&optional slot-name
&aux (asm-routine
(case barrierp
(:weak 'weak-vector-set)
(:untagged 'gc-barrier-store-untagged)
(t 'gc-barrier-store)))
notinline done)
(declare (type (member t nil :weak :untagged) barrierp))
(declare (ignorable slot-name))
(when (and barrierp (stack-consed-p object))
;; barriers do not pertain to stack objects
(setq barrierp nil))
#+smlgc-telemetry
(inst inc :qword (thread-slot-ea (if (stack-consed-p object)
thread-ct-stack-obj-stores-slot
thread-ct-heap-obj-stores-slot)))
(labels ((imm (operation)
(let ((bits (encode-value-if-immediate value)))
;; Try to move imm-to-mem if BITS fits
(acond ((or (and (fixup-p bits)
;; immobile-object fixups must fit in 32 bits
(eq (fixup-flavor bits) :immobile-symbol)
bits)
(plausible-signed-imm32-operand-p bits))
(if (eq operation 'push)
(inst push it) ; real good
(progn (barrier)
(inst mov :qword ea it))))
(t
(inst mov val-temp bits)
(if (eq operation 'push)
(inst push val-temp)
(progn (barrier)
(inst mov ea val-temp)))))))
(barrier ()
(when barrierp
(branch-if-barrier-on (setq notinline (gen-label))))))
(when (eq barrierp :weak) ; always call C
(if (sc-is value immediate) (imm 'push) (inst push value))
(inst lea val-temp ea)
(inst push val-temp)
(inst push (encode-value-if-immediate object))
(invoke-asm-routine 'call asm-routine vop)
(return-from emit-store))
(sc-case value
(immediate (imm 'store))
(constant
(inst mov val-temp value)
(barrier)
(inst mov :qword ea val-temp))
(t
(barrier)
(inst mov :qword ea value)))
(when barrierp
(emit-label (setq done (gen-label)))
(assemble (:elsewhere)
(emit-label NOTINLINE)
(if (sc-is value immediate) (imm 'push) (inst push value))
(inst lea val-temp ea)
(inst push val-temp)
(inst push (encode-value-if-immediate object))
(invoke-asm-routine 'call asm-routine vop)
(inst jmp DONE)))))
;;; RAX is loaded with the expected oldval. Return from the slow path
;;; is the same as if by the fast path (actual oldval is in RAX)
(defun emit-cmpxchg (vop barrierp object ea old new rax
&aux (asm-routine
(if (eq barrierp :untagged)
'gc-barrier-cmpxchg-untagged
'gc-barrier-cmpxchg))
(notinline (gen-label)))
(unless barrierp
(inst mov rax (encode-value-if-immediate old))
(inst cmpxchg :lock ea new)
(return-from emit-cmpxchg))
(assemble ()
;; optimistically assume we're taking the fast path
(inst mov rax (encode-value-if-immediate old))
(branch-if-barrier-on NOTINLINE)
(inst cmpxchg :lock ea new)
DONE
(assemble (:elsewhere)
(emit-label NOTINLINE)
(inst push new)
(inst lea rax ea)
(inst push rax)
(inst push (encode-value-if-immediate object))
(inst mov rax (encode-value-if-immediate old))
(invoke-asm-routine 'call asm-routine vop)
(inst jmp done))))
;;; CELL-REF and CELL-SET are used to define VOPs like CAR, where the
;;; offset to be read or written is a property of the VOP used.
(define-vop (cell-ref)
(:args (object :scs (descriptor-reg)))
(:results (value :scs (descriptor-reg any-reg)))
(:variant-vars offset lowtag)
(:policy :fast-safe)
(:generator 4
(check-alivep object lowtag)
(loadw value object offset lowtag)))
;; This vop's sole purpose is to be an ancestor for other vops, to assign
;; default operands, policy, and generator.
(define-vop (cell-set) (define-vop (cell-set)
(:args (object :scs (descriptor-reg)) (:args (object :scs (descriptor-reg))
(value :scs (descriptor-reg any-reg immediate))) (value :scs (descriptor-reg any-reg immediate)))
@ -93,9 +205,9 @@
(:temporary (:sc unsigned-reg) val-temp) (:temporary (:sc unsigned-reg) val-temp)
(:vop-var vop) (:vop-var vop)
(:generator 4 (:generator 4
(check-alivep object lowtag)
(emit-gengc-barrier object nil val-temp (vop-nth-arg 1 vop) value) (emit-gengc-barrier object nil val-temp (vop-nth-arg 1 vop) value)
(let ((ea (object-slot-ea object offset lowtag))) (emit-store vop t object (object-slot-ea object offset lowtag) value val-temp)))
(emit-store ea value val-temp))))
;;; X86 special ;;; X86 special
(define-vop (cell-xadd) (define-vop (cell-xadd)
@ -286,3 +398,50 @@
;; It wants a function, not a symbol ;; It wants a function, not a symbol
(setf (sb-c::vop-info-optimizer (template-or-lose name)) (setf (sb-c::vop-info-optimizer (template-or-lose name))
(lambda (vop) (sb-c::elide-zero-fill vop)))) (lambda (vop) (sb-c::elide-zero-fill vop))))
;; Placeholders until non-STW GC becomes a reality
#+weak-vector-readbarrier
(progn
(define-full-setter %weakvec-set * vector-data-offset other-pointer-lowtag
(any-reg descriptor-reg) * %weakvec-set)
(define-vop (%weakvec-ref)
(:translate %weakvec-ref)
(:policy :fast-safe)
(:args (object :scs (descriptor-reg))
(index :scs (any-reg signed-reg unsigned-reg)))
(:arg-types * tagged-num)
(:results (value :scs (descriptor-reg)))
(:result-types *)
(:temporary (:sc unsigned-reg) junk)
(:vop-var vop)
(:generator 10
(pseudo-atomic ()
(inst mov junk (ea (make-fixup "weakrefget_ct" :foreign-dataref)))
(inst add :dword :lock (ea junk) 1)
(inst cmp :byte (thread-slot-ea thread-gc-phase-slot) GC-PHASE-ASYNC)
(inst jmp :e FAST)
(inst push object)
(inst push index)
(unless (sc-is index any-reg) (inst shl (ea rsp-tn) n-fixnum-tag-bits)) ; pass as fixnum
(invoke-asm-routine 'call 'weak-vector-ref vop)
FAST
(inst mov value (ea (- (* vector-data-offset n-word-bytes) other-pointer-lowtag)
object index (index-scale n-word-bytes index))))))
(define-vop (%weak-pointer-value)
(:policy :fast-safe)
(:args (weakptr :scs (descriptor-reg)))
(:results (value :scs (descriptor-reg)))
(:vop-var vop)
(:temporary (:sc unsigned-reg) junk)
(:generator 10
(pseudo-atomic ()
(inst mov junk (ea (make-fixup "weakrefget_ct" :foreign-dataref)))
(inst add :dword :lock (ea junk) 1)
(inst cmp :byte (thread-slot-ea thread-gc-phase-slot) GC-PHASE-ASYNC)
(inst jmp :e FAST)
(inst push weakptr)
(invoke-asm-routine 'call 'weak-pointer-ref vop)
FAST
(loadw value weakptr weak-pointer-value-slot other-pointer-lowtag)))))

View file

@ -78,6 +78,7 @@
(any-reg descriptor-reg)) (any-reg descriptor-reg))
(defun move-immediate (target val &optional tmp-tn zeroed) (defun move-immediate (target val &optional tmp-tn zeroed)
(setq zeroed nil)
;; Try to emit the smallest immediate operand if the destination word ;; Try to emit the smallest immediate operand if the destination word
;; is already zeroed. Otherwise a :qword. ;; is already zeroed. Otherwise a :qword.
(cond (cond

View file

@ -22,12 +22,12 @@
;;; This size is supposed to indicate something about the actual granularity ;;; This size is supposed to indicate something about the actual granularity
;;; at which you can map memory. We just hardwire it, but that may or may not ;;; at which you can map memory. We just hardwire it, but that may or may not
;;; be necessary any more. ;;; be necessary any more.
(defconstant +backend-page-bytes+ #+win32 65536 #-win32 32768) (defconstant +backend-page-bytes+ #+win32 65536 #-win32 4096) ; 32768)
;;; The size in bytes of GENCGC pages. A page is the smallest amount of memory ;;; The size in bytes of GENCGC pages. A page is the smallest amount of memory
;;; that a thread can claim for a thread-local region, and also determines ;;; that a thread can claim for a thread-local region, and also determines
;;; the granularity at which we can find the start of a sequence of objects. ;;; the granularity at which we can find the start of a sequence of objects.
(defconstant gencgc-page-bytes 32768) (defconstant gencgc-page-bytes 4096) ; 32768)
;;; The divisor relative to page-bytes which computes the granularity ;;; The divisor relative to page-bytes which computes the granularity
;;; at which writes to old generations are logged. ;;; at which writes to old generations are logged.
#+soft-card-marks (defconstant cards-per-page #+soft-card-marks (defconstant cards-per-page

View file

@ -30,8 +30,9 @@
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:note "SAP to pointer coercion") (:note "SAP to pointer coercion")
(:node-var node) (:node-var node)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 20 (:generator 20
(alloc-other sap-widetag sap-size res node nil thread-tn) (alloc-other sap-widetag sap-size res node (list temp temp2) thread-tn)
(storew sap res sap-pointer-slot other-pointer-lowtag))) (storew sap res sap-pointer-slot other-pointer-lowtag)))
(define-move-vop move-from-sap :move (define-move-vop move-from-sap :move
(sap-reg) (descriptor-reg)) (sap-reg) (descriptor-reg))

View file

@ -96,8 +96,10 @@
(:node-var node) (:node-var node)
(:arg-types ,type) (:arg-types ,type)
(:note "AVX2 to pointer coercion") (:note "AVX2 to pointer coercion")
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other simd-pack-256-widetag simd-pack-256-size y node nil thread-tn) (alloc-other simd-pack-256-widetag simd-pack-256-size y node
(list temp temp2) thread-tn)
(storew (fixnumize ,tag) (storew (fixnumize ,tag)
y simd-pack-256-tag-slot other-pointer-lowtag) y simd-pack-256-tag-slot other-pointer-lowtag)
(let ((ea (object-slot-ea (let ((ea (object-slot-ea
@ -198,8 +200,10 @@
(:result-types t) (:result-types t)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other simd-pack-256-widetag simd-pack-256-size dst node nil thread-tn) (alloc-other simd-pack-256-widetag simd-pack-256-size dst node
(list temp temp2) thread-tn)
;; see +simd-pack-element-types+ ;; see +simd-pack-element-types+
(storew tag dst simd-pack-256-tag-slot other-pointer-lowtag) (storew tag dst simd-pack-256-tag-slot other-pointer-lowtag)
(storew p0 dst simd-pack-256-p0-slot other-pointer-lowtag) (storew p0 dst simd-pack-256-p0-slot other-pointer-lowtag)

View file

@ -90,8 +90,10 @@
(:node-var node) (:node-var node)
(:arg-types ,type) (:arg-types ,type)
(:note "AVX2 to pointer coercion") (:note "AVX2 to pointer coercion")
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other simd-pack-widetag simd-pack-size y node nil thread-tn) (alloc-other simd-pack-widetag simd-pack-size y node
(list temp temp2) thread-tn)
(storew (fixnumize ,tag) (storew (fixnumize ,tag)
y simd-pack-tag-slot other-pointer-lowtag) y simd-pack-tag-slot other-pointer-lowtag)
(let ((ea (object-slot-ea y simd-pack-lo-value-slot other-pointer-lowtag))) (let ((ea (object-slot-ea y simd-pack-lo-value-slot other-pointer-lowtag)))
@ -197,8 +199,9 @@
(:result-types t) (:result-types t)
#+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn) #+gs-seg (:temporary (:sc unsigned-reg :offset 15) thread-tn)
(:node-var node) (:node-var node)
(:temporary (:sc unsigned-reg) temp temp2)
(:generator 13 (:generator 13
(alloc-other simd-pack-widetag simd-pack-size dst node nil thread-tn) (alloc-other simd-pack-widetag simd-pack-size dst node (list temp temp2) thread-tn)
;; see +simd-pack-element-types+ ;; see +simd-pack-element-types+
(storew tag dst simd-pack-tag-slot other-pointer-lowtag) (storew tag dst simd-pack-tag-slot other-pointer-lowtag)
(storew lo dst simd-pack-lo-value-slot other-pointer-lowtag) (storew lo dst simd-pack-lo-value-slot other-pointer-lowtag)

View file

@ -358,8 +358,13 @@
'list)) 'list))
(a (make-array (1+ (slot-offset (car (last slots)))) (a (make-array (1+ (slot-offset (car (last slots))))
:initial-element nil))) :initial-element nil)))
(dolist (slot slots a) (dolist (slot slots)
(setf (aref a (slot-offset slot)) (slot-name slot))))) (setf (aref a (slot-offset slot)) (slot-name slot)))
(let ((i sb-vm::thread-ap4-slot))
(setf (aref a i) "ap4.freebit.ptr"
(aref a (+ i 1)) "ap4.freebit.mask"
(aref a (+ i 2)) "ap4.freeptr"))
a))
;;; Prints a memory reference to STREAM. VALUE is a list of ;;; Prints a memory reference to STREAM. VALUE is a list of
;;; (BASE-REG OFFSET INDEX-REG INDEX-SCALE), where any component may be ;;; (BASE-REG OFFSET INDEX-REG INDEX-SCALE), where any component may be

View file

@ -86,7 +86,12 @@ C_SRC = $(COMMON_SRC) ${ARCH_SRC} ${OS_SRC} ${GC_SRC}
SRCS = $(C_SRC) ${ASSEM_SRC} SRCS = $(C_SRC) ${ASSEM_SRC}
OBJS = $(C_SRC:.c=.o) $(ASSEM_SRC:.S=.o) ../../tlsf-bsd/tlsf/tlsf.o SMLSHARPGC_OBJS = ../../smlsharpgc/control.o ../../smlsharpgc/error.o \
../../smlsharpgc/heap_concurrent.o ../../smlsharpgc/lispobj.o ../../smlsharpgc/xmalloc.o
../../smlsharpgc/%.o: CFLAGS+=-I../../smlsharpgc -DHAVE_GENESIS_CONFIG -DHAVE_CONFIG_H -DWITHOUT_MASSIVETHREADS
OBJS = $(C_SRC:.c=.o) $(ASSEM_SRC:.S=.o) ../../tlsf-bsd/tlsf/tlsf.o $(SMLSHARPGC_OBJS)
LIBS = ${OS_LIBS} $(LDLIBS) -lm LIBS = ${OS_LIBS} $(LDLIBS) -lm
@ -101,6 +106,7 @@ ldb: $(LIBSBCL)
$(TARGET): $(LIBSBCL) $(TARGET): $(LIBSBCL)
$(CC) ${LINKFLAGS} -o $@ $(USE_LIBSBCL) $(LIBS) $(CC) ${LINKFLAGS} -o $@ $(USE_LIBSBCL) $(LIBS)
$(SBCL_PAXCTL) $@ $(SBCL_PAXCTL) $@
#rm -f mylib.a ; ar cqD mylib.a $(filter-out main.o,$(USE_LIBSBCL))
# tests/heap-reloc/fake-mman.c assumes #+linux, so this recipe # tests/heap-reloc/fake-mman.c assumes #+linux, so this recipe
# only works on linux. # only works on linux.
@ -178,8 +184,8 @@ TAGS tags: $(SRCS) $(HEADERS) $(INC)
@etags --language=c $(SRCS) $(HEADERS) $(INC) || true @etags --language=c $(SRCS) $(HEADERS) $(INC) || true
clean: clean:
-rm -f *.[do] ../../tlsf-bsd/tlsf/tlsf.o $(TARGET) *.tmp libsbcl.a sbcl.h \ -rm -f *.[do] ../../tlsf-bsd/tlsf/tlsf.o ../../smlsharpgc/*.o $(TARGET) *.tmp libsbcl.a \
ldb unit-tests libsbcl.a shrinkwrap-sbcl* sbcl.mk core ldb unit-tests libsbcl.a shrinkwrap-sbcl* sbcl.mk core sbcl.h
%.d: %.c sbcl.h %.d: %.c sbcl.h
@$(CC) $(DEPEND_FLAGS) $(CPPFLAGS) $< > $@.tmp; \ @$(CC) $(DEPEND_FLAGS) $(CPPFLAGS) $< > $@.tmp; \

View file

@ -698,7 +698,7 @@ struct visitor {
long nwords; long nwords;
}; };
static void visit(lispobj obj, void* arg) { static void visit(__attribute__((unused)) lispobj parent, lispobj obj, void* arg) {
struct visitor* v = arg; struct visitor* v = arg;
if (find_containing_arena(obj) == v->arena) v->nwords += object_size(native_pointer(obj)); if (find_containing_arena(obj) == v->arena) v->nwords += object_size(native_pointer(obj));
} }
@ -707,8 +707,7 @@ size_t count_arena_live_bytes(lispobj arena) {
struct visitor v; struct visitor v;
v.arena = arena; v.arena = arena;
v.nwords = 0; v.nwords = 0;
struct grvisit_context* c = struct grvisit_context* c = visit_heap_from_roots(&h, visit, &v, 0, NULL, 0);
visit_heap_from_static_roots(&h, visit, &v);
hopscotch_destroy(&h); hopscotch_destroy(&h);
free(c); free(c);
return v.nwords * N_WORD_BYTES; return v.nwords * N_WORD_BYTES;

View file

@ -558,6 +558,8 @@ static void print_backtrace_frame(char *pc, void *fp, int i, FILE *f) {
#endif #endif
struct code *code = (void*)component_ptr_from_pc(pc); struct code *code = (void*)component_ptr_from_pc(pc);
if (code) { if (code) {
fprintf(f, "= id %x + %x ", code_serialno(code),
(int)((char*)pc - (char*)code_text_start(code)));
struct compiled_debug_fun *df = debug_function_from_pc(code, pc); struct compiled_debug_fun *df = debug_function_from_pc(code, pc);
if (df) if (df)
print_entry_name(barrier_load(&df->name), f); print_entry_name(barrier_load(&df->name), f);
@ -614,6 +616,9 @@ log_backtrace_from_fp(struct thread* th, void *fp, int nframes, int start, FILE
void backtrace_from_fp(void *fp, int nframes, int start) { void backtrace_from_fp(void *fp, int nframes, int start) {
log_backtrace_from_fp(get_sb_vm_thread(), fp, nframes, start, stdout); log_backtrace_from_fp(get_sb_vm_thread(), fp, nframes, start, stdout);
} }
extern void backtrace_to_file(FILE* f) {
log_backtrace_from_fp(get_sb_vm_thread(), __builtin_frame_address(0), 100, 0, f);
}
void print_backtrace_from_context(os_context_t *context, int nframes, FILE* file) { void print_backtrace_from_context(os_context_t *context, int nframes, FILE* file) {
void *fp = (void *)os_context_frame_pointer(context); void *fp = (void *)os_context_frame_pointer(context);
@ -659,8 +664,11 @@ int simple_fun_index_from_pc(struct code* code, char *pc)
static bool __attribute__((unused)) print_lisp_fun_name(char* pc) static bool __attribute__((unused)) print_lisp_fun_name(char* pc)
{ {
struct code* code; struct code* code;
if (gc_managed_heap_space_p((uword_t)pc) && extern uword_t* codeblob_from_interior_ptr(void* addr);
(code = (void*)component_ptr_from_pc(pc)) != 0) { code = (void*)codeblob_from_interior_ptr(pc);
if (!code && gc_managed_heap_space_p((uword_t)pc))
code = (void*)component_ptr_from_pc(pc);
if (code) {
struct compiled_debug_fun* df = debug_function_from_pc(code, pc); struct compiled_debug_fun* df = debug_function_from_pc(code, pc);
if (df) { if (df) {
fprintf(stderr, " %p [", pc); fprintf(stderr, " %p [", pc);
@ -712,7 +720,9 @@ void libunwind_backtrace(struct thread *th, os_context_t *context)
// In case you get no backtrace whatsoever, maybe at least see where the // In case you get no backtrace whatsoever, maybe at least see where the
// signal was received, probably in a function without the standard // signal was received, probably in a function without the standard
// frame pointer setup. // frame pointer setup.
fprintf(stderr, " interrupted @ PC %p\n", (void*)OS_CONTEXT_PC(context)); fprintf(stderr, " interrupted FP=%p, PC=%p\n",
(void*)*os_context_fp_addr(context),
(void*)OS_CONTEXT_PC(context));
if (lispthread->waiting_for != NIL) { if (lispthread->waiting_for != NIL) {
fprintf(stderr, "waiting for %p", (void*)lispthread->waiting_for); fprintf(stderr, "waiting for %p", (void*)lispthread->waiting_for);
if (instancep(lispthread->waiting_for)) { if (instancep(lispthread->waiting_for)) {
@ -747,14 +757,19 @@ void libunwind_backtrace(struct thread *th, os_context_t *context)
do { do {
uword_t offset; uword_t offset;
char *pc; char *pc;
unw_get_reg(&cursor, UNW_TDEP_IP, (uword_t*)&pc); uword_t *bp, *sp;
unw_get_reg(&cursor, UNW_TDEP_IP, (unw_word_t*)&pc);
unw_get_reg(&cursor, UNW_TDEP_BP, (unw_word_t*)&bp);
unw_get_reg(&cursor, UNW_TDEP_SP, (unw_word_t*)&sp);
fprintf(stderr, " %p %p %p ", sp, bp, pc);
if (print_lisp_fun_name(pc)) { if (print_lisp_fun_name(pc)) {
// printed // printed
} else if (!unw_get_proc_name(&cursor, procname, sizeof procname, &offset)) { } else if (!unw_get_proc_name(&cursor, procname, sizeof procname, &offset)) {
fprintf(stderr, " %p [%s]\n", pc, procname); fprintf(stderr, "[%s]", procname);
} else { } else {
fprintf(stderr, " %p ?\n", pc); fprintf(stderr, "?");
} }
putc('\n', stderr);
} while (unw_step(&cursor)); } while (unw_step(&cursor));
#else #else
// If you don't have libunwind, this will almost surely not work, // If you don't have libunwind, this will almost surely not work,

View file

@ -234,6 +234,7 @@ struct heap_adjust {
#include "genesis/cons.h" #include "genesis/cons.h"
#include "genesis/hash-table.h" #include "genesis/hash-table.h"
#include "genesis/vector.h" #include "genesis/vector.h"
#include "genesis/weak-pointer.h"
static inline sword_t calc_adjustment(struct heap_adjust* adj, lispobj x) static inline sword_t calc_adjustment(struct heap_adjust* adj, lispobj x)
{ {
@ -1205,7 +1206,7 @@ void gc_load_corefile_ptes(int card_table_nbits,
// Apply physical page protection as needed. // Apply physical page protection as needed.
// The non-soft-card-mark code is disgusting and I do not understand it. // The non-soft-card-mark code is disgusting and I do not understand it.
if (gen != 0 && ENABLE_PAGE_PROTECTION) { if (0 && gen != 0 && ENABLE_PAGE_PROTECTION) {
#ifdef LISP_FEATURE_SOFT_CARD_MARKS #ifdef LISP_FEATURE_SOFT_CARD_MARKS
page_index_t p; page_index_t p;
for (p = 0; p < next_free_page; ++p) for (p = 0; p < next_free_page; ++p)
@ -1258,6 +1259,32 @@ void gc_load_corefile_ptes(int card_table_nbits,
#endif #endif
} }
int enable_async_gc = 1;
static void gather_weak_objects() {
extern void record_weak_object(lispobj);
lispobj* where = (lispobj*)DYNAMIC_SPACE_START;
lispobj* limit = (lispobj*)dynamic_space_highwatermark();
int tables = 0, vectors = 0;
while (where < limit) {
switch (widetag_of(where)) {
case INSTANCE_WIDETAG:
if (layout_depth2_id(LAYOUT(instance_layout(where))) == HASH_TABLE_LAYOUT_ID
&& hashtable_weakp(((struct hash_table*)where)))
++tables, record_weak_object(make_lispobj(where, INSTANCE_POINTER_LOWTAG));
break;
case WEAK_POINTER_WIDETAG:
// weak pointers that aren't vectors need not be recorded
// since they can't point to managed space.
if (weakptr_vectorp((struct weak_pointer*)where))
++vectors, record_weak_object(make_lispobj(where, OTHER_POINTER_LOWTAG));
break;
}
where += object_size(where);
}
fprintf(stderr, "coreparse: registered %d weak tables, %d weak vectors\n", tables, vectors);
}
/* 'merge_core_pages': Tri-state flag to determine whether we attempt to mark /* 'merge_core_pages': Tri-state flag to determine whether we attempt to mark
* pages as targets for virtual memory deduplication via MADV_MERGEABLE. * pages as targets for virtual memory deduplication via MADV_MERGEABLE.
* 1: Yes * 1: Yes
@ -1333,6 +1360,8 @@ load_core_file(char *file, os_vm_offset_t file_offset, int merge_core_pages)
process_directory(remaining_len / NDIR_ENTRY_LENGTH, process_directory(remaining_len / NDIR_ENTRY_LENGTH,
(struct ndir_entry*)ptr, fd, file_offset, (struct ndir_entry*)ptr, fd, file_offset,
merge_core_pages, spaces, &adj); merge_core_pages, spaces, &adj);
// before write-protecting
if (use_smlgc) gather_weak_objects();
break; break;
case PAGE_TABLE_CORE_ENTRY_TYPE_CODE: case PAGE_TABLE_CORE_ENTRY_TYPE_CODE:
// elements = gencgc-card-table-index-nbits, n-ptes, nbytes, data-page // elements = gencgc-card-table-index-nbits, n-ptes, nbytes, data-page
@ -1362,6 +1391,51 @@ load_core_file(char *file, os_vm_offset_t file_offset, int merge_core_pages)
print_generation_stats(); print_generation_stats();
} }
sanity_check_loaded_core(initial_function); sanity_check_loaded_core(initial_function);
extern void smlgc_init(long);
size_t smlgc_heapsize = 4*1024*1024;
char* specified_heapsize = getenv("SMLGC_HEAPSIZE");
if (specified_heapsize) // in megabytes
smlgc_heapsize = atol(specified_heapsize) * 1048576;
// default is now asynchronous enabled
if (getenv("SMLGC_ASYNC") && !strcmp(getenv("SMLGC_ASYNC"),"0"))
enable_async_gc = 0;
if (use_smlgc && !specified_heapsize)
smlgc_heapsize = 1024*1024*1024;
smlgc_heapsize = ALIGN_DOWN(smlgc_heapsize, 65536);
//gclogfd = open("/tmp/gclog.txt", O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0666);
smlgc_init(smlgc_heapsize);
int import_dynamic = 0;
if (getenv("SMLGC_IMPORT_CORE"))
import_dynamic = atoi(getenv("SMLGC_IMPORT_CORE"));
if (import_dynamic) {
lose("Won't import dynamic space");
extern lispobj import_dynamic_space(lispobj);
//initial_function = import_dynamic_space(initial_function);
if (import_dynamic == 2) {
uword_t end = dynamic_space_highwatermark();
// change to unallocated
next_free_page = 0;
munmap((char*)DYNAMIC_SPACE_START, end - DYNAMIC_SPACE_START);
fprintf(stderr, "--> Unmapped gencgc dynamic space [%p:%p]\n",
(char*)DYNAMIC_SPACE_START, (char*)end);
}
} else if (!import_dynamic && use_smlgc) {
char* unmap_from = page_address(next_free_page);
long unmap_len = page_address(page_table_pages) - unmap_from;
mprotect(unmap_from, unmap_len, PROT_NONE);
fprintf(stderr, "--> gencgc space [%p:%p] has PROT_NONE\n",
unmap_from, page_address(page_table_pages));
}
fprintf(stderr, "R/O @ %p:%p\n",
(void*)READ_ONLY_SPACE_START, read_only_space_free_pointer);
#if 0
int* hint = 0;
lispobj sym =
find_symbol("*SHOW-NEW-CODE*",
VECTOR(lisp_package_vector)->data[20], &hint);
printf("show_new_code=%lx\n", sym);
if(sym) SYMBOL(sym)->value = LISP_T;
#endif
return initial_function; return initial_function;
case RUNTIME_OPTIONS_MAGIC: break; // already processed case RUNTIME_OPTIONS_MAGIC: break; // already processed
default: default:
@ -1414,7 +1488,21 @@ void asm_routine_poke(const char* routine, int offset, char byte)
static void trace_sym(lispobj, struct symbol*, struct grvisit_context*); static void trace_sym(lispobj, struct symbol*, struct grvisit_context*);
#define RECURSE(x) if(is_lisp_pointer(x))graph_visit(ptr,x,context) static int reject(lispobj ptr)
{
struct thread* th;
if (ptr >= READ_ONLY_SPACE_START && ptr < (lispobj)read_only_space_free_pointer)
return 1;
for_each_thread(th) {
if (ptr >= (lispobj)th->control_stack_start
&& ptr < (lispobj)th->control_stack_end) return 1;
}
return 0;
}
#define RECURSE(x) if(is_lisp_pointer(x) && !reject(x)) graph_visit(ptr,x,context)
int graph_visit_skip_weak_pointers;
/* Despite this being a nice concise expression of a pointer tracing algorithm, /* Despite this being a nice concise expression of a pointer tracing algorithm,
* it turns out to be almost unusable in any sufficiently complicated object graph * it turns out to be almost unusable in any sufficiently complicated object graph
@ -1430,12 +1518,21 @@ static void graph_visit(lispobj referer, lispobj ptr, struct grvisit_context* co
if (++context->depth > context->maxdepth) context->maxdepth = context->depth; if (++context->depth > context->maxdepth) context->maxdepth = context->depth;
// TODO: add rejection function for off-heap objects as part of supplied context // TODO: add rejection function for off-heap objects as part of supplied context
hopscotch_insert(context->seen, ptr, 1); hopscotch_insert(context->seen, ptr, 1);
if (context->action) context->action(ptr, context->data); if (context->action) context->action(referer, ptr, context->data);
lispobj layout, *obj; lispobj layout, *obj;
sword_t nwords, i; sword_t nwords, i;
if (lowtag_of(ptr) == LIST_POINTER_LOWTAG) { if (lowtag_of(ptr) == LIST_POINTER_LOWTAG) {
// When constructing lists there is a transient state with a bad cdr depending on
// whether more than one aallocation is needed.
// It might not be an error, as long as no other thread can see the data.
if (CONS(ptr)->car == (uword_t)-1 || CONS(ptr)->cdr == (uword_t)-1) {
char buf[80];
int n = snprintf(buf, sizeof buf,
"sus' cons @ %p: %lx %lx\n", (void*)ptr, CONS(ptr)->car, CONS(ptr)->cdr);
write(2, buf, n);
}
RECURSE(CONS(ptr)->car); RECURSE(CONS(ptr)->car);
RECURSE(CONS(ptr)->cdr); if (CONS(ptr)->cdr != (uword_t)-1) RECURSE(CONS(ptr)->cdr);
} else switch (widetag_of(obj = native_pointer(ptr))) { } else switch (widetag_of(obj = native_pointer(ptr))) {
case SIMPLE_VECTOR_WIDETAG: case SIMPLE_VECTOR_WIDETAG:
{ {
@ -1447,11 +1544,13 @@ static void graph_visit(lispobj referer, lispobj ptr, struct grvisit_context* co
case INSTANCE_WIDETAG: case INSTANCE_WIDETAG:
case FUNCALLABLE_INSTANCE_WIDETAG: case FUNCALLABLE_INSTANCE_WIDETAG:
layout = layout_of(obj); layout = layout_of(obj);
graph_visit(ptr, layout, context); if (layout != 0) {
nwords = headerobj_size(obj); graph_visit(ptr, layout, context);
struct bitmap bitmap = get_layout_bitmap(LAYOUT(layout)); nwords = headerobj_size(obj);
for (i=0; i<(nwords-1); ++i) struct bitmap bitmap = get_layout_bitmap(LAYOUT(layout));
if (bitmap_logbitp(i, bitmap)) RECURSE(obj[1+i]); for (i=0; i<(nwords-1); ++i)
if (bitmap_logbitp(i, bitmap)) RECURSE(obj[1+i]);
}
break; break;
case CODE_HEADER_WIDETAG: case CODE_HEADER_WIDETAG:
nwords = code_header_words((struct code*)obj); nwords = code_header_words((struct code*)obj);
@ -1479,6 +1578,9 @@ static void graph_visit(lispobj referer, lispobj ptr, struct grvisit_context* co
RECURSE(obj[2]); RECURSE(obj[2]);
RECURSE(decode_fdefn_rawfun((struct fdefn*)obj)); RECURSE(decode_fdefn_rawfun((struct fdefn*)obj));
break; break;
case WEAK_POINTER_WIDETAG:
if (graph_visit_skip_weak_pointers) break;
// else FALLTHROUGH
default: default:
// weak-pointer can be considered an ordinary boxed object. // weak-pointer can be considered an ordinary boxed object.
// the 'next' link looks like a fixnum. // the 'next' link looks like a fixnum.
@ -1504,15 +1606,20 @@ static void trace_sym(lispobj ptr, struct symbol* sym, struct grvisit_context* c
* dictated by thread stacks, etc. Caller may - but need not - provide * dictated by thread stacks, etc. Caller may - but need not - provide
* an 'action' to invoke on each object */ * an 'action' to invoke on each object */
struct grvisit_context* struct grvisit_context*
visit_heap_from_static_roots(struct hopscotch_table* reached, visit_heap_from_roots(struct hopscotch_table* reached,
void (*action)(lispobj, void*), void (*action)(lispobj, lispobj, void*),
void* data) void* data,
int quasi_static_roots_too,
lispobj* extra_roots,
int n_extra_roots)
{ {
hopscotch_create(reached, HOPSCOTCH_HASH_FUN_DEFAULT, hopscotch_create(reached, HOPSCOTCH_HASH_FUN_DEFAULT,
0, // no values 0, // no values
1<<18, /* initial size */ 1<<18, /* initial size */
0); 0);
struct timespec start_time, end_time;
clock_gettime(CLOCK_REALTIME, &start_time);
struct grvisit_context* context = malloc(sizeof (struct grvisit_context)); struct grvisit_context* context = malloc(sizeof (struct grvisit_context));
context->seen = reached; context->seen = reached;
context->action = action; context->action = action;
@ -1525,9 +1632,38 @@ visit_heap_from_static_roots(struct hopscotch_table* reached,
graph_visit(0, compute_lispobj(where), context); graph_visit(0, compute_lispobj(where), context);
where += object_size(where); where += object_size(where);
} }
if (quasi_static_roots_too) {
where = (lispobj*)DYNAMIC_SPACE_START;
end = (lispobj*)dynamic_space_highwatermark();
while (where<end) {
if (page_table[find_page_index(where)].gen
== PSEUDO_STATIC_GENERATION) {
graph_visit(0, compute_lispobj(where), context);
}
where += object_size(where);
}
}
int i;
for (i=0; i<n_extra_roots; ++i) {
lispobj root = extra_roots[i];
gc_assert(is_lisp_pointer(root));
graph_visit(0, root, context);
}
clock_gettime(CLOCK_REALTIME, &end_time);
context->microsec_elapsed =
(end_time.tv_sec - start_time.tv_sec)*1000000
+ (end_time.tv_nsec - start_time.tv_nsec)/1000;
return context; return context;
} }
int calc_log2size(long size)
{
int log2size = 3; // naive integer-length algorithmn
while (1<<log2size < size) ++log2size;
gc_assert(log2size <= 12);
return log2size;
}
// Caution: use at your own risk // Caution: use at your own risk
#if defined DEBUG_CORE_LOADING && DEBUG_CORE_LOADING #if defined DEBUG_CORE_LOADING && DEBUG_CORE_LOADING
struct visitor { struct visitor {
@ -1537,6 +1673,8 @@ struct visitor {
int count; int count;
int words; int words;
} headers[65], sv_subtypes[3]; } headers[65], sv_subtypes[3];
int log2size_histo[13];
int n_oversized;
struct hopscotch_table *reached; struct hopscotch_table *reached;
}; };
@ -1563,6 +1701,12 @@ static void tally(lispobj ptr, struct visitor* v)
v->sv_subtypes[subtype].words += words; v->sv_subtypes[subtype].words += words;
} }
} }
sword_t bytes = words * N_WORD_BYTES;
if (bytes > 4096)
++v->n_oversized;
else {
++v->log2size_histo[calc_log2size(bytes)];
}
} }
/* This printing in here is useful, but it's too much to output in make-target-2, /* This printing in here is useful, but it's too much to output in make-target-2,
@ -1587,7 +1731,7 @@ static uword_t visit(lispobj* where, lispobj* limit, uword_t arg)
} }
lispobj ptr = compute_lispobj(obj); lispobj ptr = compute_lispobj(obj);
tally(ptr, v); tally(ptr, v);
if (!hopscotch_get(v->reached, ptr, 0)) printf("unreachable: %p\n", (void*)ptr); //if (!hopscotch_get(v->reached, ptr, 0)) printf("unreachable: %p\n", (void*)ptr);
obj += object_size(obj); obj += object_size(obj);
} }
return 0; return 0;
@ -1602,7 +1746,7 @@ static void sanity_check_loaded_core(lispobj initial_function)
memset(v, 0, sizeof v); memset(v, 0, sizeof v);
// Pass 1: Count objects reachable from known roots. // Pass 1: Count objects reachable from known roots.
struct grvisit_context* c struct grvisit_context* c
= visit_heap_from_static_roots(&reached, 0, 0); = visit_heap_from_roots(&reached, 0, 0, 0, 0);
graph_visit(0, initial_function, c); // initfun is not otherwise reachable graph_visit(0, initial_function, c); // initfun is not otherwise reachable
// having computed the reaching graph, tally up the dynamic space objects // having computed the reaching graph, tally up the dynamic space objects
int key_index; int key_index;
@ -1652,6 +1796,10 @@ static void sanity_check_loaded_core(lispobj initial_function)
v[1].headers[64].words += v[1].headers[i].words; v[1].headers[64].words += v[1].headers[i].words;
} }
} }
printf("Breakdown by log2(size):\n");
for (i=(N_WORD_BYTES==32)?3:4; i<=12; ++i)
printf(" %d", v[1].log2size_histo[i]);
printf(" oversized=%d\n", v[1].n_oversized);
hopscotch_destroy(&reached); hopscotch_destroy(&reached);
} }
#else #else
@ -1675,3 +1823,22 @@ void gc_store_corefile_ptes(struct corefile_pte *ptes)
ptes[i].words_used = used | page_single_obj_p(i); ptes[i].words_used = used | page_single_obj_p(i);
} }
} }
static uword_t visit_instances(lispobj* where, lispobj* limit, uword_t arg)
{
lispobj* obj = where;
while (obj < limit) {
if (widetag_of(obj) == INSTANCE_WIDETAG) {
if (instance_layout(obj) == 0) {
fprintf(stderr, "bad: %p\n", obj);
++ *(int*)arg;
}
}
obj += object_size(obj);
}
return 0;
}
void check_for_layoutless_instances() {
int bad_count = 0;
walk_generation(visit_instances, -1, (uword_t)&bad_count);
if (bad_count) { fprintf(stderr, "total bad instances: %d\n", bad_count); }
}

View file

@ -346,6 +346,37 @@ void scan_binding_stack()
#endif #endif
} }
// Forwarded code objects contain FPs for each embedded function
void store_code_forwarding_ptrs(struct code* old, struct code* new)
{
set_forwarding_pointer((lispobj *)old, make_lispobj(new, OTHER_POINTER_LOWTAG));
sword_t displacement = (char*)new - (char*)old;
#if defined LISP_FEATURE_PPC || defined LISP_FEATURE_PPC64 || \
defined LISP_FEATURE_X86 || defined LISP_FEATURE_X86_64
// Fixup absolute jump tables. These aren't recorded in code->fixups
// because we don't need to denote an arbitrary set of places in the code.
// The count alone suffices. A GC immediately after creating the code
// could cause us to observe some 0 words here. Those should be ignored.
lispobj* jump_table = code_jumptable_start(new);
int count = jumptable_count(jump_table);
int i;
for (i = 1; i < count; ++i)
if (jump_table[i]) jump_table[i] += displacement;
#endif
for_each_simple_fun(i, new_fun, new, 1, {
// Calculate the old raw function pointer
struct simple_fun* old_fun = (struct simple_fun*)((char*)new_fun - displacement);
if (fun_self_from_baseptr(old_fun) == old_fun->self) {
new_fun->self = fun_self_from_baseptr(new_fun);
set_forwarding_pointer((lispobj*)old_fun,
make_lispobj(new_fun, FUN_POINTER_LOWTAG));
}
})
}
extern int pin_all_dynamic_space_code; extern int pin_all_dynamic_space_code;
static struct code * static struct code *
trans_code(struct code *code) trans_code(struct code *code)
@ -378,32 +409,8 @@ trans_code(struct code *code)
if (l_new_code == l_code) return code; if (l_new_code == l_code) return code;
set_forwarding_pointer((lispobj *)code, l_new_code); struct code* new_code = (void*)(l_new_code-OTHER_POINTER_LOWTAG);
store_code_forwarding_ptrs(code, new_code);
struct code *new_code = (struct code *) native_pointer(l_new_code);
sword_t displacement = l_new_code - l_code;
#if defined LISP_FEATURE_PPC || defined LISP_FEATURE_PPC64 || \
defined LISP_FEATURE_X86 || defined LISP_FEATURE_X86_64
// Fixup absolute jump tables. These aren't recorded in code->fixups
// because we don't need to denote an arbitrary set of places in the code.
// The count alone suffices. A GC immediately after creating the code
// could cause us to observe some 0 words here. Those should be ignored.
lispobj* jump_table = code_jumptable_start(new_code);
int count = jumptable_count(jump_table);
int i;
for (i = 1; i < count; ++i)
if (jump_table[i]) jump_table[i] += displacement;
#endif
for_each_simple_fun(i, new_fun, new_code, 1, {
// Calculate the old raw function pointer
struct simple_fun* old_fun = (struct simple_fun*)((char*)new_fun - displacement);
if (fun_self_from_baseptr(old_fun) == old_fun->self) {
new_fun->self = fun_self_from_baseptr(new_fun);
set_forwarding_pointer((lispobj*)old_fun,
make_lispobj(new_fun, FUN_POINTER_LOWTAG));
}
})
gencgc_apply_code_fixups(code, new_code); gencgc_apply_code_fixups(code, new_code);
os_flush_icache(code_text_start(new_code), code_text_size(new_code)); os_flush_icache(code_text_start(new_code), code_text_size(new_code));
return new_code; return new_code;
@ -1169,8 +1176,10 @@ DEF_SCAV_TRANS_SIZE_UB(128)
* containing a single key */ * containing a single key */
static sword_t scav_weakptr(lispobj *where, lispobj __attribute__((unused)) object) static sword_t scav_weakptr(lispobj *where, lispobj __attribute__((unused)) object)
{ {
// fprintf(stderr, "scav_weakptr %p (hdr=%lx) ", where, object);
if (weakptr_vectorp((struct weak_pointer*)where)) { if (weakptr_vectorp((struct weak_pointer*)where)) {
add_to_weak_vector_list(where, *where); // treat it like weak simple-vector add_to_weak_vector_list(where, *where); // treat it like weak simple-vector
// fprintf(stderr, "vectorp\n");
return size_vector_t(where); return size_vector_t(where);
} }
struct weak_pointer * wp = (struct weak_pointer*)where; struct weak_pointer * wp = (struct weak_pointer*)where;
@ -1188,6 +1197,7 @@ static sword_t scav_weakptr(lispobj *where, lispobj __attribute__((unused)) obje
immobile_obj_gen_bits(base_pointer(pointee)) == from_space) immobile_obj_gen_bits(base_pointer(pointee)) == from_space)
#endif #endif
); );
// fprintf(stderr, "pointee=%lx%s\n", pointee, breakable?" BREAKABLE":"");
if (breakable) { // Pointee could potentially be garbage. if (breakable) { // Pointee could potentially be garbage.
// But it might already have been deemed live and forwarded. // But it might already have been deemed live and forwarded.
if (forwarding_pointer_p(native_pointer(pointee))) if (forwarding_pointer_p(native_pointer(pointee)))
@ -1905,14 +1915,17 @@ sword_t scav_code_blob(lispobj *object, lispobj header);
lispobj * lispobj *
component_ptr_from_pc(char *pc) component_ptr_from_pc(char *pc)
{ {
extern uword_t* codeblob_from_interior_ptr(void* addr);
lispobj* object = NULL;
if(use_smlgc) object = codeblob_from_interior_ptr(pc);
/* This will safely look in one or both codeblob trees and/or the /* This will safely look in one or both codeblob trees and/or the
* sorted array of immobile text pages. Failing those, it'll perform * sorted array of immobile text pages. Failing those, it'll perform
* the usual linear scan of generation 1 and up pages. In any case * the usual linear scan of generation 1 and up pages. In any case
* it should be perfectly threadsafe because the trees are made of immutable * it should be perfectly threadsafe because the trees are made of immutable
* nodes, and linear scan only operates on pages that can't be * nodes, and linear scan only operates on pages that can't be
* concurrently manipulated */ * concurrently manipulated */
lispobj *object = search_all_gc_spaces(pc); if (object == NULL) object = search_all_gc_spaces(pc);
if (object != NULL && widetag_of(object) == CODE_HEADER_WIDETAG) if (object != NULL && widetag_of(object) == CODE_HEADER_WIDETAG)
return object; return object;
@ -2076,6 +2089,8 @@ properly_tagged_p_internal(lispobj pointer, lispobj *start_addr)
int int
valid_tagged_pointer_p(lispobj pointer) valid_tagged_pointer_p(lispobj pointer)
{ {
extern int smlgc_valid_tagged_pointer_p();
if (smlgc_valid_tagged_pointer_p(pointer)) return 1;
/* We don't have a general way to ask a specific GC implementation /* We don't have a general way to ask a specific GC implementation
* whether 'pointer' is definitely the tagged pointer to an object - * whether 'pointer' is definitely the tagged pointer to an object -
* all we have is "search for a containing object" and then a decision * all we have is "search for a containing object" and then a decision
@ -2099,8 +2114,7 @@ valid_tagged_pointer_p(lispobj pointer)
== pointer; == pointer;
} }
lispobj *start = search_all_gc_spaces((void*)pointer); lispobj *start = search_all_gc_spaces((void*)pointer);
if (start != NULL) if (start != NULL) return properly_tagged_descriptor_p((void*)pointer, start);
return properly_tagged_descriptor_p((void*)pointer, start);
return 0; return 0;
} }
@ -2972,6 +2986,7 @@ int hexdump_and_verify_heap(lispobj* cur_thread_approx_stackptr, int flags)
} }
#endif #endif
#if 0
/* These are do-nothing wrappers for now */ /* These are do-nothing wrappers for now */
lispobj *lisp_component_ptr_from_pc(char *pc) { lispobj *lisp_component_ptr_from_pc(char *pc) {
lispobj *result = component_ptr_from_pc(pc); lispobj *result = component_ptr_from_pc(pc);
@ -2981,3 +2996,4 @@ int lisp_valid_tagged_pointer_p(lispobj pointer) {
int result = valid_tagged_pointer_p(pointer); int result = valid_tagged_pointer_p(pointer);
return result; return result;
} }
#endif

View file

@ -1,6 +1,8 @@
#ifndef _GENCGC_ALLOC_REGION_H_ #ifndef _GENCGC_ALLOC_REGION_H_
#define _GENCGC_ALLOC_REGION_H_ #define _GENCGC_ALLOC_REGION_H_
#include "../../smlsharpgc/alloc_ptr.h"
/* Abstract out the data for an allocation region allowing a single /* Abstract out the data for an allocation region allowing a single
* routine to be used for allocation and closing. */ * routine to be used for allocation and closing. */
/* Caution: if you change this, you may have to change compiler/generic/objdef /* Caution: if you change this, you may have to change compiler/generic/objdef
@ -34,7 +36,7 @@ static inline void gc_init_region(struct alloc_region *region)
typedef struct { typedef struct {
struct alloc_region cons; struct alloc_region cons;
struct alloc_region mixed; struct alloc_region mixed;
uword_t token; uintptr_t token;
} arena_state; } arena_state;
// One region for each of page type. // One region for each of page type.

View file

@ -654,6 +654,33 @@ bool page_is_zeroed(page_index_t page)
} }
#endif #endif
__attribute__((unused)) static char* region_name(struct thread* th, struct alloc_region* r) {
if (r == &th->mixed_tlab) { return "um"; }
if (r == &th->cons_tlab) { return "uc"; }
if (r == &th->sys_mixed_tlab) { return "sm"; }
if (r == &th->sys_cons_tlab) { return "sc"; }
if (r == code_region) { return "code"; }
return 0;
}
static void show_new_region(__attribute__((unused)) struct alloc_region* r,
__attribute__((unused)) int page_type) {
#if 0
extern struct thread* mainthread;
struct thread* th = get_sb_vm_thread();
char *name = name_of_region(th, r);
char buf[100];
int n;
if (name)
n = snprintf(buf, sizeof buf, "%s: %s = %lx..%lx %x\n",
th == mainthread ? "m" : "f", name,
(uword_t)r->start_addr, (uword_t)r->end_addr, page_type);
else
n = snprintf(buf, sizeof buf, "%s: %p = %lx..%lx %x\n",
th == mainthread ? "m" : "f", r,
(uword_t)r->start_addr, (uword_t)r->end_addr, page_type);
#endif
}
static void* static void*
gc_alloc_new_region(sword_t nbytes, int page_type, struct alloc_region *alloc_region, int unlock) gc_alloc_new_region(sword_t nbytes, int page_type, struct alloc_region *alloc_region, int unlock)
{ {
@ -694,6 +721,7 @@ gc_alloc_new_region(sword_t nbytes, int page_type, struct alloc_region *alloc_re
} }
alloc_region->free_pointer = alloc_region->start_addr; alloc_region->free_pointer = alloc_region->start_addr;
gc_assert(find_page_index(alloc_region->start_addr) == page); gc_assert(find_page_index(alloc_region->start_addr) == page);
show_new_region(alloc_region, page_type);
return alloc_region->free_pointer; return alloc_region->free_pointer;
} }
@ -764,6 +792,7 @@ gc_alloc_new_region(sword_t nbytes, int page_type, struct alloc_region *alloc_re
INSTRUMENTING(zeroize_pages_if_needed(first_page+(page_words_used(first_page)?1:0), INSTRUMENTING(zeroize_pages_if_needed(first_page+(page_words_used(first_page)?1:0),
last_page, page_type), et_bzeroing); last_page, page_type), et_bzeroing);
show_new_region(alloc_region, page_type);
return alloc_region->free_pointer; return alloc_region->free_pointer;
} }
@ -869,6 +898,20 @@ gc_close_region(struct alloc_region *alloc_region, int page_type)
char *page_base = page_address(first_page); char *page_base = page_address(first_page);
char *free_pointer = alloc_region->free_pointer; char *free_pointer = alloc_region->free_pointer;
#if 0
extern struct thread* mainthread;
char buf[100];
int n;
struct thread* th = get_sb_vm_thread();
char* name = name_of_region(th, alloc_region);
if (name)
n = snprintf(buf, sizeof buf, "%s: c %s %x\n",
th == mainthread ? "m" : "f", name, page_type);
else
n = snprintf(buf, sizeof buf, "%s: c %p %x\n",
th == mainthread ? "m" : "f", alloc_region, page_type);
#endif
#if defined LISP_FEATURE_SYSTEM_TLABS && defined DEBUG #if defined LISP_FEATURE_SYSTEM_TLABS && defined DEBUG
if (alloc_region == &get_sb_vm_thread()->sys_mixed_tlab || if (alloc_region == &get_sb_vm_thread()->sys_mixed_tlab ||
alloc_region == &get_sb_vm_thread()->sys_cons_tlab) { alloc_region == &get_sb_vm_thread()->sys_cons_tlab) {
@ -4018,6 +4061,7 @@ long tot_gc_nsec;
void NO_SANITIZE_ADDRESS NO_SANITIZE_MEMORY void NO_SANITIZE_ADDRESS NO_SANITIZE_MEMORY
collect_garbage(generation_index_t last_gen) collect_garbage(generation_index_t last_gen)
{ {
if (get_sb_vm_thread()->ap4.freebit.mask != (uint32_t)-1) { fprintf(stderr, "gencgc ignoring collect_garbage call\n"); return; }
++n_gcs; ++n_gcs;
THREAD_JIT(0); THREAD_JIT(0);
generation_index_t gen = 0, i; generation_index_t gen = 0, i;
@ -4302,6 +4346,7 @@ lisp_alloc(int flags, struct alloc_region *region, sword_t nbytes,
{ {
os_vm_size_t trigger_bytes = 0; os_vm_size_t trigger_bytes = 0;
if (use_smlgc) lose("How did we get a gencgc allocation request?");
gc_assert(nbytes > 0); gc_assert(nbytes > 0);
/* Check for alignment allocation problems. */ /* Check for alignment allocation problems. */

View file

@ -12,6 +12,9 @@
#ifndef _INCLUDED_GLOBALS_H_ #ifndef _INCLUDED_GLOBALS_H_
#define _INCLUDED_GLOBALS_H_ #define _INCLUDED_GLOBALS_H_
extern int use_smlgc;
extern int enable_async_gc;
#ifndef __ASSEMBLER__ #ifndef __ASSEMBLER__
# include <stdbool.h> # include <stdbool.h>
# include <sys/types.h> # include <sys/types.h>

View file

@ -15,14 +15,21 @@
#include "hopscotch.h" #include "hopscotch.h"
struct grvisit_context { struct grvisit_context {
struct hopscotch_table* seen; struct hopscotch_table* seen;
void (*action)(lispobj, void*); void (*action)(lispobj, lispobj, void*);
void* data; void* data;
int depth; int depth;
int maxdepth; int maxdepth;
long microsec_elapsed;
int n_known_ranges;
struct {
lispobj start, end;
unsigned int *markbits;
} ranges[4];
}; };
extern struct grvisit_context* extern struct grvisit_context*
visit_heap_from_static_roots(struct hopscotch_table* reached, visit_heap_from_roots(struct hopscotch_table* reached,
void (*action)(lispobj, void*), void (*action)(lispobj, lispobj, void*),
void* data); void* data, int,
lispobj*, int);
#endif #endif

View file

@ -19,6 +19,7 @@ extern void lose(char *fmt, ...)
#endif #endif
never_returns; never_returns;
extern void tprintf(char *fmt, ...); extern void tprintf(char *fmt, ...);
extern void tprintf_(char *fmt, ...);
extern int lose_on_corruption_p; extern int lose_on_corruption_p;
extern void corruption_warning_and_maybe_lose(char *fmt, ...); extern void corruption_warning_and_maybe_lose(char *fmt, ...);
extern void enable_lossage_handler(void); extern void enable_lossage_handler(void);

View file

@ -69,6 +69,7 @@
#include "genesis/cons.h" #include "genesis/cons.h"
#include "genesis/vector.h" #include "genesis/vector.h"
#include "atomiclog.inc" #include "atomiclog.inc"
#include "search.h"
#ifdef ATOMIC_LOGGING #ifdef ATOMIC_LOGGING
uword_t *eventdata; uword_t *eventdata;
@ -258,10 +259,12 @@ resignal_to_lisp_thread(int signal, os_context_t *context)
char* vm_thread_name(struct thread* th) char* vm_thread_name(struct thread* th)
{ {
if (!th) return "non-lisp"; if (!th) return "non-lisp";
struct thread_instance *lispthread = if (th->lisp_thread) {
(void*)(th->lisp_thread - INSTANCE_POINTER_LOWTAG); struct thread_instance *lispthread =
lispobj name = lispthread->_name; (void*)(th->lisp_thread - INSTANCE_POINTER_LOWTAG);
if (simple_base_string_p(name)) return vector_sap(name); lispobj name = lispthread->_name;
if (simple_base_string_p(name)) return vector_sap(name);
}
return "?"; return "?";
} }
@ -329,7 +332,12 @@ static void record_signal(int sig, void* context)
{ {
event2("got signal %d @ pc=%p", sig, os_context_pc(context)); event2("got signal %d @ pc=%p", sig, os_context_pc(context));
} }
#define RECORD_SIGNAL(sig,ctxt) if(sig!=SIGSEGV)record_signal(sig,ctxt); #define RECORD_SIGNAL(sig,ctxt) if(sig!=4) { \
char buf[100]; \
int n = snprintf(buf, sizeof buf,"[%s] sig%d @ pc=%lx fp=%lx sp=%lx\n", \
vm_thread_name(get_sb_vm_thread()), \
sig, os_context_pc(ctxt), *os_context_fp_addr(ctxt), \
*os_context_sp_addr(ctxt)); write(2,buf,n); }
#else #else
#define RECORD_SIGNAL(sig,ctxt) #define RECORD_SIGNAL(sig,ctxt)
#endif #endif
@ -337,7 +345,12 @@ static void record_signal(int sig, void* context)
#ifdef LISP_FEATURE_WIN32 #ifdef LISP_FEATURE_WIN32
# define should_handle_in_this_thread(c) (1) # define should_handle_in_this_thread(c) (1)
#else #else
# define should_handle_in_this_thread(c) lisp_thread_p(c) // collector needs to handle sigsegv in exception_handling_load()
// in otherptr_mseg. Probably would be best to consider the collector thread
// to be a Lisp thread, or else specifically allow that thread.
// But wait! low_level_handle_now no longer uses SAVE_ERRNO so therefore
// does not use should_handle_in_this_thread so why is this diff here?
# define should_handle_in_this_thread(c) (lisp_thread_p(c)||signal==SIGSEGV)
#endif #endif
#define SAVE_ERRNO(signal,context,void_context) \ #define SAVE_ERRNO(signal,context,void_context) \
{ \ { \
@ -1040,6 +1053,7 @@ bool interrupt_handler_pending_p(void)
void void
interrupt_handle_pending(os_context_t *context) interrupt_handle_pending(os_context_t *context)
{ {
// fprintf(stderr, "entered interrupt_handle_pending\n");
#ifdef ADDRESS_SANITIZER #ifdef ADDRESS_SANITIZER
__asan_unpoison_memory_region(context, sizeof *context); __asan_unpoison_memory_region(context, sizeof *context);
#endif #endif
@ -1401,10 +1415,18 @@ maybe_now_maybe_later(int signal, siginfo_t *info, void *void_context)
#ifdef LISP_FEATURE_GC_METRICS #ifdef LISP_FEATURE_GC_METRICS
pthread_cond_t gcmetrics_condvar = PTHREAD_COND_INITIALIZER; pthread_cond_t gcmetrics_condvar = PTHREAD_COND_INITIALIZER;
pthread_mutex_t gcmetrics_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t gcmetrics_mutex = PTHREAD_MUTEX_INITIALIZER;
int current_thread_total_gc_pause_microseconds() {
struct thread* th = get_sb_vm_thread();
struct extra_thread_data *data = thread_extra_data(th);
return data->sum_gc_wait;
}
#endif #endif
#ifdef THREADS_USING_GCSIGNAL #ifdef THREADS_USING_GCSIGNAL
extern int enable_async_gc;
extern void sml_check_internal(void *);
/* This function must not cons, because that may trigger a GC. */ /* This function must not cons, because that may trigger a GC. */
void void
sig_stop_for_gc_handler(int __attribute__((unused)) signal, sig_stop_for_gc_handler(int __attribute__((unused)) signal,
@ -1417,17 +1439,20 @@ sig_stop_for_gc_handler(int __attribute__((unused)) signal,
/* Test for GC_INHIBIT _first_, else we'd trap on every single /* Test for GC_INHIBIT _first_, else we'd trap on every single
* pseudo atomic until gc is finally allowed. */ * pseudo atomic until gc is finally allowed. */
if (read_TLS(GC_INHIBIT,thread) != NIL) { if (read_TLS(GC_INHIBIT,thread) != NIL) {
tprintf_("GC sig deferred: INHIBIT");
event0("stop_for_gc deferred for *GC-INHIBIT*"); event0("stop_for_gc deferred for *GC-INHIBIT*");
write_TLS(STOP_FOR_GC_PENDING, LISP_T, thread); write_TLS(STOP_FOR_GC_PENDING, LISP_T, thread);
return; return;
} else if (arch_pseudo_atomic_atomic(thread)) { } else if (arch_pseudo_atomic_atomic(thread)) {
event0("stop_for_gc deferred for PA"); tprintf_("GC sig deferred: pseudo-atomic");
event0("GC-cooperate deferred for PA");
write_TLS(STOP_FOR_GC_PENDING, LISP_T, thread); write_TLS(STOP_FOR_GC_PENDING, LISP_T, thread);
arch_set_pseudo_atomic_interrupted(thread); arch_set_pseudo_atomic_interrupted(thread);
maybe_save_gc_mask_and_block_deferrables(context); maybe_save_gc_mask_and_block_deferrables(context);
return; return;
} }
extern void adjust_context_for_implicit_pseudoatomic(os_context_t*);
adjust_context_for_implicit_pseudoatomic(context);
event0("stop_for_gc"); event0("stop_for_gc");
/* Not PA and GC not inhibited -- we can stop now. */ /* Not PA and GC not inhibited -- we can stop now. */
@ -1460,6 +1485,12 @@ sig_stop_for_gc_handler(int __attribute__((unused)) signal,
interrupt_data->gc_blocked_deferrables = 0; interrupt_data->gc_blocked_deferrables = 0;
} }
if (use_smlgc && enable_async_gc) {
extern void cooperate_with_gc(uword_t);
cooperate_with_gc(*os_context_sp_addr(context));
goto Done;
}
/* No need to use an atomic memory load here - this thead "owns" its state /* No need to use an atomic memory load here - this thead "owns" its state
* for now, and nobody else touches it, the sole exception being that GC * for now, and nobody else touches it, the sole exception being that GC
* sets it to RUNNING. The loads inside thread_wait_until_not() * sets it to RUNNING. The loads inside thread_wait_until_not()
@ -1515,6 +1546,7 @@ sig_stop_for_gc_handler(int __attribute__((unused)) signal,
if (my_state != STATE_RUNNING) if (my_state != STATE_RUNNING)
lose("stop_for_gc: bad state on wakeup: %x", my_state); lose("stop_for_gc: bad state on wakeup: %x", my_state);
Done:
if (was_in_lisp) { if (was_in_lisp) {
undo_fake_foreign_function_call(context); undo_fake_foreign_function_call(context);
} }
@ -1911,6 +1943,7 @@ extern void restore_sbcl_signals () {
} }
} }
extern int is_collector_thread();
static void static void
low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context) low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context)
{ {
@ -1924,7 +1957,8 @@ low_level_handle_now_handler(int signal, siginfo_t *info, void *void_context)
RECORD_SIGNAL(signal,void_context); RECORD_SIGNAL(signal,void_context);
UNBLOCK_SIGSEGV(); UNBLOCK_SIGSEGV();
RESTORE_FP_CONTROL_WORD(context,void_context); RESTORE_FP_CONTROL_WORD(context,void_context);
if (lisp_thread_p(void_context)) { // assume we know how to handle signals (SIGSEGV) in the collector thread
if (lisp_thread_p(void_context) || is_collector_thread()) {
interrupt_low_level_handlers[signal](signal, info, context); interrupt_low_level_handlers[signal](signal, info, context);
} }
#if defined LISP_FEATURE_DARWIN && defined LISP_FEATURE_SB_THREAD #if defined LISP_FEATURE_DARWIN && defined LISP_FEATURE_SB_THREAD

View file

@ -59,6 +59,15 @@ int personality (unsigned long);
#include <sys/personality.h> #include <sys/personality.h>
#endif #endif
char * cur_thread_name()
{
struct thread *th = get_sb_vm_thread();
if (!th) return "gc";
struct thread_instance *ti = (void*)native_pointer(th->lisp_thread);
lispobj threadname = ti->_name;
return (char*)VECTOR(threadname)->data;
}
#ifdef LISP_FEATURE_SB_FUTEX #ifdef LISP_FEATURE_SB_FUTEX
#include <sys/syscall.h> #include <sys/syscall.h>
#include <unistd.h> #include <unistd.h>
@ -113,6 +122,7 @@ void lisp_mutex_start_eventrecording() {
eventcount = 0; eventcount = 0;
record_mutex_events = 1; record_mutex_events = 1;
} }
void lisp_mutex_done_eventrecording() { void lisp_mutex_done_eventrecording() {
record_mutex_events = 0; record_mutex_events = 0;
int i; int i;
@ -259,7 +269,7 @@ void os_init()
#ifdef LISP_FEATURE_SB_FUTEX #ifdef LISP_FEATURE_SB_FUTEX
futex_init(); futex_init();
#endif #endif
#ifdef LISP_FEATURE_SB_DEVEL #if 1 // def LISP_FEATURE_SB_DEVEL
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY);
#endif #endif
} }
@ -375,11 +385,27 @@ fallback_sigsegv_handler(int signal, siginfo_t *info, os_context_t *context)
void (*sbcl_fallback_sigsegv_handler) // Settable by user. void (*sbcl_fallback_sigsegv_handler) // Settable by user.
(int, siginfo_t*, os_context_t*) = fallback_sigsegv_handler; (int, siginfo_t*, os_context_t*) = fallback_sigsegv_handler;
extern uword_t exception_handling_load(uword_t*);
extern void ldb_monitor();
static void static void
sigsegv_handler(int signal, siginfo_t *info, os_context_t *context) sigsegv_handler(int signal, siginfo_t *info, os_context_t *context)
{ {
uword_t pc = os_context_pc(context);
if (pc == (uword_t)&exception_handling_load) {
// instruction at PC: 488B07 MOV RAX, [RDI]
set_os_context_pc(context, pc + 3);
extern int mseg_rej_memfault;
__sync_fetch_and_add(&mseg_rej_memfault, 1);
*os_context_register_addr(context, 0) = 0;
return;
}
os_vm_address_t addr = arch_get_bad_addr(signal, info, context); os_vm_address_t addr = arch_get_bad_addr(signal, info, context);
//fprintf(stderr, "fault @ pc=%p addr=%p fp=%lx\n", (void*)pc, addr, *os_context_fp_addr(context));
if (find_page_index(addr)<0) ldb_monitor();
#ifdef LISP_FEATURE_SB_SAFEPOINT #ifdef LISP_FEATURE_SB_SAFEPOINT
if (handle_safepoint_violation(context, addr)) return; if (handle_safepoint_violation(context, addr)) return;
#endif #endif
@ -395,12 +421,20 @@ sigsegv_handler(int signal, siginfo_t *info, os_context_t *context)
sbcl_fallback_sigsegv_handler(signal, info, context); sbcl_fallback_sigsegv_handler(signal, info, context);
} }
void crash(__attribute__((unused)) int sig,
__attribute__((unused)) siginfo_t* info,
void* context)
{
extern void sb_dump_mcontext(char*,void*);
fprintf(stderr, "pthread %lx vm-thread %p\n", pthread_self(), get_sb_vm_thread());
sb_dump_mcontext("SIGSEGV", context);
ldb_monitor();
}
void void
os_install_interrupt_handlers(void) os_install_interrupt_handlers(void)
{ {
if (INSTALL_SIG_MEMORY_FAULT_HANDLER) {
ll_install_handler(SIG_MEMORY_FAULT, sigsegv_handler); ll_install_handler(SIG_MEMORY_FAULT, sigsegv_handler);
}
} }
char *os_get_runtime_executable_path() char *os_get_runtime_executable_path()
@ -416,3 +450,5 @@ char *os_get_runtime_executable_path()
return copied_string(path); return copied_string(path);
} }
uword_t get_stderr() { return (uword_t)stderr; }

View file

@ -1,8 +1,12 @@
#include "interr.h" #include "interr.h"
int use_smlgc;
int main(int argc, char *argv[], char *envp[]) int main(int argc, char *argv[], char *envp[])
{ {
if (getenv("SMLGC")) use_smlgc = 1;
extern int initialize_lisp(int argc, char *argv[], char *envp[]); extern int initialize_lisp(int argc, char *argv[], char *envp[]);
initialize_lisp(argc, argv, envp); initialize_lisp(argc, argv, envp);
lose("unexpected return from initial thread in main()"); lose("unexpected return from initial thread in main()");
return 0; return 0;

View file

@ -234,10 +234,11 @@ void save_gc_crashdump(char *pathname,
#endif #endif
static cmd call_cmd, dump_cmd, print_cmd, quit_cmd, help_cmd; static cmd call_cmd, dump_cmd, print_cmd, quit_cmd, help_cmd;
static cmd flush_cmd, regs_cmd, exit_cmd; static cmd findpath_cmd, flush_cmd, regs_cmd, exit_cmd;
static cmd print_context_cmd, pte_cmd, search_cmd; static cmd print_context_cmd, pte_cmd, search_cmd;
static cmd backtrace_cmd, catchers_cmd; static cmd backtrace_cmd, catchers_cmd;
static cmd threads_cmd, findpath_cmd, layouts_cmd; static cmd threads_cmd, findpath_cmd, layouts_cmd;
static cmd segs_cmd, hashsets_cmd;
extern void gc_stop_the_world(), gc_start_the_world(); extern void gc_stop_the_world(), gc_start_the_world();
static void suspend_other_threads() { static void suspend_other_threads() {
@ -267,7 +268,8 @@ static int save_cmd(char **ptr) {
#endif #endif
return 0; return 0;
} }
void list_lisp_threads(int regions) { extern uword_t* obj_from_ambiguous_ptr(lispobj);
void list_lisp_threads(int regions, int stack) {
struct thread* th; struct thread* th;
fprintf(stderr, "(thread*,pthread,sb-vm:thread,name)\n"); fprintf(stderr, "(thread*,pthread,sb-vm:thread,name)\n");
void* pthread; void* pthread;
@ -301,9 +303,27 @@ void list_lisp_threads(int regions) {
show_tlab("cons ", cons_region); show_tlab("cons ", cons_region);
#undef show_tlab #undef show_tlab
} }
if (stack) { // show stack roots of current thread only
lispobj* low = (void*)ALIGN_DOWN((uword_t)&regions, N_WORD_BYTES);
lispobj* high = get_sb_vm_thread()->control_stack_end;
lispobj* sp;
for (sp = high-1; sp >= low; --sp) {
lispobj word = *sp;
lispobj* obj;
if ((word >= READ_ONLY_SPACE_START && word < (lispobj)read_only_space_free_pointer) ||
(word >= STATIC_SPACE_START && word < (lispobj)static_space_free_pointer)) {
} else if ((obj = obj_from_ambiguous_ptr(word)) != 0) {
fprintf(stderr, "%p: %16lx -> %p (%s)\n", sp, word, obj,
(lowtag_of(word) == LIST_POINTER_LOWTAG) ? "cons"
: widetag_names[widetag_of(obj)>>2]);
}
}
}
} }
static int threads_cmd(char **ptr) { static int threads_cmd(char **ptr) {
list_lisp_threads(more_p(ptr) && !strncmp(*ptr, "-r", 2)); int show_regions = more_p(ptr) && !strncmp(*ptr, "-r", 2);
int show_stack = more_p(ptr) && !strncmp(*ptr, "-s", 2);
list_lisp_threads(show_regions, show_stack);
return 0; return 0;
} }
extern int heap_trace_verbose; extern int heap_trace_verbose;
@ -365,6 +385,18 @@ static int verify_cmd(char __attribute__((unused)) **ptr) {
return 0; return 0;
} }
static int gc_cmd(char **ptr) { static int gc_cmd(char **ptr) {
#if 0
extern void sml_force_gc();
extern int sml_current_phase();
extern void sml_check_internal(void *frame_pointer);
int old = sml_current_phase();
if (!strncmp(*ptr, "start", 5))
sml_force_gc();
else if (!strncmp(*ptr, "sync", 4))
sml_check_internal(&ptr);
int new = sml_current_phase();
fprintf(stderr, "Phase %d -> %d\n", old, new);
#else
int last_gen = 0; int last_gen = 0;
extern generation_index_t verify_gens; extern generation_index_t verify_gens;
if (more_p(ptr)) parse_number(ptr, &last_gen); if (more_p(ptr)) parse_number(ptr, &last_gen);
@ -374,6 +406,7 @@ static int gc_cmd(char **ptr) {
suspend_other_threads(); suspend_other_threads();
collect_garbage(last_gen); collect_garbage(last_gen);
unsuspend_other_threads(); unsuspend_other_threads();
#endif
return 0; return 0;
} }
@ -404,12 +437,14 @@ static struct cmd {
{"findpath", "Find path to an object.", findpath_cmd}, {"findpath", "Find path to an object.", findpath_cmd},
{"flush", "Flush all temp variables.", flush_cmd}, {"flush", "Flush all temp variables.", flush_cmd},
{"layouts", "Dump LAYOUT instances.", layouts_cmd}, {"layouts", "Dump LAYOUT instances.", layouts_cmd},
{"hashsets", "Show hashsets.", hashsets_cmd},
{"print", "Print object at ADDRESS.", print_cmd}, {"print", "Print object at ADDRESS.", print_cmd},
{"p", "(an alias for print)", print_cmd}, {"p", "(an alias for print)", print_cmd},
{"pte", "Page table entry for address", pte_cmd}, {"pte", "Page table entry for address", pte_cmd},
{"quit", "Quit.", quit_cmd}, {"quit", "Quit.", quit_cmd},
{"regs", "Display current Lisp registers.", regs_cmd}, {"regs", "Display current Lisp registers.", regs_cmd},
{"search", "Search heap for object.", search_cmd}, {"search", "Search heap for object.", search_cmd},
{"segs", "List all segments", segs_cmd},
{"save", "Produce crashdump", save_cmd}, {"save", "Produce crashdump", save_cmd},
{"threads", "List threads", threads_cmd}, {"threads", "List threads", threads_cmd},
{"tlsfdump", "Dump TLSF structures", tlsf_cmd}, {"tlsfdump", "Dump TLSF structures", tlsf_cmd},
@ -546,11 +581,15 @@ dump_cmd(char **ptr)
return 0; return 0;
} }
extern void gc_show_seg(lispobj);
static int static int
print_cmd(char **ptr) print_cmd(char **ptr)
{ {
lispobj obj; lispobj obj;
if (parse_lispobj(ptr, &obj)) print(obj); if (parse_lispobj(ptr, &obj)) {
if (use_smlgc) gc_show_seg(obj);
print(obj);
}
return 0; return 0;
} }
@ -563,6 +602,12 @@ pte_cmd(char **ptr)
return 0; return 0;
} }
static int segs_cmd(__attribute__((unused)) char **ptr) {
extern void sml_heap_dump_everything();
//sml_heap_dump_everything();
return 0;
}
static int static int
regs_cmd(char __attribute__((unused)) **ptr) regs_cmd(char __attribute__((unused)) **ptr)
{ {
@ -761,6 +806,12 @@ backtrace_cmd(char **ptr)
int n; int n;
if (more_p(ptr)) { if (more_p(ptr)) {
long fp = strtol(*ptr, 0, 0);
fprintf(stderr, "using fp %lx\n", fp);
extern void log_backtrace_from_fp(struct thread* th, void *fp, int nframes, int start, FILE *f);
log_backtrace_from_fp(get_sb_vm_thread(), (void*)fp, 1000, 0, stdout);
return 0;
if (!parse_number(ptr, &n)) return 0; if (!parse_number(ptr, &n)) return 0;
} else } else
n = 100; n = 100;
@ -1192,3 +1243,32 @@ int main(int argc, char *argv[], char **envp)
ldb_monitor(); ldb_monitor();
} }
#endif #endif
static uword_t scan_for_hashsets(lispobj* where, lispobj* limit, uword_t arg)
{
for ( ; where < limit ; where += object_size(where) ) {
if (widetag_of(where)==SYMBOL_WIDETAG) {
lispobj value = ((struct symbol*)where)->value;
if (instancep(value)) {
lispobj layout = instance_layout(INSTANCE(value));
if (layout_depth2_id(LAYOUT(layout)) == 145) {
lispobj lname = decode_symbol_name(((struct symbol*)where)->name);
struct vector* name = VECTOR(lname);
lispobj storage = INSTANCE(value)->slots[INSTANCE_DATA_START];
lispobj cells = storage ? INSTANCE(storage)->slots[INSTANCE_DATA_START] : 0;
fprintf(stderr, "sym %lx -> hs %lx -> storage %lx -> cells %lx [%lx] (%s)\n",
(uword_t)where, value, storage, cells, *native_pointer(cells),
(char*)name->data);
}
}
}
}
return 0;
}
static int hashsets_cmd(char __attribute__((unused)) **ptr)
{
walk_generation(scan_for_hashsets, -1, 0);
return 0;
}

View file

@ -208,6 +208,7 @@ void os_unlink_runtime()
bool gc_managed_heap_space_p(lispobj addr) bool gc_managed_heap_space_p(lispobj addr)
{ {
extern int in_bitmapped_subheap(void* addr);
if ((READ_ONLY_SPACE_START <= addr && addr < READ_ONLY_SPACE_END) if ((READ_ONLY_SPACE_START <= addr && addr < READ_ONLY_SPACE_END)
|| (STATIC_SPACE_START <= addr && addr < STATIC_SPACE_END) || (STATIC_SPACE_START <= addr && addr < STATIC_SPACE_END)
#if defined LISP_FEATURE_GENERATIONAL #if defined LISP_FEATURE_GENERATIONAL
@ -225,6 +226,7 @@ bool gc_managed_heap_space_p(lispobj addr)
#endif #endif
) )
return 1; return 1;
if (use_smlgc && in_bitmapped_subheap((void*)addr)) return 1;
return 0; return 0;
} }

View file

@ -205,6 +205,8 @@ int parse_addr(char **ptr, bool safely, char **output)
static lispobj lookup_symbol(char *name) static lispobj lookup_symbol(char *name)
{ {
extern lispobj search_smlgc_heap_for_symbol(char*);
return search_smlgc_heap_for_symbol(name);
uword_t ranges[][2] = { uword_t ranges[][2] = {
{ STATIC_SPACE_OBJECTS_START, (uword_t)static_space_free_pointer }, { STATIC_SPACE_OBJECTS_START, (uword_t)static_space_free_pointer },
#ifdef LISP_FEATURE_IMMOBILE_SPACE #ifdef LISP_FEATURE_IMMOBILE_SPACE

View file

@ -339,10 +339,15 @@ char * simple_base_stringize(struct vector * string)
return newstring; return newstring;
} }
struct vector * classoid_name(lispobj* classoid);
static void brief_struct(lispobj obj) static void brief_struct(lispobj obj)
{ {
if (layoutp(obj)) { // print the classoid this layout is for
struct vector* name = classoid_name(native_pointer(LAYOUT(obj)->classoid));
printf("#<layout-for %s %"OBJ_FMTX">", (char*)name->data, obj);
return;
}
struct instance *instance = INSTANCE(obj); struct instance *instance = INSTANCE(obj);
extern struct vector * instance_classoid_name(lispobj*);
struct vector * classoid_name; struct vector * classoid_name;
classoid_name = instance_classoid_name((lispobj*)instance); classoid_name = instance_classoid_name((lispobj*)instance);
lispobj layout = instance_layout((lispobj*)instance); lispobj layout = instance_layout((lispobj*)instance);

View file

@ -31,4 +31,6 @@ extern void print_list_car_ptrs(lispobj, FILE*);
void odxprint_fun(const char *fmt, ...); void odxprint_fun(const char *fmt, ...);
extern struct vector* instance_classoid_name(lispobj*);
#endif #endif

View file

@ -605,6 +605,8 @@ parse_argv(struct memsize_options memsize_options,
return o; return o;
} }
extern void instant_stop_handler(int, siginfo_t*, void*);
int int
initialize_lisp(int argc, char *argv[], char *envp[]) initialize_lisp(int argc, char *argv[], char *envp[])
{ {
@ -770,6 +772,15 @@ initialize_lisp(int argc, char *argv[], char *envp[])
ll_install_handler(SIGURG, thruption_handler); ll_install_handler(SIGURG, thruption_handler);
# elif defined LISP_FEATURE_SB_THREAD # elif defined LISP_FEATURE_SB_THREAD
ll_install_handler(SIG_STOP_FOR_GC, sig_stop_for_gc_handler); ll_install_handler(SIG_STOP_FOR_GC, sig_stop_for_gc_handler);
struct sigaction sa;
sa.sa_sigaction = instant_stop_handler;
sa.sa_mask = blockable_sigset;
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_NODEFER;
sigaction(SIGPROF, &sa, 0);
//ll_install_handler(SIGPWR, sigpwr_handler);
fprintf(stderr, "installed stop-for-gc handler\n");
# endif # endif
#else #else
/* wos_install_interrupt_handlers(handler); */ /* wos_install_interrupt_handlers(handler); */

View file

@ -661,6 +661,7 @@ static void prepare_dynamic_space_for_final_gc()
char gc_coalesce_string_literals = 0; char gc_coalesce_string_literals = 0;
extern void move_rospace_to_dynamic(int), prepare_readonly_space(int,int); extern void move_rospace_to_dynamic(int), prepare_readonly_space(int,int);
extern lispobj copy_smlgc_heap_to_gencgc(lispobj);
/* Do a non-conservative GC twice, and then save a core with the initial /* Do a non-conservative GC twice, and then save a core with the initial
* function being set to the value of 'lisp_init_function'. * function being set to the value of 'lisp_init_function'.
@ -743,6 +744,10 @@ gc_and_save(char *filename, bool prepend_runtime, bool purify,
struct thread *thread = get_sb_vm_thread(); struct thread *thread = get_sb_vm_thread();
gc_close_thread_regions(thread, 0); gc_close_thread_regions(thread, 0);
gc_close_collector_regions(0); gc_close_collector_regions(0);
gencgc_verbose = 1;
if (use_smlgc) {
lisp_init_function = copy_smlgc_heap_to_gencgc(lisp_init_function);
}
#ifdef LISP_FEATURE_MARK_REGION_GC #ifdef LISP_FEATURE_MARK_REGION_GC
/* Do a minor GC to instate allocation bitmap for new objects. /* Do a minor GC to instate allocation bitmap for new objects.
* This is needed to make heap walking in move_rospace_to_dynamic * This is needed to make heap walking in move_rospace_to_dynamic

View file

@ -277,6 +277,24 @@ uword_t brothertree_find_greatereql(uword_t key, lispobj tree)
return best; return best;
} }
#include <stdio.h>
void dump_brothertree(lispobj tree)
{
if (tree == NIL) return;
lispobj layout = instance_layout(INSTANCE(tree));
if (layout_depth2_id(LAYOUT(layout)) == BROTHERTREE_UNARY_NODE_LAYOUT_ID)
dump_brothertree(((struct unary_node*)INSTANCE(tree))->child);
else {
struct binary_node* node = (void*)INSTANCE(tree);
lispobj l = NIL, r = NIL;
// unless a fringe node, read the left and right pointers
if (!fringe_node_p(node)) l = node->_left, r = node->_right;
dump_brothertree(l);
printf("%lx\n", node->uw_key);
dump_brothertree(r);
}
}
#define BSEARCH_ALGORITHM_IMPL \ #define BSEARCH_ALGORITHM_IMPL \
int low = 0; \ int low = 0; \
int high = nelements - 1; \ int high = nelements - 1; \
@ -363,3 +381,24 @@ split_ordered_list_find(struct split_ordered_list* solist,
node = (void*)native_pointer(node->_node_next); node = (void*)native_pointer(node->_node_next);
} }
} }
#include <stdio.h>
void dump_solist(lispobj list)
{
lispobj ptr = ((struct split_ordered_list*)INSTANCE(list))->head;
int n = 0;
do {
struct split_ordered_list_node* node = (void*)native_pointer(ptr);
lispobj next = node->_node_next;
sword_t hash = fixnum_value(node->node_hash);
if (hash & 1) { // ordinary node
fprintf(stderr, "key node %p hash=%16lx next=%lx key=%lx\n",
node, hash, next, node->so_key);
++n;
} else {
fprintf(stderr, "dummy node %p hash=%16lx next=%lx\n", node, hash, next);
}
ptr = next;
} while (ptr != LFLIST_TAIL_ATOM);
fprintf(stderr, "%d keys\n", n);
}

View file

@ -92,6 +92,13 @@ link_thread(struct thread *th)
th->next=all_threads; th->next=all_threads;
th->prev=0; th->prev=0;
all_threads=th; all_threads=th;
/* This ID is just for debugging the C runtime with a relatively small number of
* threads and you want to refer to them in diagnostic messages using an identifier
* other than kernel thread or pthread which are often hard to distinguish by eye,
* and if thread names are blank or otherwise non-identifying.
* This number can wrap around, resulting in accidental re-use */
static uword_t serialno;
th->serialno = ++serialno; // under a lock, so this is fine
} }
#ifdef LISP_FEATURE_SB_THREAD #ifdef LISP_FEATURE_SB_THREAD
@ -284,7 +291,7 @@ extern pthread_key_t ignore_stop_for_gc;
#if !defined COLLECT_GC_STATS && !defined STANDALONE_LDB && \ #if !defined COLLECT_GC_STATS && !defined STANDALONE_LDB && \
defined LISP_FEATURE_LINUX && defined LISP_FEATURE_SB_THREAD && defined LISP_FEATURE_64_BIT defined LISP_FEATURE_LINUX && defined LISP_FEATURE_SB_THREAD && defined LISP_FEATURE_64_BIT
#define COLLECT_GC_STATS #undef COLLECT_GC_STATS
#endif #endif
#ifdef COLLECT_GC_STATS #ifdef COLLECT_GC_STATS
static struct timespec gc_start_time; static struct timespec gc_start_time;
@ -326,6 +333,11 @@ char* thread_name_from_pthread(pthread_t pointer){
} }
#endif #endif
struct thread* mainthread;
extern void sml_current_user_set_arbdata(void*);
extern void sml_current_worker_set_thread(void*,int);
extern void smlgc_clear_alloc_ptr(struct alloc_ptr*);
void create_main_lisp_thread(lispobj function) { void create_main_lisp_thread(lispobj function) {
#ifdef LISP_FEATURE_WIN32 #ifdef LISP_FEATURE_WIN32
InitializeCriticalSection(&all_threads_lock); InitializeCriticalSection(&all_threads_lock);
@ -333,8 +345,13 @@ void create_main_lisp_thread(lispobj function) {
InitializeCriticalSection(&in_gc_lock); InitializeCriticalSection(&in_gc_lock);
#endif #endif
struct thread *th = alloc_thread_struct(0); struct thread *th = alloc_thread_struct(0);
mainthread = th;
if (!th || arch_os_thread_init(th)==0 || !init_shared_attr_object()) if (!th || arch_os_thread_init(th)==0 || !init_shared_attr_object())
lose("can't create initial thread"); lose("can't create initial thread");
sml_current_worker_set_thread(th, 1);
sml_current_user_set_arbdata(th);
//SYMBOL(FORCE_WEAK_POINTER_BARRIER_ON)->value = 0;
th->state_word.sprof_enable = 1; th->state_word.sprof_enable = 1;
#if defined LISP_FEATURE_SB_THREAD && !defined LISP_FEATURE_GCC_TLS && !defined LISP_FEATURE_WIN32 #if defined LISP_FEATURE_SB_THREAD && !defined LISP_FEATURE_GCC_TLS && !defined LISP_FEATURE_WIN32
pthread_key_create(&current_thread, 0); pthread_key_create(&current_thread, 0);
@ -373,6 +390,10 @@ void create_main_lisp_thread(lispobj function) {
#ifdef COLLECT_GC_STATS #ifdef COLLECT_GC_STATS
atexit(summarize_gc_stats); atexit(summarize_gc_stats);
#endif #endif
fprintf(stderr, "create_main_thread: stack=%p:%p bindings=%p:%p TLS=%p:%p\n",
th->control_stack_start, th->control_stack_end,
th->binding_stack_start, th->alien_stack_start,
th, (char*)th + dynamic_values_bytes);
/* WIN32 has a special stack arrangement, calling /* WIN32 has a special stack arrangement, calling
* call_into_lisp_first_time will put the new stack in the middle * call_into_lisp_first_time will put the new stack in the middle
* of the current stack */ * of the current stack */
@ -449,11 +470,13 @@ init_new_thread(struct thread *th,
#endif #endif
} }
extern void smlgc_unregister_lisp_thread(struct thread*);
static void static void
unregister_thread(struct thread *th, unregister_thread(struct thread *th,
init_thread_data __attribute__((unused)) *scribble) init_thread_data __attribute__((unused)) *scribble)
{ {
block_blockable_signals(0); block_blockable_signals(0);
smlgc_unregister_lisp_thread(th);
gc_close_thread_regions(th, LOCK_PAGE_TABLE|CONSUME_REMAINDER); gc_close_thread_regions(th, LOCK_PAGE_TABLE|CONSUME_REMAINDER);
#ifdef LISP_FEATURE_SB_SAFEPOINT #ifdef LISP_FEATURE_SB_SAFEPOINT
pop_gcing_safety(&scribble->safety); pop_gcing_safety(&scribble->safety);
@ -479,6 +502,7 @@ unregister_thread(struct thread *th,
os_sem_destroy(&semaphores->sprof_sem); os_sem_destroy(&semaphores->sprof_sem);
#endif #endif
#ifndef LISP_FEATURE_SB_SAFEPOINT #ifndef LISP_FEATURE_SB_SAFEPOINT
//pthread_mutex_destroy(&extra_data->signal_delivery_lock);
os_sem_destroy(&semaphores->state_sem); os_sem_destroy(&semaphores->state_sem);
os_sem_destroy(&semaphores->state_not_running_sem); os_sem_destroy(&semaphores->state_not_running_sem);
os_sem_destroy(&semaphores->state_not_stopped_sem); os_sem_destroy(&semaphores->state_not_stopped_sem);
@ -515,6 +539,7 @@ void* new_thread_trampoline(void* arg)
{ {
struct thread* th = arg; struct thread* th = arg;
ASSOCIATE_OS_THREAD(th); ASSOCIATE_OS_THREAD(th);
th->os_kernel_tid = get_nonzero_tid();
#ifdef LISP_FEATURE_SB_SAFEPOINT #ifdef LISP_FEATURE_SB_SAFEPOINT
init_thread_data scribble; init_thread_data scribble;
@ -568,8 +593,14 @@ void* new_thread_trampoline(void* arg)
&& !defined LISP_FEATURE_SB_SAFEPOINT && !defined LISP_FEATURE_SB_SAFEPOINT
th->control_stack_end = (lispobj*)&arg + 1; th->control_stack_end = (lispobj*)&arg + 1;
#endif #endif
th->os_kernel_tid = get_nonzero_tid();
init_new_thread(th, SCRIBBLE, 0); init_new_thread(th, SCRIBBLE, 0);
long foo[3] = {0xFEED, 0xC0FFEE, 0xBEAD};
extern void sml_start(void*), sml_end();
fprintf(stderr, "SB-VM thread %p gets control_stack_end %p\n", th, th->control_stack_end);
sml_start(foo);
sml_current_worker_set_thread(th, 1);
sml_current_user_set_arbdata(th);
/*if (SYMBOL(USE_SMLGC)->value == make_fixnum(-1)) smlgc_clear_alloc_ptr(&th->cons_ap);*/
// Passing the untagged pointer ensures 2 things: // Passing the untagged pointer ensures 2 things:
// - that the pinning mechanism works as designed, and not just by accident. // - that the pinning mechanism works as designed, and not just by accident.
// - that the initial stack does not contain a lisp pointer after it is not needed. // - that the initial stack does not contain a lisp pointer after it is not needed.
@ -577,6 +608,7 @@ void* new_thread_trampoline(void* arg)
funcall1(startfun, (lispobj)lispthread); // both pinned funcall1(startfun, (lispobj)lispthread); // both pinned
// Close the GC region and unlink from all_threads // Close the GC region and unlink from all_threads
unregister_thread(th, SCRIBBLE); unregister_thread(th, SCRIBBLE);
sml_end(); // must balance with sml_start
return 0; return 0;
} }
@ -840,6 +872,18 @@ callback_wrapper_trampoline(
#endif /* LISP_FEATURE_SB_THREAD */ #endif /* LISP_FEATURE_SB_THREAD */
extern void smlgc_clear_alloc_ptr(struct alloc_ptr*);
void initialize_bitmap_alloc_pointers(struct thread* th, int enable) {
int size_log2;
struct alloc_ptr* ap = &th->ap4;
sml_bmword_t mask = enable ? 1 : (sml_bmword_t)-1;
for (size_log2 = 4; size_log2 <= 12; ++size_log2, ++ap) {
ap->blocksize_bytes = 1 << size_log2;
smlgc_clear_alloc_ptr(ap);
ap->freebit.mask = mask;
}
}
/* this is called from any other thread to create the new one, and /* this is called from any other thread to create the new one, and
* initialize all parts of it that can be initialized from another * initialize all parts of it that can be initialized from another
* thread * thread
@ -926,7 +970,7 @@ alloc_thread_struct(void* spaces) {
th->tls_size = dynamic_values_bytes; th->tls_size = dynamic_values_bytes;
#endif #endif
__attribute((unused)) lispobj* tls = (lispobj*)th; lispobj* tls = (lispobj*)th;
#ifdef THREAD_T_NIL_CONSTANTS_SLOT #ifdef THREAD_T_NIL_CONSTANTS_SLOT
tls[THREAD_T_NIL_CONSTANTS_SLOT] = (NIL << 32) | LISP_T; tls[THREAD_T_NIL_CONSTANTS_SLOT] = (NIL << 32) | LISP_T;
#endif #endif
@ -945,6 +989,7 @@ alloc_thread_struct(void* spaces) {
tls[THREAD_TEXT_CARD_COUNT_SLOT] = text_space_size / IMMOBILE_CARD_BYTES; tls[THREAD_TEXT_CARD_COUNT_SLOT] = text_space_size / IMMOBILE_CARD_BYTES;
tls[THREAD_TEXT_CARD_MARKS_SLOT] = (lispobj)text_page_touched_bits; tls[THREAD_TEXT_CARD_MARKS_SLOT] = (lispobj)text_page_touched_bits;
#endif #endif
tls[THREAD_STEPPING_SLOT] = 0;
th->os_address = spaces; th->os_address = spaces;
th->control_stack_start = (lispobj*)aligned_spaces; th->control_stack_start = (lispobj*)aligned_spaces;
@ -994,6 +1039,7 @@ alloc_thread_struct(void* spaces) {
memset(extra_data, 0, sizeof *extra_data); memset(extra_data, 0, sizeof *extra_data);
#if defined LISP_FEATURE_SB_THREAD && !defined LISP_FEATURE_SB_SAFEPOINT #if defined LISP_FEATURE_SB_THREAD && !defined LISP_FEATURE_SB_SAFEPOINT
//pthread_mutex_init(&extra_data->signal_delivery_lock);
os_sem_init(&extra_data->state_sem, 1); os_sem_init(&extra_data->state_sem, 1);
os_sem_init(&extra_data->state_not_running_sem, 0); os_sem_init(&extra_data->state_not_running_sem, 0);
os_sem_init(&extra_data->state_not_stopped_sem, 0); os_sem_init(&extra_data->state_not_stopped_sem, 0);
@ -1029,6 +1075,8 @@ alloc_thread_struct(void* spaces) {
#endif #endif
INIT_THREAD_REGIONS(th); INIT_THREAD_REGIONS(th);
initialize_bitmap_alloc_pointers(th, use_smlgc);
#ifdef LISP_FEATURE_SB_THREAD #ifdef LISP_FEATURE_SB_THREAD
/* This parallels the same logic in globals.c for the /* This parallels the same logic in globals.c for the
* single-threaded foreign_function_call_active, KLUDGE and * single-threaded foreign_function_call_active, KLUDGE and
@ -1093,7 +1141,8 @@ alloc_thread_struct(void* spaces) {
thread_private_events(th,i) = CreateEvent(NULL,FALSE,FALSE,NULL); thread_private_events(th,i) = CreateEvent(NULL,FALSE,FALSE,NULL);
thread_extra_data(th)->synchronous_io_handle_and_flag = 0; thread_extra_data(th)->synchronous_io_handle_and_flag = 0;
#endif #endif
th->stepping = 0; fprintf(stderr, "alloc_thread_struct: stack range: %p .. %p\n", th->control_stack_start,
th->control_stack_end);
return th; return th;
} }
#ifdef LISP_FEATURE_SB_THREAD #ifdef LISP_FEATURE_SB_THREAD
@ -1322,3 +1371,13 @@ void wake_thread(struct thread_instance* lispthread)
#endif #endif
} }
#endif #endif
void print_thread_cons_metrics(){
/*
struct thread* th = get_sb_vm_thread();
printf("cons stats: 1=%d+%d 2=%d+%d 3=%d+%d\n",
(int)th->cons1_fast, (int)th->cons1_slow,
(int)th->cons2_fast, (int)th->cons2_slow,
(int)th->cons3_fast, (int)th->cons3_slow);
*/
}

View file

@ -47,7 +47,7 @@ typedef struct hopscotch_table* inverted_heap_t;
#define inverted_heap_get_ref(graph, key) hopscotch_get_ref(graph, key) #define inverted_heap_get_ref(graph, key) hopscotch_get_ref(graph, key)
#endif #endif
int heap_trace_verbose = 0; int heap_trace_verbose = 2;
typedef uintptr_t traceroot_pointer; typedef uintptr_t traceroot_pointer;
@ -91,12 +91,10 @@ static int traceroot_gen_of(lispobj obj) {
static const char* classify_obj(lispobj ptr) static const char* classify_obj(lispobj ptr)
{ {
extern lispobj* instance_classoid_name(lispobj*);
lispobj* name; // a Lisp string lispobj* name; // a Lisp string
switch(lowtag_of(ptr)) { switch(lowtag_of(ptr)) {
case INSTANCE_POINTER_LOWTAG: case INSTANCE_POINTER_LOWTAG:
name = instance_classoid_name(native_pointer(ptr)); name = (void*)instance_classoid_name(native_pointer(ptr));
if (widetag_of(name) == SIMPLE_BASE_STRING_WIDETAG) return (char*)(name + 2); if (widetag_of(name) == SIMPLE_BASE_STRING_WIDETAG) return (char*)(name + 2);
break; break;
case LIST_POINTER_LOWTAG: case LIST_POINTER_LOWTAG:
@ -715,8 +713,10 @@ static bool record_ptr(lispobj* source, lispobj target, struct scan_state* ss)
return 1; return 1;
} }
extern int in_sml_heap_range_p(lispobj);
#define relevant_ptr_p(x) \ #define relevant_ptr_p(x) \
(find_page_index((void*)(x))>=0||immobile_space_p((lispobj)(x))||readonly_space_p(x)) (in_sml_heap_range_p(x) || find_page_index((void*)(x))>=0 || \
immobile_space_p((lispobj)(x)) || readonly_space_p(x))
#define COUNT_POINTER(x) { ++n_scanned_words; \ #define COUNT_POINTER(x) { ++n_scanned_words; \
if (!is_lisp_pointer(x)) ++n_immediates; \ if (!is_lisp_pointer(x)) ++n_immediates; \
@ -812,6 +812,15 @@ static uword_t build_refs(lispobj* where, lispobj* end,
} }
} }
break; break;
case SYMBOL_WIDETAG:
{
struct symbol* sym = (void*)where;
check_ptr(decode_symbol_name(sym->name));
check_ptr(sym->value);
check_ptr(sym->info);
check_ptr(sym->fdefn);
}
continue;
case FILLER_WIDETAG: continue; case FILLER_WIDETAG: continue;
default: default:
if (!(other_immediate_lowtag_p(widetag) && LOWTAG_FOR_WIDETAG(widetag))) if (!(other_immediate_lowtag_p(widetag) && LOWTAG_FOR_WIDETAG(widetag)))
@ -871,6 +880,20 @@ static void scan_spaces(struct scan_state* ss)
show_tally(old, ss, "text"); show_tally(old, ss, "text");
#endif #endif
old = *ss; old = *ss;
#if 0
extern int gather_all_smlheap_objects(uword_t** list);
uword_t* list;
int count = gather_all_smlheap_objects(&list);
printf("traceroot got %d SML heap objects\n", count);
int i;
for(i=0; i<count; ++i) {
lispobj* objbase = (lispobj*)list[i];
lispobj* end = objbase + object_size(objbase);
build_refs(objbase, end, ss);
}
show_tally(old, ss, "SMLgc");
old = *ss;
#endif
walk_generation((uword_t(*)(lispobj*,lispobj*,uword_t))build_refs, walk_generation((uword_t(*)(lispobj*,lispobj*,uword_t))build_refs,
-1, (uword_t)ss); -1, (uword_t)ss);
show_tally(old, ss, "dynamic"); show_tally(old, ss, "dynamic");

View file

@ -622,3 +622,23 @@ double sb_hypot (double x, double y) {
} }
#endif #endif
#if 0
extern void clobber_clobberables();
int clock_gettime(int clockid, struct timespec* ts)
{
int fooseconds = 0;
if (clockid == CLOCK_THREAD_CPUTIME_ID) {
ts->tv_sec = 0xBABAB00E;
ts->tv_nsec = 0xABADBAD;
clobber_clobberables();
return 0;
} else {
ts->tv_sec = ++fooseconds;
ts->tv_nsec = 1;
clobber_clobberables();
return 0;
}
}
#endif

View file

@ -29,6 +29,7 @@
#include "unaligned.h" #include "unaligned.h"
#include "search.h" #include "search.h"
#include "var-io.h" #include "var-io.h"
#include "code.h"
#include "genesis/fdefn.h" #include "genesis/fdefn.h"
#include "genesis/static-symbols.h" #include "genesis/static-symbols.h"
@ -38,6 +39,7 @@
#define INT3_INST 0xCC #define INT3_INST 0xCC
#define INTO_INST 0xCE #define INTO_INST 0xCE
#define UD1_INST 0xb90f
#define UD2_INST 0x0b0f #define UD2_INST 0x0b0f
#define BREAKPOINT_WIDTH 1 #define BREAKPOINT_WIDTH 1
@ -820,3 +822,89 @@ lispobj call_into_lisp_first_time(lispobj fun, lispobj *args, int nargs) {
} }
#include "x86-arch-shared.inc" #include "x86-arch-shared.inc"
#define JAE_rel8 0x73
int possibly_implicit_pseudoatomic(unsigned char* pc) {
unsigned char opcode = *pc;
if (opcode == JAE_rel8) return 1;
if (opcode == 0x0F && pc[1] == 0x83) return 1; // JAE rel32
int lock = 0;
if (*pc == 0xF0) { lock = 1; ++pc; }
if ((*pc & 0xF8) == 0x48) { // REX.w
unsigned char first = pc[1];
unsigned char second = pc[2];
if (first == 0x89 || // MOV r/m64, r64
first == 0xC7 || // MOV r/m64, imm32
(lock && (first == 0x0F) && (second == 0xB1)))
return 1;
}
return 0;
}
uint32_t* code_pseudoatomic_locs(struct code* code, int* pcount)
{
int n_funs = code_n_funs(code);
if (!n_funs) return 0;
uint32_t* end = (uint32_t*)((lispobj*)code + code_total_nwords(code));
int n_locations = end[-(n_funs + 2)];
if (!n_locations) return 0;
*pcount = n_locations;
return &end[-(n_funs + 2 + n_locations)];
}
void adjust_context_for_implicit_pseudoatomic(os_context_t* context)
{
extern uword_t* codeblob_from_interior_ptr(void* addr);
unsigned char* pc = (void*)os_context_pc(context);
if (!possibly_implicit_pseudoatomic(pc)) { // look only at the bytes @ PC
tprintf_("GC sig @ %p", pc);
return;
}
lispobj* code = codeblob_from_interior_ptr(pc);
if (!code) {
tprintf_("GC sig @ %p (non-lisp)", pc);
return;
}
uint32_t* pa_locs;
int nlocs, loc_index = -1;
pa_locs = code_pseudoatomic_locs((void*)code, &nlocs);
unsigned char *condjmp = 0, *memory_op = 0;
if (pa_locs) {
// find the nearest pseudoatomic store (or cmpxchg) below PC
unsigned char* insts = (void*)code_text_start((void*)code);
uint32_t pc_offs = pc - insts;
loc_index = bsearch_lesseql_uint32(pc_offs, pa_locs, nlocs);
if (loc_index >= 0) {
condjmp = insts + pa_locs[loc_index];
if (*condjmp == JAE_rel8)
memory_op = condjmp + 2;
else
memory_op = condjmp + 6;
}
}
// If the interrupted PC is one of the two indivisible instructions
// following the CMP of the GC phase, then upon return-from-interupt
// take the branch.
if (pc == condjmp || pc == memory_op) {
/* If interrupted at either of the 2 instructions that follow comparison
* of the GC phase, take the slow path on return from interrupt, as if
* a phase change occurred just prior to delivery of the signal */
unsigned char* next_inst;
int disp;
if (*condjmp == JAE_rel8) {
next_inst = condjmp + 2;
disp = (int)*(signed char*)(condjmp + 1);
} else {
next_inst = condjmp + 6;
disp = *(int*)(condjmp + 2);
}
pc = next_inst + disp;
set_os_context_pc(context, (uword_t)pc);
tprintf_("GC sig @ %p code %p + %x [%02x %02x %02x] phase %d, PA @ %p",
pc, code, (int)((char*)pc - code_text_start((struct code*)code)),
pc[0], pc[1], pc[3], get_sb_vm_thread()->gc_phase, condjmp);
} else {
tprintf_("GC sig @ %p not pseudo-atomic");
}
}

View file

@ -83,7 +83,16 @@
#define CARG2 %rsi #define CARG2 %rsi
#define CARG3 %rdx #define CARG3 %rdx
#endif #endif
.text
.globl GNAME(exception_handling_load)
TYPE(GNAME(exception_handling_load))
.align align_16byte,0x90
GNAME(exception_handling_load):
mov (CARG1), %rax # if fault, handler loads RAX with 0 and skips this
ret
SIZE(GNAME(exception_handling_load))
#ifdef LISP_FEATURE_OS_THREAD_STACK #ifdef LISP_FEATURE_OS_THREAD_STACK
.text .text
.globl GNAME(funcall1_switching_stack) .globl GNAME(funcall1_switching_stack)
@ -114,6 +123,37 @@ GNAME(funcall1_switching_stack):
#endif #endif
.text .text
.globl GNAME(get_nonvolatile_gprs)
TYPE(GNAME(get_nonvolatile_gprs))
.align align_16byte,0x90
// copy rbx, r12..r15 (in no particular order) into array pointed to rdi
GNAME(get_nonvolatile_gprs):
mov %r12, 0(%rdi)
mov %r13, 8(%rdi)
mov %r14, 16(%rdi)
mov %r15, 24(%rdi)
mov %rbx, 32(%rdi)
ret
SIZE(GNAME(get_nonvolatile_gprs))
.globl GNAME(clobber_clobberables)
TYPE(GNAME(clobber_clobberables))
.align align_16byte,0x90
GNAME(clobber_clobberables):
xor %rax, %rax
lea 0xeefbad1(%rax), %rcx
lea 0xeefbad2(%rax), %rdx
lea 0xeefbad3(%rax), %rsi
lea 0xeefbad4(%rax), %rdi
lea 0xeefbad8(%rax), %r8
lea 0xeefbad9(%rax), %r9
lea 0xeefbada(%rax), %r10
lea 0xeefbadb(%rax), %r11
ret
SIZE(GNAME(clobber_clobberables))
.globl GNAME(clobber_clobberables)
.globl GNAME(call_into_lisp_first_time_) .globl GNAME(call_into_lisp_first_time_)
TYPE(GNAME(call_into_lisp_first_time_)) TYPE(GNAME(call_into_lisp_first_time_))

View file

@ -1,3 +1,4 @@
(invoke-restart 'run-tests::skip-file)
(progn (progn
(defun on-large-page-p (x) (defun on-large-page-p (x)
(and (eq (sb-ext:heap-allocated-p x) :dynamic) (and (eq (sb-ext:heap-allocated-p x) :dynamic)

View file

@ -606,7 +606,14 @@
(format *error-output* "Failure:~{~%~A~}~%" lines) (format *error-output* "Failure:~{~%~A~}~%" lines)
(error "Bad result for ~S" symbol)))))))))) (error "Bad result for ~S" symbol))))))))))
(defun collect-objects-pointing-off-heap () ;;; Using this function you can search for objects pointing
;;; to a particular subspace, such as :STATIC or :READ-ONLY.
;;; The default list of spaces '(NIL) implies that you want to see
;;; any object NOT pointing to a GC-managed space.
;;; As a special case, we ignore NIL in any slot, because without that
;;; exception, most objects would point to static space
;;; which is not particularly enlightening.
(defun collect-objects-pointing-to (&optional (spaces '(nil)))
(let (list) (let (list)
(flet ((add-to-result (obj referent) (flet ((add-to-result (obj referent)
;; If this is a code component and it points to fixups ;; If this is a code component and it points to fixups
@ -624,8 +631,9 @@
list))))) list)))))
(macrolet ((visit (referent) (macrolet ((visit (referent)
`(let ((r ,referent)) `(let ((r ,referent))
(when (and (is-lisp-pointer (get-lisp-obj-address r)) (when (and r
(not (heap-allocated-p r)) (is-lisp-pointer (get-lisp-obj-address r))
(member (heap-allocated-p r) spaces)
(add-to-result obj r)) (add-to-result obj r))
(return-from done))))) (return-from done)))))
(map-allocated-objects (map-allocated-objects

View file

@ -1,4 +1,4 @@
#|
;;; Please keep all the "smoke" tests in this file fairly lightweight. ;;; Please keep all the "smoke" tests in this file fairly lightweight.
;;; Don't crash on layoutless instances. It's that simple! ;;; Don't crash on layoutless instances. It's that simple!
@ -12,3 +12,4 @@
(defvar *foo* (cons nil nil)) (defvar *foo* (cons nil nil))
(rplacd *foo* *foo*) (rplacd *foo* *foo*)
(with-test (:name :circular-list) (gc :gen 7)) (with-test (:name :circular-list) (gc :gen 7))
|#

View file

@ -1,4 +1,4 @@
#+(and linux sb-thread 64-bit) #+nil ; (and linux sb-thread 64-bit)
(sb-alien:alien-funcall (sb-alien:extern-alien (sb-alien:alien-funcall (sb-alien:extern-alien
"reset_gc_stats" "reset_gc_stats"
(function sb-alien:void))) (function sb-alien:void)))

View file

@ -19,6 +19,22 @@
(make-list (reduce #'max (mapcar #'length k)))) (make-list (reduce #'max (mapcar #'length k))))
(compile 'foo) (compile 'foo)
(with-test (:name :recognize-implicit-pseudoatomic)
(with-alien ((matchp (function int system-area-pointer)
:extern "possibly_implicit_pseudoatomic"))
(let ((all (sb-vm:list-allocated-objects :all :type sb-vm:code-header-widetag))
(n 0))
(dolist (c all)
(let ((locs (sb-impl::code-pseudo-atomic-locations c)))
(when (plusp (length locs))
(sb-int:dovector (loc locs)
(let ((abs-pc (sb-sys:sap+ (sb-kernel:code-instructions c) loc)))
(assert (= (alien-funcall matchp abs-pc) 1))))
(incf n))))
(format t "~&Tested ~D/~D codeblobs~%" n (length all)))))
(with-test (:name :lowtag-test-elision) (with-test (:name :lowtag-test-elision)
;; This tests a certain behavior that while "undefined" should at least not ;; This tests a certain behavior that while "undefined" should at least not
;; be fatal. This is important for things like hash-table :TEST where we might ;; be fatal. This is important for things like hash-table :TEST where we might