<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=us-ascii" /><title>JEP TBD: Asynchronous Stack Trace VM API</title><style type="text/css" xml:space="preserve">

      A { text-decoration: none; }
      A:link { color: #437291; }
      A:visited { color: #666666; }
      A.anchor:link, A.anchor:visited { color: black; }
      A[href]:hover { color: #e76f00; }
      A IMG { border-width: 0px; }

      BODY {
        background: white;
        margin: 2em;
        margin-bottom: 150%;
        font-family: DejaVu Sans, Bitstream Vera Sans, Helvetica, Verdana, sans;
        font-size: 10pt; line-height: 1.4;
        width: 42em;
      }
      TT, CODE, PRE { font-family: DejaVu Sans Mono, Bitstream Vera Sans Mono,
                      monospace; }

      P { padding: 0pt; margin: 1ex 0em; }
      P:first-child, PRE:first-child { margin-top: 0pt; }
      H1 { font-size: 13pt; font-weight: bold;
           padding: 0pt; margin: 2ex .5ex 1ex 0pt; }
      H1:first-child, H2:first-child { margin-top: 0ex; }
      H2 { font-size: 11pt; font-weight: bold;
           padding: 0pt; margin: 2ex 0pt 1ex 0pt; }
      H3 { font-size: 10pt; font-weight: bold; font-style: italic;
           padding: 0pt; margin: 1ex 0pt 1ex 0pt; }
      H4 { font-size: 9pt; font-weight: bold;
           padding: 0pt; margin: 1ex 0pt 1ex 0pt; }
      P.subhead { font-size: smaller; margin-top: -1ex; }
      P.foot { font-size: x-small; margin-top: 6ex; }

      UL, OL { margin-top: 1ex; margin-bottom: 1ex; margin-right: 2em; }
      UL { padding-left: 2em; list-style-type: square; }
      UL UL { margin-top: 0ex; margin-bottom: 0ex; }
      LI { margin-top: 0pt; margin-bottom: 0pt; }
      LI > P:first-child { margin-top: 1ex; }

      BLOCKQUOTE { margin: 1.5ex 0ex; margin-left: 2em; }
      /*
      LI BLOCKQUOTE { margin-left: 0em; }
      LI { margin: .5ex 0em; }
      UL LI { list-style-type: square; }
      */

    </style><style type="text/css" xml:space="preserve">
      TABLE { border-collapse: collapse; padding: 0px; margin: 1em 0; }
      TR:first-child TH, TR:first-child TD { padding-top: 0; }
      TH, TD { padding: 0px; padding-top: .5ex; vertical-align: baseline; text-align: left; }
      TD + TD, TH + TH { padding-left: 1em; }
      TD:first-child, TH:first-child, TD.jep { text-align: right; }
      TABLE.head TD:first-child { font-style: italic; padding-left: 2em; }
      PRE { padding-left: 2em; margin: 1ex 0; font-size: inherit; }
      TABLE PRE { padding-left: 0; margin: 0; }
      TABLE.jeps TD:first-child + TD,
      TABLE.jeps TD:first-child + TD + TD { padding-left: .5em; }
      TABLE.jeps TD:first-child,
      TABLE.jeps TD:first-child + TD,
      TABLE.jeps TD:first-child + TD + TD { font-size: smaller; }
      TABLE.jeps TD.cl { font-size: smaller; padding-right: 0; text-align: right; }
      TABLE.jeps TD.cm { font-size: smaller; padding-left: .1em; padding-right: .1em; }
      TABLE.jeps TD.cr { font-size: smaller; padding-left: 0; }
      TABLE.jeps TD.z { padding-left: 0; padding-right: 0; }
      TABLE.head TD { padding-top: 0; }
      .withdrawn { text-decoration: line-through; }
    </style></head><body><h1>JEP TBD: Asynchronous Stack Trace VM API</h1><table class="head"></table><div class="markdown">

<h2 id="Summary">Summary</h2>
<p>Define an efficient and reliable API to collect stack traces asynchronously and include information on both Java and native stack frames.</p>
<h2 id="Goals">Goals</h2>
<ul>
<li>
<p>Provide a well-tested API for profilers to obtain information on Java and native frames.</p>
</li>
<li>
<p>Support asynchronous usage, e.g., calling from signal handlers.</p>
</li>
<li>
<p>Do not affect performance when the API is not in use.</p>
</li>
<li>
<p>Do not significantly increase memory requirements compared to the existing <code>AsyncGetCallTrace</code> API.</p>
</li>
</ul>
<h2 id="Non-Goals">Non-Goals</h2>
<ul>
<li>It is not a goal to recommend the new API for production use, since it can crash the VM. We will minimize the chances of that via extensive testing and fuzzing.</li>
</ul>
<h2 id="Motivation">Motivation</h2>
<p>The <code>AsyncGetCallTrace</code> API is used by almost all available profilers, both open-source and commercial, including, e.g.,  <a href="https://github.com/jvm-profiling-tools/async-profiler">async-profiler</a>. Yet it has two major disadvantages:</p>
<ul>
<li>It is an internal API, not exported in any header, and</li>
<li>It only returns information about Java frames, namely their method and bytecode indices.</li>
</ul>
<p>These issues make implementing profilers and related tooling more difficult. Some additional information can be extracted from the HotSpot VM via complex code, but other useful information is hidden and impossible to obtain:</p>
<ul>
<li>Whether a compiled Java frame is inlined (currently only obtainable for the topmost compiled frames),</li>
<li>The compilation level of a Java frame (i.e., compiled by C1 or C2), and</li>
<li>Information on C/C++ frames that are not at the top of the stack.</li>
</ul>
<p>Such data can be helpful when profiling and tuning a VM for a given application, and for profiling code that uses JNI heavily.</p>
<h2 id="Description">Description</h2>
<p>We propose a new <code>AsyncGetStackTrace</code> API, modeled on the <code>AsyncGetCallTrace</code> API:</p>
<pre><code>void AsyncGetStackTrace(CallTrace *trace, jint depth, void* ucontext,
                        uint32_t options);
</code></pre>
<p>This API can be called by profilers to obtain the stack trace for the current thread. Calling this API from a signal handler is safe, and the new implementation will be at least as stable as
<code>AsyncGetCallTrace</code> or the JFR stack walking code. The VM fills in information about the frames and the number of frames. The caller of the API should allocate the <code>CallTrace</code> array with sufficient memory for the requested stack depth.</p>
<p>Parameters:</p>
<ul>
<li><code>trace</code> — buffer for structured data to be filled in by the VM</li>
<li><code>depth</code> — maximum depth of the call stack trace</li>
<li><code>ucontext</code> — optional <code>ucontext_t</code> of the current thread when it was interrupted</li>
<li><code>options</code> — bit set for options</li>
</ul>
<p>Currently only the lowest bit of the <code>options</code> is considered: It enables (<code>1</code>) or disables (<code>0</code>) the inclusion of C/C++ frames. All other bits are considered to be <code>0</code>.</p>
<p>The <code>trace</code> struct</p>
<pre><code>typedef struct {
  jint num_frames;                // number of frames in this trace
  CallFrame *frames;              // frames
  void* frame_info;               // more information on frames
} CallTrace;
</code></pre>
<p>is filled in by the VM. Its <code>num_frames</code> field contains the actual number of frames in the <code>frames</code> array or an error code. The <code>frame_info</code> field in that structure can later be used to store more information, but is currently <code>NULL</code>.</p>
<p>The error codes are a subset of the error codes for <code>AsyncGetCallTrace</code>, with the addition of <code>THREAD_NOT_JAVA</code> related to calling this procedure for non-Java threads:</p>
<pre><code>enum Error {
  NO_JAVA_FRAME         =   0,
  NO_CLASS_LOAD         =  -1, 
  GC_ACTIVE             =  -2,    
  UNKNOWN_NOT_JAVA      =  -3,
  NOT_WALKABLE_NOT_JAVA =  -4,
  UNKNOWN_JAVA          =  -5,
  UNKNOWN_STATE         =  -7,
  THREAD_EXIT           =  -8,
  DEOPT                 =  -9,
  THREAD_NOT_JAVA       = -10
};
</code></pre>
<p>Every <code>CallFrame</code> is the element of a union, since the information stored for Java and non-Java frames differs:</p>
<pre><code>typedef union {
  FrameTypeId type;     // to distinguish between JavaFrame and NonJavaFrame 
  JavaFrame java_frame;
  NonJavaFrame non_java_frame;
} CallFrame;
</code></pre>
<p>There a several distinguishable frame types:</p>
<pre><code>enum FrameTypeId : uint8_t {
  FRAME_JAVA         = 1, // JIT compiled and interpreted
  FRAME_JAVA_INLINED = 2, // inlined JIT compiled
  FRAME_NATIVE       = 3, // native wrapper to call C methods from Java
  FRAME_STUB         = 4, // VM generated stubs
  FRAME_CPP          = 5  // C/C++/... frames
};
</code></pre>
<p>The first two types are for Java frames, for which we store the following information in a struct of type <code>JavaFrame</code>:</p>
<pre><code>typedef struct {     
  FrameTypeId type;       // frame type
  int8_t comp_level;      // compilation level, 0 is interpreted
  uint16_t bci;           // 0 < bci < 65536
  jmethodID method_id;
} JavaFrame;              // used for FRAME_JAVA, FRAME_JAVA_INLINED and FRAME_NATIVE
</code></pre>
<p>The <code>comp_level</code> indicates the compilation level of the method related to the frame, with higher numbers representing higher levels of compilation. It is modeled after the <a href="https://github.com/openjdk/jdk/blob/master/src/hotspot/share/compiler/compilerDefinitions.hpp#L54"><code>CompLevel</code> enum</a> in HotSpot but is dependent on the compiler infrastructure used. A value of zero indicates no compilation, i.e., bytecode interpretation.</p>
<p>Information on all other frames is stored in <code>NonJavaFrame</code> structs:</p>
<pre><code>typedef struct {
  FrameTypeId type;  // frame type
  void *pc;          // current program counter inside this frame
} NonJavaFrame;  
</code></pre>
<p>Although the API provides more information, the amount of space required per frame (e.g., 16 bytes on x86) is the same as for the existing <code>AsyncGetCallTrace</code> API.</p>
<p>We propose to place these declarations in a new static header file, <code>profile.h</code>. In the source tree it could be located in <code>src/java.base/share/native/include</code>; in a delivered JDK bundle it should be copied into the <code>include</code> directory. The header’s license must include the Classpath Exception so that it is consumable by third-party profiling tools.</p>
<p>A prototype implementation can be found <a href="https://github.com/openjdk/jdk-sandbox/tree/asgct2">here</a>, and a demo combining it with a modified async-profiler can be found <a href="https://github.com/parttimenerd/asgct2-demo">here</a>.</p>
<h2 id="Risks-and-Assumptions">Risks and Assumptions</h2>
<p>Returning information on C/C++ frames leaks implementation details, but this is also true for the Java frames of <code>AsyncGetCallTrace</code> since they leak details of the implementation of standard library files and include native wrapper frames.</p>
<h2 id="Testing">Testing</h2>
<p>We will add new stress tests to identify stability problems on all supported platforms. We plan to profile a set of example programs (e.g., the DaCapo and Renaissance benchmark suites) repeatedly with small profiling intervals (<= 0.1ms). We will also add substantial unit tests which should cover all options and test the basic usage of the API.</p>
</div></body></html>