Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CODSPEED-CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,30 @@ last-level cache when `--LL` (or its `--L2` alias) supplies the cache configurat
The warning remains when LL is auto-detected. Cache geometry and simulation
results are unchanged.

### Callgrind: no crash on signals handled on an alternate stack

Callgrind no longer aborts with `Assertion 'sigNum == CLG_(current_state).sig'
failed` when a signal handler runs on a `sigaltstack` placed above the
interrupted stack. The stack-pointer based unwinding mistook such a handler for
a `longjmp` out of the handler. Go programs hit this on every preemption signal
(`SIGURG`) and during garbage collection.

## Features

### Callgrind: Go goroutine call stacks (amd64)

On amd64, Callgrind keeps one call stack per Go goroutine, so each goroutine's
frames stay under its own entry function without `GODEBUG`, `GOMAXPROCS` or cgo
settings. Callgrind follows the Go runtime's stack switches (`runtime.gogo`,
`runtime.mcall`, `runtime.systemstack`, `runtime.morestack` and the vDSO and cgo
calls that run on the g0 stack), keyed by the goroutine's `g` register. Work a
goroutine does on the g0 stack stays under its caller, time a goroutine spends
suspended is not part of its frames' inclusive cost, and stacks the runtime
moves are followed.

Known limitation: some call edges inside the Go runtime's garbage collector
still carry a recursion level (`'2`) that depends on GC timing.

### Fixed Linux startup stack layout

Reduce simulated cache-miss variation from argument and environment sizes
Expand Down
1 change: 1 addition & 0 deletions callgrind/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ CALLGRIND_SOURCES_COMMON = \
dump.c \
events.c \
fn.c \
gostack.c \
jumps.c \
main.c \
sim.c \
Expand Down
37 changes: 32 additions & 5 deletions callgrind/bbcc.c
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,15 @@ static void handleUnderflow(BB* bb)
call_entry_up =
&(CLG_(current_call_stack).entry[CLG_(current_call_stack).sp -1]);
/* assume this call is lasting since last dump or
* for a signal handler since it's call */
if (CLG_(current_state).sig == 0)
* for a signal handler since it's call.
* A Go goroutine's call stack starts empty when the goroutine starts (or
* when callgrind first sees it), so its caller frames cannot be older than
* now: counting from the last dump would charge them for every goroutine
* that ran on this thread before. */
if (CLG_(go_runtime))
CLG_(copy_cost)( CLG_(sets).full, call_entry_up->enter_cost,
CLG_(current_state).cost );
else if (CLG_(current_state).sig == 0)
CLG_(copy_cost)( CLG_(sets).full, call_entry_up->enter_cost,
CLG_(get_current_thread)()->lastdump_cost );
else
Expand All @@ -572,6 +579,7 @@ void CLG_(setup_bbcc)(BB* bb)
Int passed = 0, csp;
Bool ret_without_call = False;
Int popcount_on_return = 1;
Int go_sync = GO_SYNC_NONE;

CLG_DEBUG(3,"+ setup_bbcc(BB %#lx)\n", bb_addr(bb));

Expand Down Expand Up @@ -650,6 +658,24 @@ void CLG_(setup_bbcc)(BB* bb)
isConditionalJump = False;
}

/* Go stack switches; a swapped call stack has its own previous BB */
if (UNLIKELY(CLG_(go_runtime))) {
go_sync = CLG_(go_sync)(bb, last_bb, sp);
if (go_sync == GO_SYNC_SWAPPED) {
last_bbcc = CLG_(current_state).bbcc;
last_bb = last_bbcc ? last_bbcc->bb : 0;
if (last_bb) {
passed = CLG_(current_state).jmps_passed;
jmpkind = last_bb->jmp[passed].jmpkind;
isConditionalJump = (passed < last_bb->cjmp_count);
}
else {
jmpkind = jk_None;
isConditionalJump = False;
}
}
}

/* Manipulate JmpKind if needed, only using BB specific info */

csp = CLG_(current_call_stack).sp;
Expand Down Expand Up @@ -692,7 +718,8 @@ void CLG_(setup_bbcc)(BB* bb)

