/*
 * V8 DTrace ustack helper for annotating native stack traces with JavaScript
 * function names.  We start with a frame pointer (arg1) and emit a string
 * describing the current function.  We do this by chasing pointers to extract
 * the function's name (if any) and the filename and line number where the
 * function is defined.
 *
 * To use the helper, run node, then use the jstack() DTrace action to capture
 * a JavaScript stacktrace.  You may need to tune the dtrace_helper_actions_max
 * kernel variable to 128.
 */

#include <v8constants.h>

/*
 * V8 represents small integers (SMI) using the upper 31 bits of a 32-bit
 * value.  To extract the actual integer value, we must shift it over.
 */
#define	IS_SMI(value)			((value & V8_SmiTagMask) == V8_SmiTag)
#define SMI_VALUE(value)		((int32_t)(value) >> V8_SmiValueShift)

/*
 * Determine the encoding and representation of a V8 string.
 */
#define	V8_TYPE_STRING(type)	(((type) & V8_IsNotStringMask) == V8_StringTag)

#define	V8_STRENC_ASCII(type)	\
    (((type) & V8_StringEncodingMask) == V8_AsciiStringTag)

#define	V8_STRREP_SEQ(type)	\
    (((type) & V8_StringRepresentationMask) == V8_SeqStringTag)
#define	V8_STRREP_CONS(type)	\
    (((type) & V8_StringRepresentationMask) == V8_ConsStringTag)
#define	V8_STRREP_EXT(type)	\
    (((type) & V8_StringRepresentationMask) == V8_ExternalStringTag)

/*
 * String type predicates
 */
#define	ASCII_SEQSTR(value)	\
    (V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_SEQ(value))

#define	ASCII_CONSSTR(value)	\
    (V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_CONS(value))

#define	ASCII_EXTSTR(value)	\
    (V8_TYPE_STRING(value) && V8_STRENC_ASCII(value) && V8_STRREP_EXT(value))

/*
 * General helper macros
 */
#define	COPYIN_UINT8(addr)	(*(uint8_t *)copyin((addr), sizeof (uint8_t)))
#define	COPYIN_UINT32(addr)	(*(uint32_t *)copyin((addr), sizeof (uint32_t)))

#define	APPEND_CHR(c)			(this->buf[this->off++] = (c))

#define	APPEND_DGT(i, d)	\
	(((i) / (d)) ? APPEND_CHR('0' + ((i)/(d) % 10)) : 0)

#define	APPEND_NUM(i)		\
	APPEND_DGT((i), 10000);	\
	APPEND_DGT((i), 1000);	\
	APPEND_DGT((i), 100);	\
	APPEND_DGT((i), 10);	\
	APPEND_DGT((i), 1);

/*
 * The following macros are used to output ASCII SeqStrings, ConsStrings, and
 * Node.js ExternalStrings.  To represent each string, we use three fields:
 *
 *    "str":	a pointer to the string itself
 *
 *    "len":	the string length
 *
 *    "attrs":	the type identifier for the string, which indicates the
 *    		encoding and representation.  We're only interested in ASCII
 *    		encoded strings whose representation is one of:
 *
 *	SeqString	stored directly as a char array inside the object
 *
 *	ConsString	pointer to two strings that should be concatenated
 *
 * 	ExternalString	pointer to a char* outside the V8 heap
 */
 
/*
 * Load "len" and "attrs" for the given "str".
 */
#define	LOAD_STRFIELDS(str, len, attrs)					\
    len = SMI_VALUE(COPYIN_UINT32(str + V8_OFF_STR_LENGTH));		\
    this->map = COPYIN_UINT32(str + V8_OFF_HEAPOBJ_MAP);		\
    attrs = COPYIN_UINT8(this->map + V8_OFF_MAP_ATTRS);

/*
 * Print out the given SeqString, or do nothing if the string is not an ASCII
 * SeqString.
 */
#define	APPEND_SEQSTR(str, len, attrs) 					\
dtrace:helper:ustack:							\
/!this->done && len > 0 && ASCII_SEQSTR(attrs)/				\
{									\
	copyinto(str + V8_OFF_STR_CHARS, len, this->buf + this->off);	\
	this->off += len;						\
}

/*
 * Print out the given Node.js ExternalString, or do nothing if the string is
 * not an ASCII ExternalString.
 */
