October 11th, 2024

A Lisp compiler to RISC-V written in Lisp

uLisp has launched an experimental Lisp compiler that translates functions into RISC-V machine code, optimizing performance for microcontrollers like the Raspberry Pi Pico 2 and supporting various Lisp constructs.

Read original articleLink Icon
ExcitementSkepticismCuriosity
A Lisp compiler to RISC-V written in Lisp

uLisp has introduced a new experimental Lisp compiler that translates Lisp functions into RISC-V machine code. This compiler is designed to run on RISC-V cores, such as those found in the Raspberry Pi Pico 2. The project builds on previous work that created a Lisp compiler for ARM. The compiler leverages the inherent structure of Lisp, eliminating the need for a separate tokenizer or parser. It is implemented in a subset of Common Lisp supported by uLisp and can also be executed on standard computers, although the generated RISC-V code cannot be run outside the uLisp environment. The compiler supports various Lisp constructs, including variable definitions, arithmetic operations, and conditionals, while also optimizing tail calls to enhance recursive function performance. Users can compile functions by calling a simple command, and the compiler generates machine code that can be executed directly. The documentation provides examples demonstrating the compiler's capabilities, including performance comparisons between interpreted and compiled versions of functions like Fibonacci and GCD. The project aims to facilitate the execution of Lisp on microcontrollers and enhance the efficiency of Lisp programs through machine code compilation.

- uLisp has launched a Lisp compiler for RISC-V that compiles functions into machine code.

- The compiler runs on RISC-V cores, such as the Raspberry Pi Pico 2, and can also be tested on standard computers.

- It supports various Lisp constructs and optimizes recursive function calls through tail-call optimization.

- Users can compile functions easily, with examples provided for performance comparisons.

- The project aims to improve Lisp execution on microcontrollers and enhance program efficiency.

AI: What people are saying
The comments on the uLisp article reflect a mix of excitement and skepticism about the new Lisp compiler for RISC-V.
  • Several users praise uLisp as a significant achievement, expressing joy in using Lisp on microcontrollers like Arduino.
  • There are concerns about the compiler's completeness, with some noting it lacks support for essential Lisp functions.
  • Discussion about the technical aspects of the compiler, including its ability to handle assembly instructions and compressed RISC-V instructions.
  • Users express curiosity about specific features, such as forward label resolution in constructs like "if".
  • Some comments reflect a broader interest in RISC-V and its appeal to hackers and developers.
Link Icon 14 comments
By @reikonomusha - 3 months
SBCL is a Common Lisp compiler written in Common Lisp that also can target RISC-V.
By @rwmj - 3 months
As I understand it, this compiles down to assembly instructions. What then assembles it to machine code? The reason I'm asking is I wanted to find out if the compiler/assembler supports compressed instructions (which are supported by the RP RISC-V core).

Edit: Yes it does support the compressed extension, although the page calls them "compact" instructions.

By @Pet_Ant - 3 months
There is something about RISC-V that really inspires lots of hackers and it’s not really technical thing AFAICT.
By @bloopernova - 3 months
I got a RP2350 "Feather"[1] from Adafruit[2]. Amazing little thing, with lots of stuff built-in. The lipoly charge port is super useful and Just Works, and the STEMMA QT connector means no soldering or breadboards for simple projects. My main half-baked idea for this is to control a CPU usage monitor[3], but I also want to make some better lights for my Lego SHIELD Helicarrier, and maybe add some movement too.

And now you're telling me I can use Lisp on this? It would be interesting to see how streamlined the development process is for each one of uLisp, CircuitPython, MicroPython, and Arduino/C.

[1] https://www.adafruit.com/product/6000

[2] https://www.adafruit.com/new <-- one of my favourite places to window-shop :)

[3] Yeah I'm rambling but my end goal is to drive an LED matrix that ends up looking like btop's CPU meter. Why not just show btop on a separate small screen? That is a very good question to which I have no answer.

By @spsesk117 - 3 months
ulisp is an incredible achievement and has brought me a lot of joy.

There is something very fun about writing lisp for an Arduino nano, and trying to golf your intentions into ~300 characters :)

By @kragen - 3 months
I don't think it's yet complete enough to compile itself; though I haven't looked at the assembler code, I'm pretty sure it requires bitwise operations the compiler can't compile yet. Also, the compiler itself requires things like null, symbolp, eq, and atom, which it also doesn't implement yet. Without those I'm not sure that it's fair to describe its input language as Lisp, though it does support car and cdr.

But it's still super cool. A really great thing about Lisp for purposes like this is that you don't get hung up on syntax and parsing, which is the most salient part of writing a compiler but not the most important.