/* Should this jump be converted to call or pop/call ? */
if (( jmpkind != jk_Return) &&
( jmpkind != jk_Call) && last_bb) {
( jmpkind != jk_Call) && last_bb &&
( go_sync != GO_SYNC_RESTART)) {

/* We simulate a JMP/Cont to be a CALL if
* - jump is in another ELF object or section kind
Expand Down Expand Up @@ -790,11 +817,11 @@ void CLG_(setup_bbcc)(BB* bb)
}
else {
CLG_ASSERT(popcount_on_return >0);
CLG_(unwind_call_stack)(sp, popcount_on_return);
CLG_(unwind_call_stack)(sp, popcount_on_return, bb->fn);
}
}
else {
Int unwind_count = CLG_(unwind_call_stack)(sp, 0);
Int unwind_count = CLG_(unwind_call_stack)(sp, 0, bb->fn);
if (unwind_count > 0) {
/* if unwinding was done, this actually is a return */
jmpkind = jk_Return;
Expand Down
16 changes: 15 additions & 1 deletion callgrind/callstack.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ void CLG_(push_call_stack)(BBCC* from, UInt jmp, BBCC* to, Addr sp, Bool skip)
current_entry->sp = sp;
current_entry->ret_addr = ret_addr;
current_entry->nonskipped = CLG_(current_state).nonskipped;
current_entry->go_tag = CLG_(go_cur);

CLG_(current_call_stack).sp++;

Expand Down Expand Up @@ -428,6 +429,8 @@ void CLG_(pop_call_stack)(void)

CLG_(current_call_stack).sp--;

if (CLG_(go_runtime)) CLG_(go_after_pop)();

#if CLG_ENABLE_DEBUG
CLG_DEBUGIF(1) {
if (CLG_(clo).verbose<4) {
Expand Down Expand Up @@ -461,9 +464,10 @@ void CLG_(pop_call_stack)(void)


/* Unwind enough CallStack items to sync with current stack pointer.
* <running> is the function of the BB about to run.
* Returns the number of stack frames unwinded.
*/
Int CLG_(unwind_call_stack)(Addr sp, Int minpops)
Int CLG_(unwind_call_stack)(Addr sp, Int minpops, fn_node* running)
{
Int csp;
Int unwind_count = 0;
Expand All @@ -482,6 +486,15 @@ Int CLG_(unwind_call_stack)(Addr sp, Int minpops)
if ((top_ce->sp < sp) ||
((top_ce->sp == sp) && minpops>0)) {

/* Frames below an altstack handler are on another stack */
if (CLG_(sp_in_altstack_signal_handler)(sp))
break;

/* Frames of another Go stack, see gostack.c */
if (CLG_(go_runtime) &&
CLG_(go_frame_blocks_unwind)(top_ce, running))
break;

minpops--;
unwind_count++;
CLG_(pop_call_stack)();
Expand Down Expand Up @@ -573,6 +586,7 @@ static void seed_call_stack_from_native(ThreadId tid)
call_entry* ce = &cs->entry[cs->sp];
ce->jcc = 0;
ce->nonskipped = 0;
ce->go_tag = CLG_(go_cur);

/* callgrind pops a frame when SP >= ce->sp, where ce->sp must be the
* frame's *entry* SP (the SP at which its caller made the call). The
Expand Down
2 changes: 2 additions & 0 deletions callgrind/fn.c
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ fn_node* new_fn_node(const HChar *fnname,
fn->is_malloc = False;
fn->is_realloc = False;
fn->is_free = False;
fn->go_switch = GO_SWITCH_NONE;

fn->group = 0;
fn->separate_callers = CLG_(clo).separate_callers;
Expand Down Expand Up @@ -750,6 +751,7 @@ fn_node* CLG_(get_fn_node)(BB* bb)
fn->is_malloc = (VG_(strcmp)(fn->name, "malloc")==0);
fn->is_realloc = (VG_(strcmp)(fn->name, "realloc")==0);
fn->is_free = (VG_(strcmp)(fn->name, "free")==0);
CLG_(go_classify_fn)(fn);

/* apply config options from function name patterns
* given on command line */
Expand Down
41 changes: 40 additions & 1 deletion callgrind/global.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
#include "pub_tool_options.h"
#include "pub_tool_tooliface.h"
#include "pub_tool_xarray.h"
#include "pub_tool_hashtable.h"
#include "pub_tool_clientstate.h"
#include "pub_tool_machine.h" // VG_(fnptr_to_fnentry)

Expand Down Expand Up @@ -439,6 +440,9 @@ struct _fn_node {
Bool is_realloc :1;
Bool is_free :1;

/* How this Go runtime function switches stacks, see gostack.c */
UInt go_switch :3;

Int group;
Int separate_callers;
Int separate_recursions;
Expand Down Expand Up @@ -494,6 +498,7 @@ struct _call_entry {
BBCC* nonskipped; /* see above */
Context* cxt; /* context before call */
Int fn_sp; /* function stack index before call */
UWord go_tag; /* Go stack the called frame runs on, 0 if unknown */
};


Expand Down Expand Up @@ -528,6 +533,9 @@ struct _exec_state {
BBCC* nonskipped;

Int call_stack_bottom; /* Index into fn_stack */

/* The signal handler runs on the thread's sigaltstack */
Bool on_altstack;
};

/* Global state structures */
Expand Down Expand Up @@ -627,6 +635,11 @@ struct _thread_info {
fn_array fn_active;
jcc_hash jccs;
bbcc_hash bbccs;

/* Go goroutine this thread runs, see gostack.c */
UWord go_owner;
UWord go_cur;
fn_node* go_restart;
};

/* Structs used for dumping */
Expand Down Expand Up @@ -799,7 +812,7 @@ call_entry* CLG_(get_call_entry)(Int n);

void CLG_(push_call_stack)(BBCC* from, UInt jmp, BBCC* to, Addr sp, Bool skip);
void CLG_(pop_call_stack)(void);
Int CLG_(unwind_call_stack)(Addr sp, Int);
Int CLG_(unwind_call_stack)(Addr sp, Int, fn_node* running);
void CLG_(reconstruct_call_stack_from_native)(ThreadId tid);

/* from context.c */
Expand Down Expand Up @@ -834,6 +847,32 @@ void CLG_(set_current_exec_stack)(exec_stack*);
void CLG_(pre_signal)(ThreadId tid, Int sigNum, Bool alt_stack);
void CLG_(post_signal)(ThreadId tid, Int sigNum);
void CLG_(run_post_signal_on_call_stack_bottom)(void);
Bool CLG_(sp_in_altstack_signal_handler)(Addr sp);

/* from gostack.c */
/* Values of fn_node.go_switch */
#define GO_SWITCH_NONE 0
#define GO_SWITCH_GOGO 1 /* runtime.gogo: resume another goroutine */
#define GO_SWITCH_MCALL 2 /* runtime.mcall: leave goroutine for g0 */
#define GO_SWITCH_NESTED 3 /* systemstack: run on g0, come back */
#define GO_SWITCH_MORESTACK 4 /* morestack: grow the stack on g0, restart */
extern Bool CLG_(go_runtime);
extern UWord CLG_(go_owner);
extern UWord CLG_(go_cur);
extern fn_node* CLG_(go_restart);
void CLG_(go_classify_fn)(fn_node* fn);
Bool CLG_(go_frame_blocks_unwind)(call_entry* ce, fn_node* running);
void CLG_(go_after_pop)(void);
/* Results of CLG_(go_sync) */
#define GO_SYNC_NONE 0
#define GO_SYNC_SWAPPED 1 /* another goroutine's call stack is current */
#define GO_SYNC_RESTART 2 /* jump back to the entry after morestack */
Int CLG_(go_sync)(BB* bb, BB* last_bb, Addr sp);
void CLG_(go_init_thread)(thread_info* t);
void CLG_(go_save_thread)(thread_info* t);
void CLG_(go_load_thread)(thread_info* t);
void CLG_(go_unwind_thread)(void);
void CLG_(go_unwind_all)(void);

/* from dump.c */
void CLG_(init_dumps)(void);
Expand Down
Loading
Loading