#define	APPEND_NODESTR(str, len, attrs)					\
dtrace:helper:ustack:								\
/!this->done && len > 0 && ASCII_EXTSTR(attrs)/				\
{										\
	this->resource = COPYIN_UINT32(str + V8_OFF_EXTSTR_RSRC);		\
	this->dataptr = COPYIN_UINT32(this->resource +  NODE_OFF_EXTSTR_DATA);	\
	copyinto(this->dataptr, len, this->buf + this->off);			\
	this->off += len;							\
}

/*
 * Recall that each ConsString points to two other strings which are
 * semantically concatenated.  Of course, these strings may themselves by
 * ConsStrings, but in D we can only expand this recursion to a finite level.
 * Thankfully, function and script names are generally not more than a few
 * levels deep, so we unroll the expansion up to three levels.  Even this is
 * pretty hairy: we use strings "s0", ..., "s13", (each with "str", "len", and
 * "attr" fields -- see above) to store the expanded strings.  We expand the
 * original string into s0 and s7, then s0 into s1 and s4, etc:
 *
 *
 *                   +----  str  ----+
 *                  /                 \                  <--  1st expansion
 *                 /                   \
 *              s0                       s7
 *            /    \                  /     \
 *           /      \                /       \           <--  2nd expansion
 *          /        \              /         \  
 *        s1          s4          s8           s11
 *       /  \        /  \        /  \          /  \      <--  3rd expansion
 *     s2    s3    s5    s6    s9    s10    s12    s13
 *
 * Of course, for a given string, any of these expansions may not be needed.
 * For example, we may expand str and find that s0 is already a SeqString,
 * while s7 requires further expansion.  So when we expand a ConsString, we
 * zero the length of the string itself, and then at the end we print out
 * all non-zero-length strings in order (including both internal nodes and
 * leafs in the tree above) to get the final output.
 */
#define	EXPAND_START()							\
dtrace:helper:ustack:							\
/!this->done/								\
{									\
	this->s0str = this->s1str = this->s2str = 0;			\
	this->s3str = this->s4str = this->s5str = 0;			\
	this->s6str = this->s7str = this->s8str = 0;			\
	this->s9str = this->s10str = this->s11str = 0;			\
	this->s12str = this->s13str = 0;				\
									\
	this->s0len = this->s1len = this->s2len = 0;			\
	this->s3len = this->s4len = this->s5len = 0;			\
	this->s6len = this->s7len = this->s8len = 0;			\
	this->s9len = this->s10len = this->s11len = 0;			\
	this->s12len = this->s13len = 0;				\
									\
	this->s0attrs = this->s1attrs = this->s2attrs = 0;		\
	this->s3attrs = this->s4attrs = this->s5attrs = 0;		\
	this->s6attrs = this->s7attrs = this->s8attrs = 0;		\
	this->s9attrs = this->s10attrs = this->s11attrs = 0;		\
	this->s12attrs = this->s13attrs = 0;				\
}

/*
 * Expand the ConsString "str" (represensted by "str", "len", and "attrs") into
 * strings "s1" (represented by "s1s", "s1l", and "s1a") and "s2" (represented
 * by "s2s", "s2l", "s2a").  If "str" is not a ConsString, do nothing.
 */
#define	EXPAND_STR(str, len, attrs, s1s, s1l, s1a, s2s, s2l, s2a)	\
dtrace:helper:ustack:							\
/!this->done && len > 0 && ASCII_CONSSTR(attrs)/			\
{									\
	len = 0;							\
									\
	s1s = COPYIN_UINT32(str + V8_OFF_CONSSTR_CAR);			\
	LOAD_STRFIELDS(s1s, s1l, s1a)					\
									\
	s2s = COPYIN_UINT32(str + V8_OFF_CONSSTR_CDR);			\
	LOAD_STRFIELDS(s2s, s2l, s2a)					\
}

/*
 * Print out a ConsString by expanding it up to three levels and printing out
 * the resulting SeqStrings.
 */
