ANN: no.disassemble, a runtime bytecode disassembler

321 views
Skip to first unread message

Gary Trakhman

unread,
Mar 30, 2013, 9:06:25 AM3/30/13
to clo...@googlegroups.com
I made a little proof of concept last night.  You could always look at bytecode that clojure emits in few ways, you can either hack the compiler yourself, or force AOT in your project and use javap.  The first approach is a bit intrusive, and the second has a somewhat annoying turnaround time, and won't work for any code that calls eval at runtime.

This takes another approach.  It uses java's Instrumentation stuff, to provide a hook into all classloading past the point at which it's loaded, where I store off all the bytes of defined classes into a big ConcurrentHashMap.  Then I use eclipse.jdt.core's disassembler functionality to print out the bytecode.

There's a nice lein-plugin that adds the dependency to your project and looks itself up in order to add the appropriate -javaagent flag to your jvm startup options.

Now you can do something like this:

user=> (require 'no.disassemble)
nil

user=> (in-ns 'no.disassemble)
#<Namespace no.disassemble>

no.disassemble=> (println (disassemble (fn [])))
// Compiled from NO_SOURCE_FILE (version 1.5 : 49.0, super bit)
public final class no.disassemble$eval1174$fn__1175 extends clojure.lang.AFunction {

  // Method descriptor #7 ()V
  // Stack: 0, Locals: 0
  public static {};
    0  return
      Line numbers:
        [pc: 0, line: 1]

  // Method descriptor #7 ()V
  // Stack: 1, Locals: 1
  public disassemble$eval1174$fn__1175();
    0  aload_0
    1  invokespecial clojure.lang.AFunction() [10]
    4  return
      Line numbers:
        [pc: 0, line: 1]

  // Method descriptor #12 ()Ljava/lang/Object;
  // Stack: 1, Locals: 1
  public java.lang.Object invoke();
    0  aconst_null
    1  areturn
      Line numbers:
        [pc: 0, line: 1]
      Local variable table:
        [pc: 0, pc: 1] local: this index: 0 type: java.lang.Object

}
nil
no.disassemble=> 

Potential use cases:

Writing fast code faster.
Proving to newbies that clojure is not interpreted, further evidence of eval's coolness.
Checking to make sure primitives are being used where you expect.

Gary Trakhman

unread,
Mar 30, 2013, 9:06:55 AM3/30/13
to clo...@googlegroups.com

Cedric Greevey

unread,
Mar 30, 2013, 11:50:52 AM3/30/13
to clo...@googlegroups.com
On Sat, Mar 30, 2013 at 9:06 AM, Gary Trakhman <gary.t...@gmail.com> wrote:
Potential use cases:

Writing fast code faster.
Proving to newbies that clojure is not interpreted, further evidence of eval's coolness.
Checking to make sure primitives are being used where you expect.

Scaring the bejesus out of Johnny 5.

:)

Michael Klishin

unread,
Mar 30, 2013, 12:28:31 PM3/30/13
to clo...@googlegroups.com

2013/3/30 Gary Trakhman <gary.t...@gmail.com>

Potential use cases:

Writing fast code faster.
Proving to newbies that clojure is not interpreted, further evidence of eval's coolness.
Checking to make sure primitives are being used where you expect.

Great idea. This kind of information is very valuable when doing optimization work and also helps understand
how the compiler works.

Hugo Duncan

unread,
Mar 30, 2013, 2:31:36 PM3/30/13
to clo...@googlegroups.com
Gary Trakhman <gary.t...@gmail.com> writes:

> I made a little proof of concept last night. You could always look at
> bytecode that clojure emits in few ways, you can either hack the compiler
> yourself, or force AOT in your project and use javap. The first approach
> is a bit intrusive, and the second has a somewhat annoying turnaround time,
> and won't work for any code that calls eval at runtime.
>
> This takes another approach. It uses java's Instrumentation stuff, to
> provide a hook into all classloading past the point at which it's loaded,
> where I store off all the bytes of defined classes into a big
> ConcurrentHashMap. Then I use eclipse.jdt.core's disassembler
> functionality to print out the bytecode.

The Ritz' disassembler uses jpda to access the bytecode. Not yet ported to
ritz-nrepl though.

Hugo

Gary Trakhman

unread,
Mar 31, 2013, 7:30:15 PM3/31/13
to clo...@googlegroups.com
Ritz's debugger approach looks much more thorough and doesn't require invasive extra dependencies as far as I can tell.  If I had known it existed, I probably wouldn't have bothered :-).  One major weakness of my approach is it doesn't take into account class-unloading through garbage collection of clojure's ethereal classloaders.  One way around that would be apply transformations to clojure's dynamic classloaders, and add finalizers to do cleanup, but it seems more trouble than it's worth.  The jpda approach looks like what I really want.


but it appears to be more fine-grained than just grabbing the class of an object and dumping the bytecode.  Before I go down the rabbit-hole, is there anything like that in ritz's code?

Aaron

unread,
Apr 4, 2013, 10:27:53 PM4/4/13
to clo...@googlegroups.com
Cool...  I wrote a similar little disassembler for ClojureCLR a few weeks ago:


It uses Mono.Reflection to interpret the byte codes.


On Saturday, March 30, 2013 9:06:25 AM UTC-4, Gary Trakhman wrote:
Reply all
Reply to author
Forward
0 new messages