By @dang - 3 months
Edit: It's a pity we missed http://www.ulisp.com/show?4W2I. It was posted (https://news.ycombinator.com/item?id=41190553) but didn't get attention. We'd have put it in the SCP for sure (https://news.ycombinator.com/item?id=26998308) if we had seen it.

---

Related. Others?

uLisp: Lisp for Microcontrollers - https://news.ycombinator.com/item?id=41681705 - Sept 2024 (1 comment)

An ARM Assembler Written in Lisp - https://news.ycombinator.com/item?id=36646277 - July 2023 (31 comments)

uLisp wireless message display with a Pi Pico W - https://news.ycombinator.com/item?id=32722475 - Sept 2022 (6 comments)

Visible Lisp Computer: embedded real-time display of Lisp workspace using uLisp - https://news.ycombinator.com/item?id=30612770 - March 2022 (7 comments)

uLisp on the Raspberry Pi Pico - https://news.ycombinator.com/item?id=29970231 - Jan 2022 (14 comments)

uLisp - https://news.ycombinator.com/item?id=27036317 - May 2021 (87 comments)

Lisp Badge: A single-board computer that you can program in uLisp - https://news.ycombinator.com/item?id=23729970 - July 2020 (25 comments)

A new RISC-V version of uLisp - https://news.ycombinator.com/item?id=22640980 - March 2020 (35 comments)

uLisp – ARM Assembler in Lisp - https://news.ycombinator.com/item?id=22117241 - Jan 2020 (49 comments)

Ray tracing with uLisp - https://news.ycombinator.com/item?id=20565559 - July 2019 (10 comments)

uLisp: Lisp for microcontrollers - https://news.ycombinator.com/item?id=18882335 - Jan 2019 (16 comments)

GPS mapping application in uLisp - https://news.ycombinator.com/item?id=18466566 - Nov 2018 (4 comments)

Tiny Lisp Computer 2 - https://news.ycombinator.com/item?id=16347048 - Feb 2018 (2 comments)

uLisp – Lisp for the Arduino - https://news.ycombinator.com/item?id=11777662 - May 2016 (33 comments)

By @pjmlp - 3 months
I see Lisp compilers and upvote. :)

Great work.

By @Joker_vD - 3 months
This is all very neat and all but could anyone please explain to me how this thing handles forward label resolution e.g. in the "if" construct? I think I know how it does that but I am very likely to be be wrong.
By @anthk - 3 months
I'd love a cheap $100 netbook with its speed close to the specs Intel Atom n270 one or similar.

Not everyone needs a 16GB machine to compile huge current C++ projects.

By @neuroelectron - 3 months
Here's a copy in case the website goes down.