#define	APPEND_CONSSTR(str, len, attrs)					\
	EXPAND_START()							\
	EXPAND_STR(str, len, attrs,					\
	    this->s0str, this->s0len, this->s0attrs,			\
	    this->s7str, this->s7len, this->s7attrs)			\
	EXPAND_STR(this->s0str, this->s0len, this->s0attrs,		\
	    this->s1str, this->s1len, this->s1attrs,			\
	    this->s4str, this->s4len, this->s4attrs)			\
	EXPAND_STR(this->s1str, this->s1len, this->s1attrs,		\
	    this->s2str, this->s2len, this->s2attrs,			\
	    this->s3str, this->s3len, this->s3attrs)			\
	EXPAND_STR(this->s4str, this->s4len, this->s4attrs,		\
	    this->s5str, this->s5len, this->s5attrs,			\
	    this->s6str, this->s6len, this->s6attrs)			\
	EXPAND_STR(this->s7str, this->s7len, this->s7attrs,		\
	    this->s8str, this->s8len, this->s8attrs,			\
	    this->s11str, this->s11len, this->s11attrs)			\
	EXPAND_STR(this->s8str, this->s8len, this->s8attrs,		\
	    this->s9str, this->s9len, this->s9attrs,			\
	    this->s10str, this->s10len, this->s10attrs)			\
	EXPAND_STR(this->s11str, this->s11len, this->s11attrs,		\
	    this->s12str, this->s12len, this->s12attrs,			\
	    this->s13str, this->s13len, this->s13attrs)			\
									\
	APPEND_SEQSTR(str, len, attrs)					\
	APPEND_SEQSTR(this->s0str, this->s0len, this->s0attrs)		\
	APPEND_SEQSTR(this->s1str, this->s1len, this->s1attrs)		\
	APPEND_SEQSTR(this->s2str, this->s2len, this->s2attrs)		\
	APPEND_SEQSTR(this->s3str, this->s3len, this->s3attrs)		\
	APPEND_SEQSTR(this->s4str, this->s4len, this->s4attrs)		\
	APPEND_SEQSTR(this->s5str, this->s5len, this->s5attrs)		\
	APPEND_SEQSTR(this->s6str, this->s6len, this->s6attrs)		\
	APPEND_SEQSTR(this->s7str, this->s7len, this->s7attrs)		\
	APPEND_SEQSTR(this->s8str, this->s8len, this->s8attrs)		\
	APPEND_SEQSTR(this->s9str, this->s9len, this->s9attrs)		\
	APPEND_SEQSTR(this->s10str, this->s10len, this->s10attrs)	\
	APPEND_SEQSTR(this->s11str, this->s11len, this->s11attrs)	\
	APPEND_SEQSTR(this->s12str, this->s12len, this->s12attrs)	\
	APPEND_SEQSTR(this->s13str, this->s13len, this->s13attrs)	\


/*
 * Print out the given SeqString, ConsString, or ExternalString.
 * APPEND_CONSSTR implicitly handles SeqStrings as the degenerate case of an
 * expanded ConsString.
 */
#define	APPEND_V8STR(str, len, attrs)					\
	APPEND_CONSSTR(str, len, attrs)					\
	APPEND_NODESTR(str, len, attrs)

/*
 * In this first clause we initialize all variables.  We must explicitly clear
 * them because they may contain values left over from previous iterations.
 */
dtrace:helper:ustack:
{
	/* input */
	this->fp = arg1;

	/* output/flow control */
	this->buf = (char *)alloca(128);
	this->off = 0;
	this->done = 0;

	/* program state */
	this->ctx = 0;
	this->marker = 0;
	this->func = 0;	
	this->shared = 0;
	this->map = 0;
	this->funcnamestr = 0;
	this->funcnamelen = 0;
	this->funcnameattrs = 0;
	this->script = 0;
	this->scriptnamestr = 0;
	this->scriptnamelen = 0;
	this->scriptnameattrs = 0;
	this->position = 0;	
	this->line_ends = 0;	
	this->le_attrs = 0;

	/* binary search fields */
	this->bsearch_min = 0;
	this->bsearch_max = 0;
	this->ii = 0;
}

/*
 * Like V8, we first check if we've got an ArgumentsAdaptorFrame.  We've got
 * nothing to add for such frames, so we bail out quickly.
 */
dtrace:helper:ustack:
{
	this->ctx = COPYIN_UINT32(this->fp + V8_OFF_FP_CONTEXT);
}

dtrace:helper:ustack:
/IS_SMI(this->ctx) && SMI_VALUE(this->ctx) == V8_FT_ADAPTOR/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('a');
	APPEND_CHR('d');
	APPEND_CHR('a');
	APPEND_CHR('p');
	APPEND_CHR('t');
	APPEND_CHR('o');
	APPEND_CHR('r');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

/*
 * Check for other common frame types for which we also have nothing to add.
 */
