#include <signal.h>
#include <stdio.h>
#include <errno.h>
#include "thread.h"
#include "murmur_hash.h"
#include "gc-assert.h"
#include "arch.h"
#include "gc-internal.h"
#include "gc.h"
#include "lispregs.h"
#if !defined LISP_FEATURE_X86 && !defined LISP_FEATURE_X86_64
#include "callframe.inc"
#endif
#include "genesis/compiled-debug-fun.h"
#ifdef MEMORY_SANITIZER
#include <sanitizer/msan_interface.h>
#endif
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
* each thread allocates a storage for samples (traces) and a hash-table
* to groups matching samples together. Collisions in the table are resolved
* by chaining.
*
* At each sample collection:
* 1. gather up to TRACE_BUFFER_LEN program-counter locations as the stack trace
* 2. if it exceeds MAX_RECORDED_TRACE_LEN locations, condense it to
the first and last frames with an elision marker in between.
* 3. compute a hash of the trace
* 4. if trace is present in hash-table
* then increment the count
* else
* ensure trace has stable PC locations
* add or update
* Each PC location is tentatively recorded as raw PC location.
* To ensure location stability:
* Check whether each raw PC loc is within lisp code and not pseudostatic.
* For each which is movable, express the PC as a code serial# and offset.
*/
#define ELEMENT_SIZE 8
#define TRACE_BUFFER_LEN 300
#define MAX_RECORDED_TRACE_LEN 62
#define N_BUCKETS 0x10000
#define HASH_MASK (N_BUCKETS-1)
#ifdef LISP_FEATURE_64_BIT
# define CAPACITY_MAX 8*1024*1024
#else
# define CAPACITY_MAX 1024*1024
#endif
#define INITIAL_FREE_POINTER 2
struct sprof_data {
uint32_t *buckets;
#ifndef LISP_FEATURE_64_BIT
uint32_t padding0;
#endif
uint32_t free_pointer;
uint32_t capacity;
};
#define TRACE_PREFIX_ELEMENTS 2
struct trace {
uint32_t next;
uint32_t multiplicity;
#ifdef LISP_FEATURE_64_BIT
#define trace_len(trace) ((int32_t)((trace)->header))
uword_t header;
uword_t locs[TRACE_BUFFER_LEN];
#else
#define trace_len(trace) trace->len
sword_t len;
uword_t hash;
struct loc { uint32_t word0, word1; } locs[TRACE_BUFFER_LEN];
#endif
};
static inline struct trace* sprof_data_trace(struct sprof_data* data, uint32_t index) {
return (void*)((char*)data + index*ELEMENT_SIZE);
}
static int in_stack_range(uword_t pc, struct thread* thread)
{
return pc >= (uword_t)thread->control_stack_start
&& pc < (uword_t)thread->control_stack_end;
}
static inline uword_t word_mix(uword_t x, uword_t y)
{
uword_t mul = 3622009729038463111LL & UINT_MAX;
uword_t xor = 608948948376289905LL & UINT_MAX;
sword_t xy = x * mul + y;
return xor ^ xy ^ (xy >> 5);
}
#ifdef LISP_FEATURE_64_BIT
static uint32_t compute_hash(uword_t* elements, int len) {
int i;
uword_t hash = len;
for (i = 0; i < len; i++) {
uword_t pc = elements[i];
hash = word_mix(hash, pc);
}
return (uint32_t)murmur3_fmix64(hash);
}
#else
static uint32_t compute_hash(struct loc* elements, int len) {
int i;
uword_t hash = len;
for (i = 0; i < len; i++) {
uword_t pc = elements[i].word0;
hash = word_mix(hash, pc);
}
return murmur3_fmix32(hash);
}
#endif
static inline void store_trace_header(struct trace* trace, uint32_t hash, uint32_t len)
{
#ifdef LISP_FEATURE_64_BIT
trace->header = ((uword_t)hash<<32) | len;
#else
trace->hash = hash;
trace->len = len;
#endif
}
static int trace_equal(struct trace* a, struct trace* b)
{
int i;
#ifdef LISP_FEATURE_64_BIT
if (a->header != b->header) return 0;
for (i=0; i<trace_len(a); ++i) if (a->locs[i] != b->locs[i]) return 0;
#else
if (a->hash != b->hash || a->len != b->len) return 0;
for (i=0; i<trace_len(a); ++i)
if (a->locs[i].word0 != b->locs[i].word0 ||
a->locs[i].word1 != b->locs[i].word1) return 0;
#endif
return 1;
}
static uint32_t* hash_get(struct sprof_data* data, struct trace* trace, uint32_t hash)
{
int index = hash & HASH_MASK;
uint32_t entry = data->buckets[index];
while (entry) {
struct trace* key = sprof_data_trace(data, entry);
if (trace_equal(key, trace)) return &key->multiplicity;
entry = key->next;
}
return 0;
}
static uint32_t* hash_insert(struct sprof_data* data, struct trace* trace, uint32_t hash)
{
int n_elements = TRACE_PREFIX_ELEMENTS + trace_len(trace);
struct trace* copy = sprof_data_trace(data, data->free_pointer);
memcpy(copy, trace, n_elements * ELEMENT_SIZE);
copy->multiplicity = 0;
int index = hash & HASH_MASK;
copy->next = data->buckets[index];
data->buckets[index] = data->free_pointer;
data->free_pointer += n_elements;
return ©->multiplicity;
}
static int unstable_program_counter_p(uword_t addr)
{
#ifdef LISP_FEATURE_CHENEYGC
return (DYNAMIC_0_SPACE_START <= addr &&
addr < DYNAMIC_0_SPACE_START + dynamic_space_size)
|| (DYNAMIC_1_SPACE_START <= addr &&
addr < DYNAMIC_1_SPACE_START + dynamic_space_size);
#else
* space will not be garbage-collected during the profiling run.
* Similar issue for fdefns which contain an executable instruction
* as well as closure-calling trampolines and builtin-trampoline GFs.
* It might be neat to mark some objects with a bit saying
* never to move them if they appeared in a trace.
* When would the bit get cleared though? I don't know */
page_index_t page = find_page_index((void*)addr);
return page >= 0 && page_table[page].gen != PSEUDO_STATIC_GENERATION;
#endif
}
#ifdef LISP_FEATURE_64_BIT
#define STORE_PC(tr, indx, val) (tr).locs[indx] = val
#define STORE_REL_PC(tr, indx, ser, offs) \
(tr).locs[indx] = ((uword_t)1 << 63) | ((offs) << 32) | (ser)
#else
#define STORE_PC(tr, indx, val) (tr).locs[indx].word0 = val; (tr).locs[indx].word1 = 0
#define STORE_REL_PC(tr, indx, ser, offs) \
(tr).locs[indx].word0 = ser; (tr).locs[indx].word1 = offs
#endif
* Locations in foreign and pseudo-static code may remain as-is.
* Return 1 if the trace was affected by stabilizing it. */
static int NO_SANITIZE_MEMORY stabilize(struct trace* trace)
{
int len = trace_len(trace);
int changedp = 0;
int i;
uword_t pc;
for(i=0; i<len; ++i) {
#ifdef LISP_FEATURE_64_BIT
pc = trace->locs[i];
#else
pc = trace->locs[i].word0;
#endif
if (unstable_program_counter_p(pc)) {
struct code *code = (void*)component_ptr_from_pc((char*)pc);
if (code) {
STORE_REL_PC(*trace, i, code_serialno(code), (pc - (uword_t)code));
changedp = 1;
} else {
STORE_PC(*trace, i, (uword_t)-1);
}
}
}
return changedp;
}
static int NO_SANITIZE_MEMORY
gather_trace_from_context(struct thread* thread, os_context_t* context,
struct trace* trace, int limit)
{
uword_t pc = *os_context_pc_addr(context);
int len = 1;
STORE_PC(*trace, 0, pc);
#if defined LISP_FEATURE_X86 || defined LISP_FEATURE_X86_64
uword_t* fp = (uword_t*)os_context_frame_pointer(context);
uword_t* sp = (uword_t*)*os_context_sp_addr(context);
if (fp >= sp && fp < thread->control_stack_end) {
for(;;) {
uword_t prev_fp = *fp;
uword_t prev_pc = fp[1];
#ifdef LISP_FEATURE_64_BIT
if ((sword_t)prev_pc < 0) prev_pc = (sword_t)-1;
#endif
STORE_PC(*trace, len, prev_pc);
if (++len == limit) break;
if (prev_fp <= (uword_t)fp || prev_fp >= (uword_t)thread->control_stack_end
|| in_stack_range(prev_pc, thread)) break;
fp = (uword_t*)prev_fp;
}
}
#else
if (gc_managed_heap_space_p(pc) && component_ptr_from_pc((void*)pc)) {
struct call_frame* frame = (void*)(*os_context_register_addr(context, reg_CFP));
if (in_stack_range((uword_t)frame, thread)
&& lowtag_of(frame->saved_lra) == OTHER_POINTER_LOWTAG
&& component_ptr_from_pc((void*)frame->saved_lra)) {
STORE_PC(*trace, len, frame->saved_lra);
++len;
}
}
#endif
return len;
}
extern struct compiled_debug_fun*
debug_function_from_pc (struct code* code, void *pc);
static int gather_trace_from_frame(struct thread* thread, uword_t* fp,
struct trace* trace, int limit)
{
int len = 0;
#if defined LISP_FEATURE_X86 || defined LISP_FEATURE_X86_64
if (fp >= thread->control_stack_start && fp < thread->control_stack_end) {
for(;;) {
uword_t prev_fp = *fp;
uword_t prev_pc = fp[1];
#ifdef LISP_FEATURE_64_BIT
if ((sword_t)prev_pc < 0) prev_pc = (sword_t)-1;
#endif
STORE_PC(*trace, len, prev_pc);
if (++len == limit) break;
if (prev_fp <= (uword_t)fp || prev_fp >= (uword_t)thread->control_stack_end
|| in_stack_range(prev_pc, thread)) break;
fp = (uword_t*)prev_fp;
}
}
#else
struct call_info info;
memset(&info, 0, sizeof info);
info.frame = (struct call_frame *)access_control_frame_pointer(thread);
if (lisp_frame_previous(thread, &info) && info.code) {
struct compiled_debug_fun *df;
df = (void*)debug_function_from_pc((struct code *)info.code,
(void*)((uword_t)info.code + info.pc));
if (df) {
STORE_PC(*trace, 0, (uword_t)info.code + info.pc);
++len;
if (lisp_frame_previous(thread, &info) && info.code) {
STORE_PC(*trace, 0, (uword_t)info.code + info.pc);
++len;
}
}
}
#endif
return len;
}
#define LOCKED_BY_SELF 1
#define LOCKED_BY_OTHER 2
#define LOCK_CONTESTED (LOCKED_BY_SELF|LOCKED_BY_OTHER)
static void* initialize_sprof_data(struct thread* thread)
{
void* buckets = (void*)os_allocate(N_BUCKETS * sizeof (uint32_t));
if (!buckets) return 0;
int capacity = 128*1024;
struct sprof_data *data = (void*)os_allocate(capacity * ELEMENT_SIZE);
if (!data) { os_deallocate(buckets, N_BUCKETS * sizeof (uint32_t)); return 0; }
data->capacity = capacity;
data->free_pointer = INITIAL_FREE_POINTER;
data->buckets = buckets;
thread->sprof_data = (lispobj)data;
return data;
}
static struct sprof_data* enlarge_buffer(struct sprof_data* current,
uint32_t new_capacity)
{
char * new_buffer = os_allocate(new_capacity * ELEMENT_SIZE);
memcpy(new_buffer, current, current->free_pointer * ELEMENT_SIZE);
os_invalidate((void*)current, current->capacity * ELEMENT_SIZE);
current = (struct sprof_data*)new_buffer;
current->capacity = new_capacity;
return current;
}
#define SPROF_LOCK(th) thread_extra_data(th)->sprof_lock
#ifdef LISP_FEATURE_SB_THREAD
* If this thread didn't acquire the lock (old == 0 or old == 2), do nothing.
* The only interesting case is LOCK_CONTESTED */
#define RELEASE_LOCK(th) \
int oldval = __sync_val_compare_and_swap(&SPROF_LOCK(th), LOCKED_BY_SELF, 0); \
if (oldval == LOCK_CONTESTED) { \
oldval = __sync_val_compare_and_swap(&SPROF_LOCK(th), LOCK_CONTESTED, LOCKED_BY_OTHER); \
gc_assert(oldval == LOCK_CONTESTED); \
os_sem_post(&thread_extra_data(th)->sprof_sem, "sprof"); \
}
#else
#define RELEASE_LOCK(th) SPROF_LOCK(th) = 0
#endif
int sb_sprof_trace_ct;
int sb_sprof_trace_ct_max;
so anything may appear as unwritten from C depending on whether any C code
ever marked them. So it was basically down to luck whether this worked or not */
static int NO_SANITIZE_MEMORY
collect_backtrace(struct thread* th, int contextp, void* context_or_fp)
{
int oldcount = __sync_fetch_and_add(&sb_sprof_trace_ct, 1);
if (oldcount >= sb_sprof_trace_ct_max) {
__sync_fetch_and_sub(&sb_sprof_trace_ct, 1);
return -1;
}
struct trace trace;
int len;
if (contextp)
len = gather_trace_from_context(th, context_or_fp, &trace, TRACE_BUFFER_LEN);
else
len = gather_trace_from_frame(th, context_or_fp, &trace, TRACE_BUFFER_LEN);
if (len < 1) return len;
if (len > MAX_RECORDED_TRACE_LEN) {
int midpoint = MAX_RECORDED_TRACE_LEN/2;
int suffix = midpoint-1;
STORE_PC(trace, midpoint, (uword_t)-1);
memmove(&trace.locs[midpoint+1], &trace.locs[len-suffix], N_WORD_BYTES*suffix);
len = MAX_RECORDED_TRACE_LEN;
}
uword_t hash = compute_hash(trace.locs, len);
store_trace_header(&trace, hash, len);
if (__sync_val_compare_and_swap(&SPROF_LOCK(th), 0, LOCKED_BY_SELF)!=0)
return -2;
struct sprof_data* data = (void*)th->sprof_data;
if (!data) data = initialize_sprof_data(th);
uint32_t* pcount;
if ((pcount = hash_get(data, &trace, hash)) == NULL) {
if (stabilize(&trace)) {
hash = compute_hash(trace.locs, len);
store_trace_header(&trace, hash, len);
pcount = hash_get(data, &trace, hash);
}
if (!pcount) {
uint32_t n_elements = TRACE_PREFIX_ELEMENTS + len;
uint32_t capacity = data->capacity;
if (data->free_pointer + n_elements > capacity) {
if (capacity == CAPACITY_MAX) return 0;
if (SPROF_LOCK(th) & LOCKED_BY_OTHER) return 0;
data = enlarge_buffer(data, 2*capacity);
th->sprof_data = (lispobj)data;
}
pcount = hash_insert(data, &trace, hash);
}
}
++*pcount;
return 1;
}
static void diagnose_failure(struct thread* thread) {
struct sprof_data* data = (void*)thread->sprof_data;
if (data && data->capacity == CAPACITY_MAX) {
thread->state_word.sprof_enable = 0;
#ifdef LISP_FEATURE_SB_THREAD
char msg[100];
int msglen = sprintf(msg,
"WARNING: pthread %p disabled sprof sampler to limit memory use\n",
(void*)pthread_self());
ignore_value(write(2, msg, msglen));
#endif
}
}
void record_backtrace_from_context(void *context, struct thread* thread) {
int success = collect_backtrace(thread, 1, context) == 1;
RELEASE_LOCK(thread);
if (!success) diagnose_failure(thread);
}
* check in interrupt.c, which would return false during GC, because Lisp binds
* *INTERRUPTS-ENABLED* to NIL in the thread which performs the GC; and all other
* threads are in their stop_for_gc handler which blocks async signals including
* SIGPROF, as per the sa_mask in the sigaction() call that assigned the handler.
* But now that SIGPROF is never deferred, we have to be careful around GC.
* There's a complicated solution and an easy solution. The complicated is to have
* component_ptr_from_pc() fail safely if called, so that taking a sample is fine
* provided that all PC locations are pseudo-static - in that case we do not use
* component_ptr_from_pc() in the signal handler.
* The easy out is just to drop the sample; so that's what we do, and versus
* blocking/unblocking SIGPROF in collect_garbage(), it avoids 2 system calls. */
void sigprof_handler(int sig, __attribute__((unused)) siginfo_t* info,
void *context)
{
if (gc_active_p) return;
int _saved_errno = errno;
struct thread* thread = get_sb_vm_thread();
if (thread) {
if (thread->state_word.sprof_enable)
record_backtrace_from_context(context, thread);
else
sigaddset(os_context_sigmask_addr(context), sig);
}
errno = _saved_errno;
}
#if !(defined LISP_FEATURE_PPC || defined LISP_FEATURE_PPC64 || defined LISP_FEATURE_SPARC)
void allocator_record_backtrace(void* frame_ptr, struct thread* thread)
{
int success = collect_backtrace(thread, 0, frame_ptr) == 1;
RELEASE_LOCK(thread);
if (!success) diagnose_failure(thread);
}
#endif
uword_t acquire_sprof_data(struct thread* thread)
{
int old = __sync_fetch_and_or(&SPROF_LOCK(thread), LOCKED_BY_OTHER);
#ifdef LISP_FEATURE_SB_THREAD
if (old != 0) {
gc_assert(old == LOCKED_BY_SELF);
os_sem_wait(&thread_extra_data(thread)->sprof_sem, "sprof");
gc_assert(SPROF_LOCK(thread) == LOCKED_BY_OTHER);
}
#else
gc_assert(old == 0);
#endif
uword_t retval = __sync_val_compare_and_swap(&thread->sprof_data, 0, 0);
if (retval) {
__sync_val_compare_and_swap(&thread->sprof_data, retval, 0);
#ifdef MEMORY_SANITIZER
int freeptr = ((struct sprof_data*)retval)->free_pointer;
__msan_unpoison((void*)retval, freeptr * ELEMENT_SIZE);
#endif
}
__sync_fetch_and_and(&SPROF_LOCK(thread), 0);
return retval;
}