clojure 2026-06-11

i of course know about the excellent https://github.com/clojure-goes-fast/clj-java-decompiler, but do you think a godbolt-style clojure-to-bytecode viewer is possible? something that captures the output of a given chunk of compiled clojure code? could a library hook into the ASM library that the compiler uses?

Do you mean something that fundamentally maps arbitrary Clojure subforms to the part of the bytecode they compile into?

yeah I figured

i'll have to see how exactly clj-java-decompiler works to see if i can do something similar for each subform

or maybe modify the compiler itself, create a standalone program like flowstorm

feels like that would probably be your best bet, I don't think there's much in the way of inspecting the compiler's behavior in vanilla Clojure

👍 1

dang actually, i bet flowstorm would be a good place for this

I'd bet that clojure to java decompiler simply compiles into a tempdir and reads the bytecode off of it

or does flowstorm already do this... i'm gonna feel mighty silly if it does lol

all flowstorm does is add instrumentation to the emitted bytecode doesn't it?

I don't think you can by decompiling, because the class file only supports adding "line markers", and not columns, while in Clojure we have a bunch of expressions on the same line. But make something like ClojureStorm generate a source map is maybe possible?

👍 1

i suspect you could hack the line maker support by multiplying every number by 1000, using the lower 4 digits to store the column number lol

must use Cantor pairs, columns may exceed 1k

😄 1

I played with that already, but it uses an int if I don't remember wrong and it wasn't enough

of course you did lol

glad i'm asking before spending a bunch of time to retread this ground

But for your idea I guess you don't need to add anything to classes, you could generate a separate source map I guess

☝️ 1

Just emit the regular bytecode, and add a tail of trash insns to encode stuff, duh

does bytecode support something like a comment?

What I would try is on Compiler.java, to make sure all ... class XXX implements Expr { has fields for line and column (some of them have I think), then being sure you fill them on the parsing part, and on the emit map the blocks of generated bytecode to those line,cols

I assume this is also a problem the Java world has, src <-> bytecode source map-ish stuff?

Looking at the class spec, I guess you could have a custom attribute, and put that mapping there, but not sure of the benefit vs a separate source map file since only your tool will work with it

@borkdude classes have already a https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.12 attribute, so only a way of mapping lines to bytecode, which I guess works for languages like Java

which is probably sufficient?

Alex Miller (Clojure team) 2026-06-11T12:25:57.181639Z

you can kind of hack stuff with SMAP via the SourceDebugExtension (JSR 45)

although not sure how debuggers like IntelliJ that allows you to step into multiple expressions on the same line works

Alex Miller (Clojure team) 2026-06-11T12:27:07.307699Z

jruby does some crazy stuff where they insert virtual stack frames to delineate expression boundaries, not sure at what level they do that

> which is probably sufficient? when I looked at it it wasn't, since that line info is an int

I guess this SourceDebugExtension (JSR 45) Alex mentioned is probably the way to go, since it is a source map thing, but better because the info there could be leveraged from other tools also

👍 1
Alex Miller (Clojure team) 2026-06-11T12:30:41.297459Z

https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L4896-L4910 is a use of smap in clojure to attach debug info

👀 1

what is the target of that info Alex? What tool is making use of it?

Alex Miller (Clojure team) 2026-06-11T12:33:36.384539Z

existing java debuggers like in IntelliJ

👍 1
Alex Miller (Clojure team) 2026-06-11T12:33:56.227509Z

it's only line level though, I don't think you can get to columns

Perhaps some heuristics can be applied to get the relevant form

Alex Miller (Clojure team) 2026-06-11T12:34:41.548379Z

you could reformat the code to have one expression per line :)

😂 1

(I'm
  (sure
    (most people
      (will be 
        (happy with that))))))))))

closing parens can still be on the same line, so it's only half bad

Alex Miller (Clojure team) 2026-06-11T12:39:27.008489Z

you don't have to show it to people :)

Does anybody knows where this SMAP grammar is specified? Can't find it

do you mean, reformat, then recompile?

Alex Miller (Clojure team) 2026-06-11T12:40:11.032449Z