dtrace:helper:ustack:
/!this->done/
{
	this->marker = COPYIN_UINT32(this->fp + V8_OFF_FP_MARKER);
}

dtrace:helper:ustack:
/!this->done && IS_SMI(this->marker) &&
 SMI_VALUE(this->marker) == V8_FT_ENTRY/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('e');
	APPEND_CHR('n');
	APPEND_CHR('t');
	APPEND_CHR('r');
	APPEND_CHR('y');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

dtrace:helper:ustack:
/!this->done && IS_SMI(this->marker) &&
 SMI_VALUE(this->marker) == V8_FT_ENTRYCONSTRUCT/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('e');
	APPEND_CHR('n');
	APPEND_CHR('t');
	APPEND_CHR('r');
	APPEND_CHR('y');
	APPEND_CHR('_');
	APPEND_CHR('c');
	APPEND_CHR('o');
	APPEND_CHR('n');
	APPEND_CHR('s');
	APPEND_CHR('t');
	APPEND_CHR('r');
	APPEND_CHR('u');
	APPEND_CHR('c');
	APPEND_CHR('t');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

dtrace:helper:ustack:
/!this->done && IS_SMI(this->marker) &&
 SMI_VALUE(this->marker) == V8_FT_EXIT/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('e');
	APPEND_CHR('x');
	APPEND_CHR('i');
	APPEND_CHR('t');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

dtrace:helper:ustack:
/!this->done && IS_SMI(this->marker) &&
 SMI_VALUE(this->marker) == V8_FT_INTERNAL/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('i');
	APPEND_CHR('n');
	APPEND_CHR('t');
	APPEND_CHR('e');
	APPEND_CHR('r');
	APPEND_CHR('n');
	APPEND_CHR('a');
	APPEND_CHR('l');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

dtrace:helper:ustack:
/!this->done && IS_SMI(this->marker) &&
 SMI_VALUE(this->marker) == V8_FT_CONSTRUCT/
{
	this->done = 1;
	APPEND_CHR('<');
	APPEND_CHR('<');
	APPEND_CHR(' ');
	APPEND_CHR('c');
	APPEND_CHR('o');
	APPEND_CHR('n');
	APPEND_CHR('s');
	APPEND_CHR('t');
	APPEND_CHR('r');
	APPEND_CHR('u');
	APPEND_CHR('c');
	APPEND_CHR('t');
	APPEND_CHR('o');
	APPEND_CHR('r');
	APPEND_CHR(' ');
	APPEND_CHR('>');
	APPEND_CHR('>');
	APPEND_CHR('\0');
	stringof(this->buf);
}

/*
 * At this point, we're either looking at a JavaScriptFrame or an
 * OptimizedFrame.  For now, we assume JavaScript and start by grabbing the
 * function name.
 */
dtrace:helper:ustack:
/!this->done/
{
	this->func = COPYIN_UINT32(this->fp + V8_OFF_FP_FUNC);
	this->shared = COPYIN_UINT32(this->func + V8_OFF_FUNC_SHARED);
	this->funcnamestr = COPYIN_UINT32(this->shared + V8_OFF_SHARED_NAME);
	LOAD_STRFIELDS(this->funcnamestr, this->funcnamelen,
	    this->funcnameattrs);
}

dtrace:helper:ustack:
/!this->done && this->funcnamelen == 0/
{
	/*
	 * This is an anonymous function, but if it was invoked as a method of
	 * some object then V8 will have computed an inferred name that we can
	 * include in the stack trace.
	 */
	APPEND_CHR('(');
	APPEND_CHR('a');
	APPEND_CHR('n');
	APPEND_CHR('o');
	APPEND_CHR('n');
	APPEND_CHR(')');
	APPEND_CHR(' ');
	APPEND_CHR('a');
	APPEND_CHR('s');
	APPEND_CHR(' ');

	this->funcnamestr = COPYIN_UINT32(this->shared + V8_OFF_SHARED_INFERRED);
	LOAD_STRFIELDS(this->funcnamestr, this->funcnamelen,
	    this->funcnameattrs);
}

dtrace:helper:ustack:
/!this->done && this->funcnamelen == 0/
{
	APPEND_CHR('(');
	APPEND_CHR('a');
	APPEND_CHR('n');
	APPEND_CHR('o');
	APPEND_CHR('n');
	APPEND_CHR(')');
}

APPEND_V8STR(this->funcnamestr, this->funcnamelen, this->funcnameattrs)

