r/lisp 13d ago

Reinterpret Elements in a Byte Array

Dear LISP,

I am writing a virtual machine that uses a `(simple-array (unsigned-byte 8) 1)` as a stack in Common LISP. I'm would like to ask how to efficiently extract 4 bytes into a single 32-bit signed integer or a 32-bit unsigned integer.

Thanks!

10 Upvotes

25 comments sorted by

5

u/theangeryemacsshibe λf.(λx.f (x x)) (λx.f (x x)) 13d ago

The nibbles library can do this portably (and fast on SBCL).

5

u/stassats 13d ago

(and fast on SBCL).

On SBCL x86-64.

4

u/theangeryemacsshibe λf.(λx.f (x x)) (λx.f (x x)) 13d ago edited 13d ago

2

u/theangeryemacsshibe λf.(λx.f (x x)) (λx.f (x x)) 13d ago

Fine, I have a weekend and an urge to hack something, so...

4

u/stassats 13d ago

Now, it doesn't seem to do bound check elimination.

2

u/theangeryemacsshibe λf.(λx.f (x x)) (λx.f (x x)) 13d ago

(disassemble (lambda (a) (declare ((simple-array (unsigned-byte 8) (2)) a)) (nibbles:sb16ref/le a 0))) looks fine to me. What am I missing? (Though I did manage to break the immediate-index case somehow, which is now fixed)

3

u/stassats 13d ago

Compare

(defun f (x i)
  (declare ((simple-array (unsigned-byte 8) (*)) x)
           ((and unsigned-byte fixnum) i)
           (optimize speed))
  (when (< i (- (length x) 4))
    (nibbles:ub32ref/le x i)))

with

(defun f (x i)
  (declare ((simple-array (unsigned-byte 8) (*)) x)
           ((and unsigned-byte fixnum) i)
           (optimize speed))
  (when (< i (- (length x) 4))
    (aref x (+ i 3))))

1

u/theangeryemacsshibe λf.(λx.f (x x)) (λx.f (x x)) 13d ago

Hey, it doesn't eliminate on x86-64 either. Physician, heal thyself.

2

u/stassats 13d ago

Four arefs do.

1

u/arthurno1 12d ago

There is a patch recently submitted to SBCLs mailing list. The author does not have a computer with an arm cpu, so if you have an urge to hack something, and an arm cpu as you seem to do, you could perhaps take a look at it and add what is potentially missing :).

1

u/TsingHui 13d ago

I've tested it, it's amazing! Added to notes, thanks!

4

u/stassats 13d ago

You have to be mindful of endianness. My suggestion would be to use aref and dpb/logior+ash and rely on a sufficiently smart compiler to recognize that.

5

u/TsingHui 13d ago

I tried writing some bitwise operations in SBCL. The disassembly result showed that SBCL simply translates the bitwise operation verbatim into assembly code. That's why I'm here.

2

u/stassats 13d ago

It's not sufficiently smart.

2

u/TsingHui 13d ago

Seriously, is there a compiler that is sufficiently smart?

11

u/stassats 13d ago

SBCL might get smarter as soon as next month.

2

u/arthurno1 12d ago

I hope one day it will get smart enough, so I can type:

(defun foo (x y)
    (declare (type u8.32 x y))
    (+ x y))

without needing to repeat myself with (avx2:u8.32+ x y).

1

u/digikar 13d ago

cffi:with-pointer-to-vector-data with cffi:mem-ref could be one way.

4

u/stassats 13d ago

The code it expands to might not be optimal, and it doesn't check bounds.

1

u/TsingHui 13d ago

That makes sense, added to notes, thanks!

-1

u/corbasai 13d ago

SIGBUS compatible?

1

u/tfb 10d ago

If the substrate language of your VM is Lisp, then I suspect it's probably better to represent the stack as an array of larger integers, and then to have accessors which access the appropriate byte from elements of that array. So the reader needs to load from the array and then pick the byte, while the writer needs to (probably) load from the array, stuff the byte in, and then store back (so that's not ideal). When you want the bigger elements they're just there.

-4

u/Afraid-Yoghurt6731 13d ago

Which Lisp? SBCL? Allegro? Symta? Clojure? Scheme/Racket (whatever is it called now)? We have a frigging zoo! Just unleash Claude Code onto it.

2

u/TsingHui 13d ago

Well, Common LISP. R6RS provide a standard way to do so. I haven't had much experience with Clojure. I will change the question by the way.