Hacker News new | past | comments | ask | show | jobs | submit login
uLisp – ARM Assembler in Lisp (ulisp.com)
172 points by eggy on Jan 22, 2020 | hide | past | favorite | 49 comments



I find Lisps to be very well suited towards this task, because of its homoiconicity and features like quasiquotation, effectively you have access to the best macro system possible.

Related: Z80 assembler in Scheme

https://github.com/siraben/zkeme80/blob/master/src/assembler...


I once threw together a 6502 assembler (really a front end to x65) in Scheme to be able to write Commodore programs with powerful Lisp-like macro facilities.


That reminds me of a great anecdote from an old thread:

In 1983, Rescue on Fractalus and Ballblazer, the first releases from Lucasfilm Games, were built with a system written in Lisp. It was a 6502 assembler with Lisp syntax and a Lisp-style macro facility, written in Portable Standard Lisp, running in Berkeley Unix on a 4MB VAX 11/750. Unfortunately it was eventually abandoned because the developer had left and it was a bit of a strain on the VAX that was shared by the whole development team.

Yes, I wrote it. Yes, it was my first non-academic programming job. Yes, the users complained about the parentheses, and the slowness. But, they also took advantage of the powerful macro facility.

https://news.ycombinator.com/item?id=2475210


You might have an idea on this but how would you approach the opposite operation? Assembly to an in-memory representation, how does Lisp/Scheme handle that kind of manipulation?


With the BYTE accessors and binary constants, I find bit and byte manipulation to be far easier in common-lisp than in C.

