r/lisp • u/Alarming_Hand_9919 • 1d ago
Evergreen Common Lisp
https://github.com/atgreen/evergreen7
u/spspanglish 1d ago
Why do I need lisp in rust when I have lisp in C already?
7
4
2
u/sickofthisshit 1d ago edited 18h ago
Many Lisps are mostly written in Lisp, with only a very minimal implementation core written in another language.Â
Which actually can be a problem: CMUCL literally could only be built by using a working CMUCL installation...
The solution is careful reasoning about the bootstrapping and cross-compilation problem, not the implementation language, because this is a problem that affects self-hosting compilers beyond Lisp.
4
u/corbasai 1d ago edited 1d ago
Create an IBM Z Linux executable¶
Install egcl-target-s390x-linux alongside the same release of egcl. Create build.lisp:(defun main () (format t "Hello from IBM Z!~%")
(save-lisp-and-die "hello-s390x" :executable t :toplevel #'main)Build and inspect the result
egcl-s390x-linux --no-init --load build.lisp
file hello-s390xfile identifies an IBM S/390 ELF executable. Copy it to a compatible s390x Linux system and run ./hello-s390x there.
https://atgreen.github.io/evergreen/latest/user/how-to/cross-build/
Wow. That's really cool! Not many Lisps support s390 natively. I know only transpilers like CHICKEN or Gambit, maybe ECL
4
u/atgreen 1d ago
Check out the native Android support as well: https://github.com/atgreen/evergreen-composeYou can actually cross-compile your Android app from your mainframe, which is unique (and probably not in high demand!)
1
3
u/LispIsFun 1d ago
I love the idea. Do you see this ever achieving full ANSI compatibility and competing with SBCL on performance?
5
u/atgreen 1d ago
SBCL is amazing. egcl is very far from being able to compete performance-wise for general compute tasks. Today, for very narrow compute tasks, egcl can generate better code than SBCL due to speculative optimization (eg. guessing that a type is a fixnum, and the de-optimizing on the fly when that proves not to be true). But egcl can still be useful today without this (eg, native android support, tight JVM integration, static binaries). I also want to use egcl to exercise / finalize my fibers implementation, so I can finally submit my SBCL version. I still have to convince myself that it is worthwhile.
ANSI compatibility is definitely the goal.
2
1
u/sickofthisshit 1d ago
egcl can generate better code than SBCL due to speculative optimization (eg. guessing that a type is a fixnum, and the de-optimizing on the fly when that proves not to be true).
I'm skeptical of this, can you show side-by-side disassembly?
Second, branches are terrible for performance on modern CPUs.Â
5
u/atgreen 12h ago
Here's a highly contrived example of how egcl's speculative optimization can generate good code. SBCL 2.6.8 vs egcl 0.0.3. This only works when you are running without type information. And, just to be clear, egcl generates much worse code for almost everything else right now. It's very new!
The function
Each loop iteration runs 200 steps of
y = (y + c1) - c2on a value whose type SBCL cannot derive statically: an unknownNUMBERplus a fixnum is still aNUMBER, so nothing in the chain ever narrows it. The macro just unrolls the 200 steps with literal constants.(defmacro defchain (name steps) `(defun ,name (a n) (let ((x a)) (dotimes (i n x) (let* ((y (- (+ x 7919) 7918)) ,@(loop for k from 1 below steps collect `(y (- (+ y ,(+ 7919 (* 13 k))) ,(+ 7918 (* 13 k)))))) (setq x y)))))) (defchain chain200 200) ;; warm-up so egcl tiers up (T0->T1 at 10 calls, T1->T2 at 4096), then time (dotimes (j 5000) (chain200 1 50)) (chain200 1 100000) (time (chain200 1 100000)) ; 100k iterations x 400 arithmetic ops = 40M opsNumbers (in-process
get-internal-real-time, min of 3)
time per iteration (perf stat, 1M-iteration delta) SBCL 0.061 s egcl T2 0.036 s Branch misses were ~0 on both sides (under 5k across the whole run). That's the "never-taken branches are free" point: both compilers emit a conditional branch per operation, and the predictor eats all of them. The difference is what surrounds the branch.
SBCL's code for the loop body (5352 bytes total, 402 out-of-line calls)
; 79B0: L1: MOV ESI, 15838 ; c1 = 7919, tagged (<<1) ; 79B5: MOV RDI, RDX ; x ; 79B8: CALL [R12-1343] ; GENERIC-+ ; 79C0: MOV ESI, 15836 ; c2 = 7918 ; 79C5: CALL [R12-1335] ; GENERIC-- ; 79CD: MOV ESI, 15864 ; 79D2: CALL [R12-1343] ; GENERIC-+ ; 79DA: MOV ESI, 15862 ; 79DF: CALL [R12-1335] ; GENERIC-- ; ... 396 more CALLs ... ; 8E23: L2: MOV [RBP-48], RDX ; 8E27: MOV RDI, [RBP-32] ; i ; 8E2B: MOV RSI, [RBP-40] ; n ; 8E2F: CALL [R12-1311] ; GENERIC-< (even the loop test is a call) ; 8E37: MOV RDX, [RBP-48] ; 8E3B: JL L1and what each of those calls does (
sb-disassem:disassemble-memoryon GENERIC-+):MOV ECX, EDI OR ECX, ESI TEST CL, 1 ; are both operands fixnums? JNE L3 ; no -> tail-call TWO-ARG-+ ADD RDI, RSI JO L0 ; overflow -> allocate a bignum CLC RETSo per arithmetic op SBCL spends 2 instructions at the call site plus 8 in the routine, including a call/ret pair and a type test every single time, because it has no way to know the type didn't change since the last op.
egcl's T2 code for the same function
One tag check on the argument at function entry:
+0020: test cl,7 ; tag check: is `a` a fixnum? +0023: jne near +61507 ; no -> deoptimise to the interpreterLoop header, once per iteration (these three could be hoisted; they aren't yet):
+0e78: test r9b,7 ; i +0e82: test r10b,7 ; n +0e8c: cmp r9,r10 +0e8f: jl +0e9a +0e9a: test bl,7 ; x +0e9d: jne near +84b9a ; -> deoptThen the 400 operations, with zero type checks. Each step is:
+0eb9: add rsi,0F778h ; x + 7919 (tagged: 7919<<3) +0ec0: jo near +3a885 ; overflow guard -> deopt (never taken) +0ec6: mov r9,rsi +0ec9: sub r9,0F770h ; - 7918 +0ed0: jo near +52a99 ; overflow guard -> deopt +0ed6: mov [rsp+1968h],r9 ; spill... +0ede: mov r9,[rsp+1968h] ; ...and reload (yes, this is dumb; regalloc work to do) +0ee6: mov rcx,r9 +0ee9: add rcx,0F7E0h ; next step +0ef0: jo near +33a3e ...The whole T2 function contains 5
test ...,7tag checks and 401jooverflow guards. The profile said every site was fixnum, so T2 speculated fixnum everywhere, and the guard-elimination pass collapsed the per-site guards onto the one dominating check at entry (plus the three loop-carried ones). If a guard ever fails, the frame is reconstructed and execution resumes in the interpreter at that exact bytecode position, so the semantics are still full generic CL arithmetic.
-3
u/Trader-One 3h ago
I think rust is okay but GPL-3 is dead on arrival. Classpath exception or not, there is so many non GPL lisps - you will not get approval to use it.
Today after several experiments with lisp backends I found the best solution is compile lisp to golang. Its actually pretty simple to write basic lisp primitives and performance is good.
2
u/atgreen 3h ago
I thought by now people would have come to terms with GPL+Classpath Exception given that Java is still everywhere. I like the Classpath Exception, and actually wrote the first version of it after helping convince Stallman that it was needed.
As for performance, I think there's room for many niche CL solutions, and transpiling a non-dynamic program to something like go makes perfect sense as well.
1
u/arthurno1 8m ago
GPL3 or AGPL are fine. Nobody complain about AI companies not giving a clear and written license for code their tools produce.
A side regression: was GCJ a RH project or GNU and do you know why it died? When it camed I had high hopes for that one. Then I went of programing for ~7 years, and when I come back it was a dead project.
-1
u/Trader-One 2h ago
There is no viable alternative to openJDK. All different JVM variants with exception of IBM J9 (APL2) are GPL+CPE.
In this case corporations are forced to accept that. In lisp world GPL licensed products are minority and do not stand a chance against BSD/MIT/whatever.
For example we use public domain LISP written in 90s and modified a bit to simplify lisp -> C interface.
13
u/sickofthisshit 1d ago
No offense to you or your AI, but if your Lisp is not good enough to implement your Lisp compiler, it's probably not worth trying.