SMAP is defined in JSR 45 - you can download the spec from the jsr page

Alex Miller (Clojure team) 2026-06-11T12:40:39.315199Z

it is somewhat arcane

but yeah I don't see any column thing, I guess you could store there your own format instead of this SMAP

Alex Miller (Clojure team) 2026-06-11T12:43:46.788009Z

> do you mean, reformat, then recompile? yeah, reformat, remember a line mapping from old to new, compile, then use the smap info and the reverse line mapping to tie bytecode back to source

Alex Miller (Clojure team) 2026-06-11T12:44:43.723769Z

yeah SMAP is only valuable because it's embedded in the class and JVM tools understand it. if you're hooking the compiler itself you could be more direct

I guess they added this https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 because https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.12 wasn't enough for other languages but they created this SMAP DSL to put there that only support lines 🙄

😅 1
Alex Miller (Clojure team) 2026-06-11T12:49:04.489709Z

I think the big advantage is the layers idea

Alex Miller (Clojure team) 2026-06-11T12:49:38.911019Z

Which no one uses

😅 1
Alex Miller (Clojure team) 2026-06-11T12:50:24.441019Z

I mean you could probably treat an intermediate expr per line as a layer?

👍 1

I guess if your tool would be the only one understanding that then you could just store edn instead of this SMAP string instead

👍 2

Or because this SMAP is a string, I guess your line numbers there can be bigger? So you could encode both lines and columns into the line fields of a stratum for Clojure ?

nah, it says : >

InputStartLine: NUMBER
> NUMBER Non negative decimal integer.

well maybe that doesn't mean tools should parse that as ints, so no idea

or use one range of the number for the column?

you could use number = 2^line * 3^column

no those numbers grow too large ;)

darn, reality messes with math

depends on how big "number" is, here. if it's a java int, that gives you 2 billion lol

well if you have line number 1051 and column number 50, it will be a number of 300+ digits

😄 1

so, scratch that idea ;)

first 32 bits for line, other 32 bits for column, should work

i suggested just multiplying row by 1000 and using the lower 3 digits for column, but jp said he ran into problems trying something like that

oh sorry I missed that

what about two lines annotations per line+column pair

because it says just Integers there, and it doesn't define any JVM types, not sure if you can break existing tooling by overflowing there. Or by putting a non SMAP string.

When I get some time I'll experiment in ClojureStorm with mapping on the heap while loading and compiling stuff, instead of adding it to the class itself, because that should be enough for a godbolt like tool I guess

that would be........ next level, truly marvelous work

@borkdude haha, I vividly remember suggesting that 2^x * 3^y as a pairing function in a uni class back in the day Funnily enough, the Cantor pairs I mentioned above would kinda suck too: they'd fail as soon as one of the numbers hits about 2^(maxbits/2), in fact slightly earlier than smooshing two maxbits/2 ints as you suggested afterwards, so it'd be much better to assume a sane column length and then encode as (nlines * (maxcol + 1)) + ncols, which is more or less what Noah suggested

➕ 1

Using 3 bits as a "form index" - just identifying which form incrementally - would give 8 forms/line and leave 8K lines

➕ 1

ah yeah, counting nested forms per line would be good

I'm really interested in these techniques for my IDE since I have a lot of control over what gets sent to the compiler and how exceptions, warnings, etc are presented to the user.

Line numbers are already annoying since you can eval a function, add a new function above it, and then the line numbers are already wrong. I like the idea of autoincrementing numeric IDs that I can tie to vars/functions/forms more directly.

i think that's a separate issue/idea than what i was proposing above, tho it would be cool to have in an editor

Can you also stuff info into the filename to improve source mapping?

the discussion here only reminded me of some obfuscators back in the beginning of 2000s that took perfectly normal compiled java classes and "harmed it" just enough to break decompilers but not enough to break the execution in the jvm ... no idea if these things are still around

and when your eclipse or netbeans debugger stumbled on those then you better had your fingers crossed

Alex Miller (Clojure team) 2026-06-11T20:07:14.903189Z

I think they are still around - stuff like yGuard

oh my god, i can't wait to try this

feedback is welcome!

👍 1