/*
 * Now look for the name of the script where the function was defined.
 */
dtrace:helper:ustack:
/!this->done/
{
	this->script = COPYIN_UINT32(this->shared + V8_OFF_SHARED_SCRIPT);
	this->scriptnamestr = COPYIN_UINT32(this->script +
	    V8_OFF_SCRIPT_NAME);
	LOAD_STRFIELDS(this->scriptnamestr, this->scriptnamelen,
	    this->scriptnameattrs);

	APPEND_CHR(' ');
	APPEND_CHR('a');
	APPEND_CHR('t');
	APPEND_CHR(' ');
}

APPEND_V8STR(this->scriptnamestr, this->scriptnamelen, this->scriptnameattrs)

/*
 * Now look for file position and line number information.
 */
dtrace:helper:ustack:
/!this->done/
{
	this->position = COPYIN_UINT32(this->shared + V8_OFF_SHARED_FUNTOK);
	this->line_ends = COPYIN_UINT32(this->script + V8_OFF_SCRIPT_LENDS);
	this->map = COPYIN_UINT32(this->line_ends + V8_OFF_HEAPOBJ_MAP);
	this->le_attrs = COPYIN_UINT8(this->map + V8_OFF_MAP_ATTRS);
}

dtrace:helper:ustack:
/!this->done && this->le_attrs != V8_IT_FIXEDARRAY/
{
	/*
	 * If the line number array was not a valid FixedArray, it's probably
	 * undefined because V8 has not had to compute it yet.  In this case we
	 * just show the raw position and call it a day.
	 */
	APPEND_CHR(' ');
	APPEND_CHR('p');
	APPEND_CHR('o');
	APPEND_CHR('s');
	APPEND_CHR('i');
	APPEND_CHR('t');
	APPEND_CHR('i');
	APPEND_CHR('o');
	APPEND_CHR('n');
	APPEND_CHR(' ');
	APPEND_NUM(this->position);
	APPEND_CHR('\0');
	this->done = 1;
	stringof(this->buf);
}

/*
 * At this point, we've got both a position in the script and an array
 * describing where each line of the file ends.  We can use this to compute the
 * line number by binary searching the array.  (This is also what V8 does when
 * computing stack traces.)
 */
dtrace:helper:ustack:
/!this->done/
{
	/* initialize binary search */
	this->bsearch_line = this->position < COPYIN_UINT32(
	    this->line_ends + V8_OFF_FA_DATA) ? 1 : 0;
	this->bsearch_min = 0;
	this->bsearch_max = this->bsearch_line != 0 ? 0 :
	    SMI_VALUE(COPYIN_UINT32(this->line_ends + V8_OFF_FA_SIZE)) - 1;
}

/*
 * Of course, we can't iterate the binary search indefinitely, so we hardcode 15
 * iterations.  That's enough to precisely identify the line number in files up
 * to 32768 lines of code.
 */
#define	BSEARCH_LOOP							\
dtrace:helper:ustack:							\
/!this->done && this->bsearch_max >= 1/					\
{									\
	this->ii = (this->bsearch_min + this->bsearch_max) >> 1;	\
}									\
									\
dtrace:helper:ustack:							\
/!this->done && this->bsearch_max >= 1 &&				\
 this->position > COPYIN_UINT32(this->line_ends + V8_OFF_FA_DATA +	\
    this->ii * sizeof (uint32_t))/					\
{									\
	this->bsearch_min = this->ii + 1;				\
}									\
									\
dtrace:helper:ustack:							\
/!this->done && this->bsearch_max >= 1 &&				\
 this->position <= COPYIN_UINT32(this->line_ends + V8_OFF_FA_DATA + 	\
    (this->ii - 1) * sizeof (uint32_t))/				\
{									\
	this->bsearch_max = this->ii - 1;				\
}

BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP
BSEARCH_LOOP

dtrace:helper:ustack:
/!this->done && !this->bsearch_line/
{
	this->bsearch_line = this->ii + 1;
}

dtrace:helper:ustack:
/!this->done/
{
	APPEND_CHR(' ');
	APPEND_CHR('l');
	APPEND_CHR('i');
	APPEND_CHR('n');
	APPEND_CHR('e');
	APPEND_CHR(' ');
	APPEND_NUM(this->bsearch_line);
	APPEND_CHR('\0');
	this->done = 1;
	stringof(this->buf);
}