e.g. set the 3 bytes starting at bit 2 of x to the binary value 110:

  (setf (ldb (byte 3 2) x) #b110)
or set the 3rd byte (8 bits starting at bit 24) of x to 0xab:

  (setf (ldb (byte 8 24) x) #xab)
The first example compiles down to the following 2 instructions on sbcl/x64:

     ; A4:       4883E1C6         AND RCX, -58
     ; A8:       4883C930         OR RCX, 48
NOTE: The values are 1 bit shifted from what you might expect (binary 110 = 6; 6 << 2 == 24, not 48) because SBCL uses low-bit tagging to distinguish integers from pointers; working with arrays of unboxed machine-friendly sized integers (8,32,64) works as expected.


Do you mean how opcodes are represented in Lisp-based assemblers?


No I mean a disassembler, I can see how an assembler is nice to write in a language like that but I've never seen much byte manipulation examples. For example going from machine code to the list of opcodes for example.


Every Common Lisp has a DISASSEMBLE function built-in. Compile a function and call the disassembler.

For example SBCL

https://github.com/sbcl/sbcl/blob/master/src/compiler/disass...

and its ARM64 instructions:

https://github.com/sbcl/sbcl/blob/master/src/compiler/arm64/...


Here's the times13 function in CCL on x86-64. (Note that #x68 is 13 shifted left 3 times to allow tag bits in the LS 3 bit positions. Also the below could be optimized a lot more but this is the default compilation.)

  ? (defun times13 (x) (* 13 x))
  TIMES13
  ? (disassemble 'times13)
  ;;; (defun times13 (x) (* 13 x))
      (recover-fn-from-rip)                   ;     [0]
      (cmpl ($ 8) (% nargs))                  ;     [7]
      (jne L41)                               ;    [10]
      (pushq (% rbp))                         ;    [12]
      (movq (% rsp) (% rbp))                  ;    [13]
      (pushq (% arg_z))                       ;    [16]

  ;;; (* 13 x)
      (movl ($ #x68) (% arg_y.l))             ;    [17]
      (movq (@ -8 (% rbp)) (% arg_z))         ;    [22]
      (movq (% rbp) (% rsp))                  ;    [26]
      (popq (% rbp))                          ;    [29]
      (jmp (@ .SPBUILTIN-TIMES))              ;    [30]

  ;;; #<no source text>

      (:align 3)
  L41                                         ;   [@56]
      (uuo-error-wrong-number-of-args)        ;    [41]
  NIL


There you go,

"CHIP-8 in Common Lisp: Disassembly"

https://stevelosh.com/blog/2017/01/chip8-disassembly/


That would be an interesting project. AFAIK Emacs has a bytecode disassembler[0] but I'm not aware of disassemblers for other instruction sets.

[0] https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/emacs-...


Nice move by uLisp.

It's powerful to re-code time critical snippets of code in Assembler, while still having access to a high level language like Lisp for the complex stuff.

It might interest people to know that Forth always offered this functionality and the ':' compiler lets one build ASM macros as well.


I was wondering why one person on Hackaday thought Forth was uniquely suited for exploration in embedded systems programming other than also being a stack language like Assembly. Thanks for pointing this out.


It's not unusual. Lots of Lisp systems support inline assembler or inline C. Historically Lisp systems stored compiled code as LAP (Lisp Assembly Program) and loaded these files via its built-in assembler.

The old Lisp 1.5 manual documents an in-memory assembler in Appendix C, Page 73

http://www.softwarepreservation.org/projects/LISP/book/LISP%...


Neat reminds me of Game Oriented Assembly Lisp, the language used for a bunch of the Naughty Dog games. It included inline assemblers for the various processors in the PS2, and was used in a very procedural, macro heavy style that this seems to be encouraging.

Neat!

Here's an example of GOAL code with inline code for the vector processors.

https://web.archive.org/web/20070127022728/http://lists.midn...


I asked a question on HN but didn't get any answers - this might be an easier forum to get one.

I'm working on a hobbyist rocketry project where I'm helping build the flight computer - basically an Arduino Nano connected to modules like a GPS system, accelerator, gyroscope, etc. Are there any Lisp dialects that someone would recommend for an Arduino? The Nano is technically replaceable so if there are better boards that can use Lisp I'd be interested too!


You might want to consider running something like the Raspberry Pi Zero. Looks like the Nano is 45mm x 18mm and the Pi Zero is 65mm x 30mm. If you could fit the Pi Zero it would probably make your life a lot easier.


This is a great suggestion; the Zero has enough oomph to run the JVM, which gives you access to Clojure!

I had the most luck installing the Zulu Embedded version of Java, and I've had a Clojure webapp running on my Pi Zero for over a week now without any hiccups.

I'm a post or two behind on chronicling my adventures, but I did at least capture how I got Clojure running on the Zero here: https://ajpierce.com/2020-01-08_safe-to-wake-pt4/


I don't think some specific lisp is required, the one which compiles to C should be good enough. Active example is https://gitlab.com/owl-lisp/owl


I don't know anything about owl-lisp, but you also need a lisp that does not do any runtime memory allocation, as those are usually problematic in embedded projects, especially in those with as little RAM as the nano.


If they have more memory than an IBM 704, it isn't that problematic.


After installing uLisp I have 518 bytes of RAM and 1050 bytes of storage on my board - I'd kill for the sweet, sweet 18,432 bytes of an IBM 704! ;)


What's wrong with the uLisp that the linked article is about?


The nano is basically a thin uno and has little storage for large projects, and OPs list of sensors is large. Something like three hundred cell limit. Its just not going to fit.

I've played with drivers using ulisp and its fun and very fast on an adafruit SAMD51 board with plenty of storage. A Metro M4 Gran Central is a lovely dev board but too large for OP to launch.

Maybe an adafruit M4 feather board would work. I've not used one but its supposedly plug and play with ulisp, so I've heard.

I've never used one, but in theory a mattairtech.com product seems like what OP wants. ulisp should compile its just another SAMD51 board, right? And its available with some of the sensors OP wanted onboard. Note that "never used one" means OP will literally be breaking new ground. All I'm sure of its a physically very small SAMD51 board and I can't see any obvious reason ulisp wouldn't work.

The future of embeded dev is probably FPGA for custom hardware and interfaces with lisp or similar HLL (Straight up Clojure getting embedded?). No I don't mean tomorrow future, but maybe 2030.


I was browsing through the uLisp sources looking for places where I could maybe provide FPGA acceleration... Didn't come up with anything off the top of my head, but the thoughts are germinating :-) Might dust off my copy of the Rekursiv book...


I did just try uLisp but it used 96% of my storage and 74% of my RAM, so it's not viable. It was nice to set up though - it pretty much worked out the box - so I do suggest trying it out on slightly larger systems.


According to recent articles on HN, NASA used to use Macintosh Common Lisp for many of its projects.

I'm not an Arduino person, but maybe there's a Common Lisp variant available to you.


Common LISP could certainly generate assembler for Arduino and other MCUs, but there's no way you're fitting that fat old beast (whom I do still love) on one . :-)


They also had several megabytes of RAM.


Yep this is an important point - I had 2 KB of RAM available and uLisp leaves me with 518 bytes.. I need to go smaller!


GP was talking about common lisp. Even CLISP requires ~4MB for the heap.


You might want to check this one:

https://makerlisp.com/


Any Raspberry Pi should work fine for Lisp but if I had to use an Arduino I'd just write the code in Processing (or whatever they call the native Arduino compiler language these days) because fitting a Lisp into one would probably be more trouble than it's worth.


Lisp used to run in 60's hardware, I bet most embedded boards have more memory than those.


Of course it can be done, but it would be a rather crippled Lisp. If the only Lisp I had was 60s Lisp, I'd probably choose Forth instead.


I just tried uLisp on my embedded board and it used 96% of the storage and 74% of the memory. Easy to set up but not viable.


no direct arduino experience, but I'd imagine the potential would be greater for scheme than lisp since it is a more minimal subset - quick google reveals 'microscheme'

https://news.ycombinator.com/item?id=11790630

that said more powerful scheme features like tail recursion and macro systems may or may not be compatible with a more embedded scheme, so I wouldn't count on it being a full 'representative' of what scheme actually is - this may or may not matter, depending on the project


Not firsthand experience, but what about one of the Scheme dialects that compiles to C?


Ah yea, I did something like this on top of Common Lisp, but not as interactive yet: https://github.com/stuij/armish

My plan at the time was to use a serial protocol and interface with a Nintendo GBA/DS for this kind of interactive Lisp programming.

It feels a bit dirty mixing implementation and target language like this, but it's great fun to program for.


Knowing nothing about uLisp beyond glancing at the code snippets, this seems like it would be a fun tool to use for a CS class on low level programming and compilers.


The author also has a uLisp to C converter and sensor library in uLisp. Very useful and fun!


I came upon uLisp over a few years ago, and was glad to see it show up for the ESP8266 chips. It makes programming these tiny controllers with WiFi enjoyable. Simple things like leaving the parameters blank in a WiFi connect function disconnects it from any current connection. It also blends in with my love of the minimal. I had been playing with Wasp Lisp [1,2] at the time when I found uLisp.

[1] https://github.com/swdunlop/WaspVM

[2] https://github.com/doublec/WaspVM


Can anybody speak to how hard uLisp would be to port to RISC-V? It looks pretty nifty. I have a little hobby computer I'm building around a RISC-V core, it might be nice to have it boot to this.


See Porting uLisp to a new platform: http://www.ulisp.com/show?2JZO.


I don't think it's too difficult. It's written in C and it has also been ported to ARM...


Thanks, just read through the source (https://github.com/technoblogy/ulisp/blob/master/ulisp.ino) and it looks do-able.


The call function has no parameters. Does this mean you can only have one assembled function at a time?


Yes, it currently provides one assembler function, but I plan to extend it to support multiple named functions.


Or can you use a closure to hold the assembled code as a context to a local instance of the call method?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: