7 ms·
A Lisp compiler to RISC-V written in Lisp
- reikonomusha 2y agoSBCL is a Common Lisp compiler written in Common Lisp that also can target RISC-V.
- neuroelectron 2y agoHere'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)))
- johnsondavies 2y agoSorry, I'd posted the link to the code in the wrong format - corrected now. Would you like to delete that copy?
- neuroelectron 2y agoIt is easier to read but that defeats the point of this comment.
- chrisjj 2y agoI think the idea was to delete and replace.
- deleted 2y ago[deleted]
- bee_rider 2y agoWait it fits in a comment? What sort of magic is this?
- deleted 2y ago[deleted]
- snvzz 2y agoThe beautiful simplicity of Lisp and RISC-V. They similarly share further magic in their inevitability.
- rwmj 2y agoAs 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.
- steeleduncan 2y agoIn the article there is a link to an earlier post with a RISC-V assembler (I think written by the same author), which generates the actual machine code
- johnsondavies 2y agoThanks - corrected "compact" to "compressed".
- Pet_Ant 2y agoThere is something about RISC-V that really inspires lots of hackers and it’s not really technical thing AFAICT.
- bee_rider 2y agoIt is cool as heck that truly open hardware might actually win in our lifetimes. (ARM was an interesting start, but too much licensing).
- chrisjj 2y ago> There is something about RISC-V that really inspires lots of hackers "Not Arm" :)
- trq01758 2y agoSimplicity, it's a modern MOS 6502. Base RISC-V has even less instructions than 6502.
- Joker_vD 2y agoAlso it has less registers (32 vs. 256 in the zero page) and less addressing modes.
- wk_end 2y agoBut the 6502's "registers" are much smaller and you can do much less with them. You can't really sensibly compare the two approaches so superficially.
- crackez 2y agoYet the same number of register bytes (when only counting 6502's zero page)... 32x8 = 256 bytes...
- brucehoult 2y ago31x4 = 124 bytes :-) The Zero register doesn't have to physically exist, and we're talking about 32 bit CPUs e.g. Pi Pico 2 here, right. Ok, 128 bytes if you add in the PC. Except for RV32E -- as seen in the very popular $0.10 CH32V003 -- which has 15x4 = 60 bytes of GPRs, plus the PC. Plus usually a few CSRs on practical CPUs, though Zicsr is an extension so you don't have to have it.
- spsesk117 2y agoulisp 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 :)
- bongodongobob 2y agoThat's neat but I don't know why you'd minimize characters rather than ROM size for a microcontroller.
- chrisjj 2y ago"very fun" ;)
- deleted 2y ago[deleted]
- bloopernova 2y agoI 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 https://www.adafruit.com/product/6000 [2] https://www.adafruit.com/new 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.
- jwhitlark 2y agoRust also runs on picos and Esp32s, if that’s your jam.
- pjmlp 2y agoI see Lisp compilers and upvote. :) Great work.
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- patrickmay 2y agoI see people who upvote Lisp compilers and upvote.
- hinkley 2y agoGretchen! Stop trying to make Lisp happen. It’s not going to happen.
- kragen 2y agoI 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.
- kazinator 2y agoThings like atom and symbols are functions in the runtime. A Lisp compiler only has to handle special forms, and function calls. If we see the compiler source code using a special operator that it doesn't handle, either directly using it or via macro expansion, then we know it's not yet self-hosting.
- kragen 2y agoNormally I would assume you were right, but the list of things supported in the compiler includes these items: List functions: car, cdr Arithmetic functions: +, -, *, /, mod, 1+, 1- Arithmetic comparisons: =, <, <=, >, >=, /= So I think the compiler may not be able to compile calls to arbitrary functions? Maybe I should read the code.
- kazinator 2y agoHandling specific functions is optional. If the compiler can compile a function call, it can compile a call to the + or car functions, which then have to be president in the run-time. Functions like these are obvious targets for special recognition and inlining. Arithmetic code won't be as fast as it could be if every + has to be a call to a function, but it will work. A compiled Lisp implementation can be bootstrapped to the point where the definition of car in the library looks like: (defun car (x) (car x)) And similarly for some other functions. Then there are only two places in the system that know how to actually extract the car field: the compiler source code, and the corresponding compiler executable needed for bootstrapping. In that case if you remove the car handling from the compiler then the system's self-hosting and bootstrapping ability breaks.
- Joker_vD 2y agoThis 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.
- johnsondavies 2y agoThe assembler is two-pass.
- dang 2y agoEdit: It's a pity we missed http://www.ulisp.com/show?4W2I http://www.ulisp.com/show?4W2I. It was posted (https://news.ycombinator.com/item?id=41190553 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 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 https://news.ycombinator.com/item?id=41681705 - Sept 2024 (1 comment) An ARM Assembler Written in Lisp - https://news.ycombinator.com/item?id=36646277 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 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 https://news.ycombinator.com/item?id=30612770 - March 2022 (7 comments) uLisp on the Raspberry Pi Pico - https://news.ycombinator.com/item?id=29970231 https://news.ycombinator.com/item?id=29970231 - Jan 2022 (14 comments) uLisp - https://news.ycombinator.com/item?id=27036317 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 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 https://news.ycombinator.com/item?id=22640980 - March 2020 (35 comments) uLisp – ARM Assembler in Lisp - https://news.ycombinator.com/item?id=22117241 https://news.ycombinator.com/item?id=22117241 - Jan 2020 (49 comments) Ray tracing with uLisp - https://news.ycombinator.com/item?id=20565559 https://news.ycombinator.com/item?id=20565559 - July 2019 (10 comments) uLisp: Lisp for microcontrollers - https://news.ycombinator.com/item?id=18882335 https://news.ycombinator.com/item?id=18882335 - Jan 2019 (16 comments) GPS mapping application in uLisp - https://news.ycombinator.com/item?id=18466566 https://news.ycombinator.com/item?id=18466566 - Nov 2018 (4 comments) Tiny Lisp Computer 2 - https://news.ycombinator.com/item?id=16347048 https://news.ycombinator.com/item?id=16347048 - Feb 2018 (2 comments) uLisp – Lisp for the Arduino - https://news.ycombinator.com/item?id=11777662 https://news.ycombinator.com/item?id=11777662 - May 2016 (33 comments)
- deleted 2y ago[deleted]
- anthk 2y agoI'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.
- crackez 2y agoVulkan shaders have entered the chat...
- brucehoult 2y agoOh, interesting. I was going to say nothing in the RISC-V world is comparable to the N100 yet, at any price, but it looks like the N100 is anywhere from 10 to 20 times faster than the N270. Geekbench 6 doesn't have any N270 results but it has a couple of Atom 230 results, and other sources indicate those two are very similar. So, ok, on Geekbench a single core of the JH7110 comes in a bit faster than the Atom 230. And it's got four of them. You can get a Milk-V Mars CM with a 1.5 GHz JH7110 with 2 GB RAM for $34. You should be able to build a decent little netbook around that for $100. It's compatible with the Raspberry Pi 4 CM, so if there is a suitable netbook enclosure for the Pi 4 CM then it should work. Otherwise I think the ClockworkPi DevTerm R-01 would be the closest that actually exist at the moment. The single 1.0 GHz C906 core is a bit slower and the price is unfortunately $239. But the MuseBook for $299 is much much better.
- snvzz 2y ago>Not everyone needs a 16GB machine to compile huge current C++ projects. These days 64GB is barely sufficient for that. We really need to switch to mold linker.
- anthk 2y agoYou can always reduce the number of jobs from make/ninja to 4... But, yes, you are right; the issue lies on linking. LibTD requires 2GB as minimum, but linking takes ages. At least with GCC, Clang requires far less RAM.
- pjmlp 2y agoOr Rust, Android,....
- crotho 2y ago[dead]