; Lisp compiler to RISC-V Assembler - Version 1 - 11th October 2024 ; #| Language definition: Defining variables and functions: defun, setq Symbols: nil, t List functions: car, cdr Arithmetic functions: +, -, *, /, mod, 1+, 1- Arithmetic comparisons: =, <, <=, >, >=, /= Conditionals: if, and, or |# ; Compile a lisp function (defun compiler (name) (if (eq (car (eval name)) 'lambda) (eval (comp (cons 'defun (cons name (cdr (eval name)))))) (error "Not a Lisp function"))) ; The main compile routine - returns compiled code for x, prefixed by type :integer or :boolean ; Leaves result in a0 (defun comp (x &optional env tail) (cond ((null x) (type-code :boolean '(($li 'a0 0)))) ((eq x t) (type-code :boolean '(($li 'a0 1)))) ((symbolp x) (comp-symbol x env)) ((atom x) (type-code :integer (list (list '$li ''a0 x)))) (t (let ((fn (first x)) (args (rest x))) (case fn (defun (setq *label-num* 0) (setq env (mapcar #'(lambda (x y) (cons x y)) (second args) *locals*)) (comp-defun (first args) (second args) (cddr args) env)) (progn (comp-progn args env tail)) (if (comp-if (first args) (second args) (third args) env tail)) (setq (comp-setq args env tail)) (t (comp-funcall fn args env tail))))))) ; Utilities (defun push-regs (&rest regs) (let ((n -4)) (append (list (list '$addi ''sp ''sp (* -4 (length regs)))) (mapcar #'(lambda (reg) (list '$sw (list 'quote reg) (incf n 4) ''(sp))) regs)))) (defun pop-regs (&rest regs) (let ((n (* 4 (length regs)))) (append (mapcar #'(lambda (reg) (list '$lw (list 'quote reg) (decf n 4) ''(sp))) regs) (list (list '$addi ''sp ''sp (* 4 (length regs))))))) ; Like mapcon but not destructive (defun mappend (fn lst) (apply #'append (mapcar fn lst))) ; The type is prefixed onto the list of assembler code instructions (defun type-code (type code) (cons type code)) (defun code-type (type-code) (car type-code)) (defun code (type-code) (cdr type-code)) (defun checktype (fn type check) (unless (or (null type) (null check) (eq type check)) (error "Argument to '~a' must be ~a not ~a" fn check type))) ; Allocate registers - s0, s1, and a0 to a5 give compact instructions (defvar *params* '(a0 a1 a2 a3)) (defvar *locals* '(a4 a5 s0 s1 a6 a7 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11)) (defvar used-params nil) ; Generate a label (defvar label-num 0) (defun gen-label () (read-from-string (format nil "lab~d" (incf *label-num*)))) ; Subfunctions (defun comp-symbol (x env) (let ((reg (cdr (assoc x env)))) (type-code nil (list (list '$mv ''a0 (list 'quote reg)))))) (defun comp-setq (args env tail) (let ((value (comp (second args) env tail)) (reg (cdr (assoc (first args) env)))) (type-code (code-type value) (append (code value) (list (list '$mv (list 'quote reg) ''a0)))))) (defun comp-defun (name args body env) (setq used-params (subseq *locals* 0 (length args))) (append (list 'defcode name args) (list name) (apply #'append (mapcar #'(lambda (x y) (list (list '$mv (list 'quote x) (list 'quote y)))) used-params params)) (code (comp-progn body env t)))) (defun comp-progn (exps env tail) (let* ((len (1- (length exps))) (nlast (subseq exps 0 len)) (last1 (nth len exps)) (start (mappend #'(lambda (x) (append (code (comp x env t)))) nlast)) (end (comp last1 env tail))) (type-code (code-type end) (append start (code end))))) (defun comp-if (pred then else env tail) (let ((lab1 (gen-label)) (lab2 (gen-label)) (test (comp pred env nil))) (checktype 'if (car test) :boolean) (type-code :integer (append (code test) (list (list '$beqz ''a0 lab1)) (code (comp then env t)) (list (list '$j lab2) lab1) (code (comp else env tail)) (list lab2) (when tail '(($ret))))))) (defun $sgt (rd rs1 rs2) ($slt rd rs2 rs1)) (defun comp-funcall (f args env tail) (let ((test (assoc f '((< . $slt) (> . $sgt)))) (teste (assoc f '((= . $seqz) (/= . $snez)))) (testn (assoc f '((>= . $slt) (<= . $sgt)))) (logical (assoc f '((and . $and) (or . $or)))) (arith1 (assoc f '((1+ . 1) (1- . -1)))) (arith (assoc f '((+ . $add) (- . $sub) (* . $mul) (/ . $div) (mod . $rem))))) (cond ((or test teste testn) (type-code :boolean (append (comp-args f args 2 :integer env) (pop-regs 'a1) (cond (test (list (list (cdr test) ''a0 ''a1 ''a0))) (teste (list '($sub 'a0 'a1 'a0) (list (cdr teste) ''a0 ''a0))) (testn (list (list (cdr testn) ''a0 ''a1 ''a0) '($xori 'a0 'a0 1)))) (when tail '(($ret)))))) (logical (type-code :boolean (append (comp-args f args 2 :boolean env) (pop-regs 'a1) (list (list (cdr logical) ''a0 ''a0 ''a1)) (when tail '(($ret)))))) (arith1 (type-code :integer (append (comp-args f args 1 :integer env) (list (list '$addi ''a0 ''a0 (cdr arith1))) (when tail '(($ret)))))) (arith (type-code :integer (append (comp-args f args 2 :integer env) (pop-regs 'a1) (list (list (cdr arith) ''a0 ''a1 ''a0)) (when tail '(($ret)))))) ((member f '(car cdr)) (type-code :integer (append (comp-args f args 1 :integer env) (if (eq f 'cdr) (list '($lw 'a0 4 '(a0))) (list '($lw 'a0 0 '(a0)) '($lw 'a0 4 '(a0)))) (when tail '(($ret)))))) (t ; function call (type-code :integer (append (comp-args f args nil :integer env) (when (> (length args) 1) (append (list (list '$mv (list 'quote (nth (1- (length args)) params)) ''a0)) (apply #'pop-regs (subseq params 0 (1- (length args)))))) (cond (tail (list (list '$j f))) (t (append (apply #'push-regs (cons 'ra (reverse used-params))) (list (list '$jal f)) (apply 'pop-regs (append used-params (list 'ra)))))))))))) (defun comp-args (fn args n type env) (unless (or (null n) (= (length args) n)) (error "Incorrect number of arguments to '~a'" fn)) (let ((n (length args))) (mappend #'(lambda (y) (let ((c (comp y env nil))) (decf n) (checktype fn type (code-type c)) (if (zerop n) (code c) (append (code c) (push-regs 'a0))))) args)))

By @hinkley - 3 months
Gretchen! Stop trying to make Lisp happen. It’s not going to happen.