DTrace support: function calls, GC activity, line execution

Tested on macOS 10.11 dtrace, Ubuntu 16.04 SystemTap, and libbcc.

Largely based by an initial patch by Jesús Cea Avión, with some
influence from Dave Malcolm's SystemTap patch and Nikhil Benesch's
unification patch.

Things deliberately left out for simplicity:
- ustack helpers, I have no way of testing them at this point since
they are Solaris-specific
- PyFrameObject * in function__entry/function__return, this is
SystemTap-specific
- SPARC support
- dynamic tracing
- sys module dtrace facility introspection

All of those might be added later.
diff --git a/Python/ceval.c b/Python/ceval.c
index d3bd8b5..a396e81 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -15,6 +15,7 @@
 #include "dictobject.h"
 #include "frameobject.h"
 #include "opcode.h"
+#include "pydtrace.h"
 #include "setobject.h"
 #include "structmember.h"
 
@@ -50,6 +51,9 @@
                            PyThreadState *, PyFrameObject *);
 static int maybe_call_line_trace(Py_tracefunc, PyObject *,
                                  PyThreadState *, PyFrameObject *, int *, int *, int *);
+static void maybe_dtrace_line(PyFrameObject *, int *, int *, int *);
+static void dtrace_function_entry(PyFrameObject *);
+static void dtrace_function_return(PyFrameObject *);
 
 static PyObject * cmp_outcome(int, PyObject *, PyObject *);
 static PyObject * import_name(PyFrameObject *, PyObject *, PyObject *, PyObject *);
@@ -822,7 +826,7 @@
 #ifdef LLTRACE
 #define FAST_DISPATCH() \
     { \
-        if (!lltrace && !_Py_TracingPossible) { \
+        if (!lltrace && !_Py_TracingPossible && !PyDTrace_LINE_ENABLED()) { \
             f->f_lasti = INSTR_OFFSET(); \
             NEXTOPARG(); \
             goto *opcode_targets[opcode]; \
@@ -832,7 +836,7 @@
 #else
 #define FAST_DISPATCH() \
     { \
-        if (!_Py_TracingPossible) { \
+        if (!_Py_TracingPossible && !PyDTrace_LINE_ENABLED()) { \
             f->f_lasti = INSTR_OFFSET(); \
             NEXTOPARG(); \
             goto *opcode_targets[opcode]; \
@@ -1042,6 +1046,9 @@
         }
     }
 
+    if (PyDTrace_FUNCTION_ENTRY_ENABLED())
+        dtrace_function_entry(f);
+
     co = f->f_code;
     names = co->co_names;
     consts = co->co_consts;
@@ -1162,6 +1169,9 @@
     fast_next_opcode:
         f->f_lasti = INSTR_OFFSET();
 
+        if (PyDTrace_LINE_ENABLED())
+            maybe_dtrace_line(f, &instr_lb, &instr_ub, &instr_prev);
+
         /* line-by-line tracing support */
 
         if (_Py_TracingPossible &&
@@ -3620,6 +3630,8 @@
 
     /* pop frame */
 exit_eval_frame:
+    if (PyDTrace_FUNCTION_RETURN_ENABLED())
+        dtrace_function_return(f);
     Py_LeaveRecursiveCall();
     f->f_executing = 0;
     tstate->frame = f->f_back;
@@ -5415,3 +5427,65 @@
     tstate->co_extra_freefuncs[new_index] = free;
     return new_index;
 }
+
+static void
+dtrace_function_entry(PyFrameObject *f)
+{
+    char* filename;
+    char* funcname;
+    int lineno;
+
+    filename = PyUnicode_AsUTF8(f->f_code->co_filename);
+    funcname = PyUnicode_AsUTF8(f->f_code->co_name);
+    lineno = PyCode_Addr2Line(f->f_code, f->f_lasti);
+
+    PyDTrace_FUNCTION_ENTRY(filename, funcname, lineno);
+}
+
+static void
+dtrace_function_return(PyFrameObject *f)
+{
+    char* filename;
+    char* funcname;
+    int lineno;
+
+    filename = PyUnicode_AsUTF8(f->f_code->co_filename);
+    funcname = PyUnicode_AsUTF8(f->f_code->co_name);
+    lineno = PyCode_Addr2Line(f->f_code, f->f_lasti);
+
+    PyDTrace_FUNCTION_RETURN(filename, funcname, lineno);
+}
+
+/* DTrace equivalent of maybe_call_line_trace. */
+static void
+maybe_dtrace_line(PyFrameObject *frame,
+                  int *instr_lb, int *instr_ub, int *instr_prev)
+{
+    int line = frame->f_lineno;
+    char *co_filename, *co_name;
+
+    /* If the last instruction executed isn't in the current
+       instruction window, reset the window.
+    */
+    if (frame->f_lasti < *instr_lb || frame->f_lasti >= *instr_ub) {
+        PyAddrPair bounds;
+        line = _PyCode_CheckLineNumber(frame->f_code, frame->f_lasti,
+                                       &bounds);
+        *instr_lb = bounds.ap_lower;
+        *instr_ub = bounds.ap_upper;
+    }
+    /* If the last instruction falls at the start of a line or if
+       it represents a jump backwards, update the frame's line
+       number and call the trace function. */
+    if (frame->f_lasti == *instr_lb || frame->f_lasti < *instr_prev) {
+        frame->f_lineno = line;
+        co_filename = PyUnicode_AsUTF8(frame->f_code->co_filename);
+        if (!co_filename)
+            co_filename = "?";
+        co_name = PyUnicode_AsUTF8(frame->f_code->co_name);
+        if (!co_name)
+            co_name = "?";
+        PyDTrace_LINE(co_filename, co_name, line);
+    }
+    *instr_prev = frame->f_lasti;
+}