Skip to content

Commit

Permalink
Wrap fn bodies in expressions and trace fn unwinds
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmonettas committed Feb 9, 2024
1 parent 31febed commit 682b31e
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 81 deletions.
6 changes: 4 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,16 @@ The important bits here are :

## Hooking into ClojureStorm

Instructions here apply to ClojureStorm >= `1.11.1-12` and `1.12.0-alpha4_6`
Instructions here apply to ClojureStorm >= `1.11.1-19` and `1.12.0-alpha4_14`

```clojure
(clojure.storm.Tracer/setTraceFnsCallbacks
{:trace-fn-call-fn (fn [_ fn-ns fn-name fn-args-vec form-id]
(prn "fn-call " fn-ns fn-name fn-args-vec form-id))
(prn "fn-call " fn-ns fn-name (into [] fn-args-vec) form-id))
:trace-fn-return-fn (fn [_ ret coord form-id]
(prn "fn-return" ret coord form-id))
:trace-fn-unwind-fn (fn [_ throwable coord form-id]
(prn "fn-unwind" throwable coord form-id))
:trace-expr-fn (fn [_ val coord form-id]
(prn "expr" val coord form-id))
:trace-bind-fn (fn [_ coord sym-name bind-val]
Expand Down
10 changes: 1 addition & 9 deletions src/clj/clojure/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7058,15 +7058,7 @@ fails, attempts to require sym's namespace and retries."
{:added "1.1"
:static true}
[f]
(let [f (binding-conveyor-fn
(fn [& args]
(try
(apply f args)
(catch Throwable t
(clojure.storm.Tracer/handleThreadException
(Thread/currentThread)
t)
(throw t)))))
(let [f (binding-conveyor-fn f)
fut (.submit clojure.lang.Agent/soloExecutor ^Callable f)]
(reify
clojure.lang.IDeref
Expand Down
3 changes: 1 addition & 2 deletions src/clj/clojure/main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -450,8 +450,7 @@ by default when a new command-line REPL is started."} repl-requires
(print value)
(catch Throwable e
(throw (ex-info nil {:clojure.error/phase :print-eval-result} e)))))))
(catch Throwable e
(clojure.storm.Tracer/handleThreadException (Thread/currentThread) e)
(catch Throwable e
(caught e)
(set! *e e))))]
(with-bindings
Expand Down
3 changes: 1 addition & 2 deletions src/jvm/clojure/lang/Agent.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@ static void doRun(Action action){
}
catch(Throwable e)
{
Tracer.handleThreadException(Thread.currentThread(), e);
error = e;
error = e;
}

if(error == null)
Expand Down
24 changes: 13 additions & 11 deletions src/jvm/clojure/lang/Compiler.java
Original file line number Diff line number Diff line change
Expand Up @@ -5636,11 +5636,12 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){
EXCEPTION_TYPES,
cv);

Label prologueTryStartLabel = null;
if(!this.skipFnCallTrace)
{
String fnName = fn.name();
if(mungedMethodTraceName != null) fnName = mungedMethodTraceName;
Emitter.emitFnCallTrace(gen, fn, fnName, argtypes, argLocals);
prologueTryStartLabel = Emitter.emitFnPrologue(gen, fn, fnName, argtypes, argLocals);
}


Expand All @@ -5665,7 +5666,7 @@ public void doEmitStatic(ObjExpr fn, ClassVisitor cv){
}

if(!this.skipFnCallTrace)
Emitter.emitFnReturnTrace(gen, fn.name(), fn.getCoord(), returnType);
Emitter.emitFnEpilogue(gen, fn.name(), fn.getCoord(), returnType, prologueTryStartLabel);

gen.returnValue();
//gen.visitMaxs(1, 1);
Expand Down Expand Up @@ -5753,12 +5754,12 @@ public void doEmitPrim(ObjExpr fn, ClassVisitor cv){
EXCEPTION_TYPES,
cv);


Label prologueTryStartLabel = null;
if(!this.skipFnCallTrace)
{
String fnName = fn.name();
if(mungedMethodTraceName != null) fnName = mungedMethodTraceName;
Emitter.emitFnCallTrace(gen, fn, fnName, argtypes, argLocals);
prologueTryStartLabel = Emitter.emitFnPrologue(gen, fn, fnName, argtypes, argLocals);
}

gen.visitCode();
Expand All @@ -5784,7 +5785,7 @@ public void doEmitPrim(ObjExpr fn, ClassVisitor cv){
}

if(!this.skipFnCallTrace)
Emitter.emitFnReturnTrace(gen, fn.name(), fn.getCoord(), returnType);
Emitter.emitFnEpilogue(gen, fn.name(), fn.getCoord(), returnType, prologueTryStartLabel);

gen.returnValue();
//gen.visitMaxs(1, 1);
Expand Down Expand Up @@ -5829,12 +5830,12 @@ public void doEmit(ObjExpr fn, ClassVisitor cv){
//todo don't hardwire this
EXCEPTION_TYPES,
cv);

Label prologueTryStartLabel = null;
if(!this.skipFnCallTrace)
{
String fnName = fn.name();
if(mungedMethodTraceName != null) fnName = mungedMethodTraceName;
Emitter.emitFnCallTrace(gen, fn, fnName, argtypes, argLocals);
prologueTryStartLabel = Emitter.emitFnPrologue(gen, fn, fnName, argtypes, argLocals);
}

gen.visitCode();
Expand All @@ -5861,7 +5862,7 @@ public void doEmit(ObjExpr fn, ClassVisitor cv){
}

if(!this.skipFnCallTrace)
Emitter.emitFnReturnTrace(gen, fn.name(), fn.getCoord(), Type.getType(Object.class));
Emitter.emitFnEpilogue(gen, fn.name(), fn.getCoord(), Type.getType(Object.class), prologueTryStartLabel);

gen.returnValue();
//gen.visitMaxs(1, 1);
Expand Down Expand Up @@ -7357,7 +7358,7 @@ public static Object eval(Object form) {
if (ce instanceof CompilerException)
{
Throwable cause = ce.getCause();
if(cause != null && (cause.getMessage().equals("Method code too large!")))
if(cause != null && cause.getMessage() !=null && cause.getMessage().equals("Method code too large!"))
{
System.out.println("Method too large, re-evaluating without storm instrumentation.");
Var.pushThreadBindings(RT.map(Emitter.INSTRUMENTATION_ENABLE, false));
Expand Down Expand Up @@ -8943,8 +8944,9 @@ public void emit(ObjExpr obj, ClassVisitor cv){

String fqMethodName = Compiler.munge(Compiler.currentNS().name.name) + "$" + getMethodName();

Label prologueTryStartLabel = null;
if(!skipFnCallTrace)
Emitter.emitFnCallTrace(gen, obj, fqMethodName, extypes, argLocals);
prologueTryStartLabel = Emitter.emitFnPrologue(gen, obj, fqMethodName, extypes, argLocals);

Label loopLabel = gen.mark();

Expand All @@ -8968,7 +8970,7 @@ public void emit(ObjExpr obj, ClassVisitor cv){
}

if(!skipFnCallTrace)
Emitter.emitFnReturnTrace(gen, fqMethodName, coord, retType);
Emitter.emitFnEpilogue(gen, fqMethodName, coord, retType, prologueTryStartLabel);


gen.returnValue();
Expand Down
92 changes: 71 additions & 21 deletions src/jvm/clojure/storm/Emitter.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import clojure.asm.Opcodes;
import clojure.asm.Type;
import clojure.asm.Label;
import clojure.asm.commons.GeneratorAdapter;
import clojure.asm.commons.Method;
import clojure.lang.AFn;
Expand Down Expand Up @@ -193,35 +194,64 @@ private static void dupAndBox(GeneratorAdapter gen, Type t) {
gen.dup();
}
}

public static void emitFnCallTrace(GeneratorAdapter gen, ObjExpr objx, String mungedFnName, Type[] argtypes, IPersistentVector arglocals) {

/**
* Emit the bytecode for a function prologue that will trace the function call and also add and return a label
* that can be used by the function epilogue emition code to wrap the fn body on a try/catch
*/
public static Label emitFnPrologue(GeneratorAdapter gen, ObjExpr objx, String mungedFnName, Type[] argtypes, IPersistentVector arglocals) {

boolean skipFn = skipInstrumentation(mungedFnName);

if (fnCallInstrumentationEnable && !skipFn) {


Label startTry = gen.newLabel();
gen.mark(startTry);

Integer formId = (Integer) Compiler.FORM_ID.deref();
if (formId == null) formId = 0;
if (formId == null) formId = 0;
Symbol name = Symbol.create(Compiler.demunge(mungedFnName));
String fnName = name.getName();
String fnNs = name.getNamespace();

gen.loadArgArray();
gen.invokeStatic(Type.getType(clojure.lang.PersistentVector.class), Method.getMethod("clojure.lang.PersistentVector create(Object[])"));
// push all the args array on the stack and then
gen.loadArgArray();

gen.push(fnNs);
gen.push(fnName);
gen.push((int)formId);
gen.invokeStatic(TRACER_CLASS_TYPE, Method.getMethod("void traceFnCall(clojure.lang.IPersistentVector, String, String, int)"));
gen.push(fnNs); // push the function namespace
gen.push(fnName); // push the function name
gen.push((int)formId); // push the form-id
gen.invokeStatic(TRACER_CLASS_TYPE, Method.getMethod("void traceFnCall(Object[], String, String, int)")); // trace the function call

emitBindTraces(gen, objx, arglocals, PersistentVector.EMPTY);
}
// emit binds for all function's arguments
emitBindTraces(gen, objx, arglocals, PersistentVector.EMPTY);

return startTry;
}

return null;
}

public static void emitFnReturnTrace(GeneratorAdapter gen, String mungedFnName, IPersistentVector coord, Type retType) {
boolean skipFn = skipInstrumentation(mungedFnName);
if (fnReturnInstrumentationEnable && !skipFn) {

/**
* Emit the bytecode for a function epilogue that will trace the function return.
* If a tryStartLabel is provided the epilogue will make sure that the entire function body is wrapped in a
* try/catch block so if any exception arises during the functions body execution it will trace a stack unwind.
*/
public static void emitFnEpilogue(GeneratorAdapter gen, String mungedFnName, IPersistentVector coord, Type retType, Label tryStartLabel) {

boolean skipFn = skipInstrumentation(mungedFnName);

if (fnReturnInstrumentationEnable && !skipFn) {

Label tryEndLabel = gen.newLabel();
Label retLabel = gen.newLabel();
Label catchHandlerLabel = gen.newLabel();

Integer formId = (Integer) Compiler.FORM_ID.deref();
if (formId == null) formId = 0;
if (formId == null) formId = 0;

// trace the return
// push a copy of the return value
if(Type.VOID_TYPE.equals(retType))
{
gen.visitInsn(Opcodes.ACONST_NULL);
Expand All @@ -230,11 +260,31 @@ public static void emitFnReturnTrace(GeneratorAdapter gen, String mungedFnName,
// duplicate the value for tracing, so we don't consume it
dupAndBox(gen, retType);
}

emitCoord(gen, coord);

gen.push((int)formId);
gen.invokeStatic(TRACER_CLASS_TYPE, Method.getMethod("void traceFnReturn(Object, String, int)"));

emitCoord(gen, coord); // push the coord
gen.push((int) formId); // push the formId
gen.invokeStatic(TRACER_CLASS_TYPE, Method.getMethod("void traceFnReturn(Object, String, int)")); // trace the return

// if we have a tryStartLabel it means that the fnCall was also instrumented, so we can
// wrap a try/catch over the fn body
if(tryStartLabel != null) {
gen.goTo(retLabel); // jump to the return, skipping exception handling code

gen.mark(tryEndLabel); // closing try block label

gen.mark(catchHandlerLabel); // if anything is thrown in the fn code, handle it here
// Throwable handler code, if we got here we have the Throwable obj on the stack
gen.dup(); // copy the throwable ref so we can trace it
emitCoord(gen, coord); // push the coord
gen.push((int)formId); // push the formId
gen.invokeStatic(TRACER_CLASS_TYPE, Method.getMethod("void traceFnUnwind(Object, String, int)")); // trace the return
gen.throwException(); // re-throw the throwable we have on the stack

// setup our throwable catch handler
gen.visitTryCatchBlock(tryStartLabel, tryEndLabel, catchHandlerLabel, "java/lang/Throwable");

gen.mark(retLabel);
}
}
}

Expand Down
49 changes: 19 additions & 30 deletions src/jvm/clojure/storm/Tracer.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,23 @@ public class Tracer {

private static IFn traceFnCallFn = null;
private static IFn traceFnReturnFn = null;
private static IFn traceFnUnwindFn = null;
private static IFn traceExprFn = null;
private static IFn traceBindFn = null;
private static IFn handleExceptionFn = null;

private static Keyword TRACE_FN_CALL_FN_KEY = Keyword.intern(null, "trace-fn-call-fn-key");
// TODO: this are depracated, remove when it is safe
private static Keyword TRACE_FN_CALL_FN_KEY = Keyword.intern(null, "trace-fn-call-fn-key");
private static Keyword TRACE_FN_RETURN_FN_KEY = Keyword.intern(null, "trace-fn-return-fn-key");
private static Keyword TRACE_EXPR_FN_KEY = Keyword.intern(null, "trace-expr-fn-key");
private static Keyword TRACE_BIND_FN_KEY = Keyword.intern(null, "trace-bind-fn-key");
private static Keyword HANDLE_EXCEPTION_FN_KEY = Keyword.intern(null, "handle-exception-fn-key");


private static Keyword TRACE_FN_CALL_FN = Keyword.intern(null, "trace-fn-call-fn");
private static Keyword TRACE_FN_RETURN_FN = Keyword.intern(null, "trace-fn-return-fn");
private static Keyword TRACE_FN_UNWIND_FN = Keyword.intern(null, "trace-fn-unwind-fn");
private static Keyword TRACE_EXPR_FN = Keyword.intern(null, "trace-expr-fn");
private static Keyword TRACE_BIND_FN = Keyword.intern(null, "trace-bind-fn");
private static Keyword HANDLE_EXCEPTION_FN = Keyword.intern(null, "handle-exception-fn");

static
{
// For all new threads created
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
handleThreadException(t, e);
});

}

public static void handleThreadException(Thread thread, Throwable ex) {
if (handleExceptionFn != null)
handleExceptionFn.invoke(thread, ex);
}

static public void traceFnCall(IPersistentVector fnArgs, String fnNs, String fnName, int formId) {

static public void traceFnCall(Object[] fnArgs, String fnNs, String fnName, int formId) {
if (traceFnCallFn != null)
traceFnCallFn.invoke(null, fnNs, fnName, fnArgs, formId);
}
Expand All @@ -62,6 +48,13 @@ static public void traceFnReturn(Object retVal, String coord, int formId) {
}
}

static public void traceFnUnwind(Object throwable, String coord, int formId) {
if (traceFnUnwindFn != null)
{
traceFnUnwindFn.invoke(null, throwable, coord, formId);
}
}

public static void traceBind(Object val, String coord, String symName) {
if (traceBindFn != null)
{
Expand Down Expand Up @@ -93,10 +86,7 @@ public static void setTraceFnsCallbacks(IPersistentMap callbacks) {

if (callbacks.valAt(TRACE_BIND_FN_KEY) != null)
traceBindFn = (IFn) callbacks.valAt(TRACE_BIND_FN_KEY);

if (callbacks.valAt(HANDLE_EXCEPTION_FN_KEY) != null)
handleExceptionFn = (IFn) callbacks.valAt(HANDLE_EXCEPTION_FN_KEY);


// New keys

if (callbacks.valAt(TRACE_FN_CALL_FN) != null)
Expand All @@ -105,15 +95,14 @@ public static void setTraceFnsCallbacks(IPersistentMap callbacks) {
if (callbacks.valAt(TRACE_FN_RETURN_FN) != null)
traceFnReturnFn = (IFn) callbacks.valAt(TRACE_FN_RETURN_FN);

if (callbacks.valAt(TRACE_FN_UNWIND_FN) != null)
traceFnUnwindFn = (IFn) callbacks.valAt(TRACE_FN_UNWIND_FN);

if (callbacks.valAt(TRACE_EXPR_FN) != null)
traceExprFn = (IFn) callbacks.valAt(TRACE_EXPR_FN);

if (callbacks.valAt(TRACE_BIND_FN) != null)
traceBindFn = (IFn) callbacks.valAt(TRACE_BIND_FN);

if (callbacks.valAt(HANDLE_EXCEPTION_FN) != null)
handleExceptionFn = (IFn) callbacks.valAt(HANDLE_EXCEPTION_FN);


}

Expand Down
Loading

0 comments on commit 682b31e

Please sign in to comment.