SIMD layout again

"Tim Bradshaw (as tfb at tfeb dot org)" <[email protected]> Wed, 15 Jul 2026 11:24:35 +0100
Newsgroups gmane.lisp.lispworks.general
Message-ID <[email protected]>
This is a question for people who understand SIMD things, which I don't.  There's a big preamble, sorry.

As I've mentioned before, my larval SOA system lets you define classes whose 'slots' are arrays, and there is then macrology which turns slot access into aref.  The way slots get mapped onto arrays and the underlying arrays are accessible via a 'low-level' interface, with the idea that you can pass these arrays to foreign code.

Here is a simple definition (do not worry about the details of the syntax, if it's ever finished there will be documentation):

(define-soa-class foo ()
  ((x :array t :type fixnum :initform 12)
   (y :array t :type fixnum :initform 13)
   (i :array t :initform nil))

This defines two arrays: one will hold x and y, and one will hold i (this is because their upgraded element types differ, although you can also force things into different arrays if you wish).

You can look at the layout (this is all available programmatically as well):

? (describe (make-instance 'foo :length 8))

[...]
its class foo has a valid layout
it has 2 arrays:
 index 0, element type (signed-byte 64), 2 slots
 index 1, element type t, 1 slot
it has 3 array slots:
 x                  index 0, offset 0
 y                  index 0, offset 1
 i                  index 1, offset 0

And you can look at the arrays:

? (soa-array (make-instance 'foo :length 2) 0)
#2A((12 13) (12 13))

? (type-of *)
(simple-array (signed-byte 64) (2 2))

The arrays have a rank one greater than the rank of the object rather than a 'stretched' last dimension because this makes handling rank-zero objects not have to be a weird special case.  Note that this doesn't alter the layout in memory of course.

You can provide an array allocator which, for instance, could allocate some arrays statically for foreign code.

Martin: this is why I was interested in pinnable arrays with dimension greater than 1.  But in fact I can't imagine a case where you would not just want them to be static, because you'll always know they're going to be passed to foreign code.

So, OK.  The layout of this array will be x0,y0,x1,y1,...

Now here's the question

I think that SIMD things might well want the layout to be, for instance x0,x1,x2,x3,y0,y1,y2,y3,...  You can achieve this by defining index mapping:

(define-soa-class foo ()
  ((x :array t :type fixnum :initform 12 :index-mappings ((2 1) (4 2) (6 3)))
   (y :array t :type fixnum :initform 13 :index-mappings ((1 4) (3 5) (5 6)))
   (i :array t :initform nil)))

Note that these are in terms of the underlying array.  But it will tell you how the underlying array is layed out (see above) so this is all portable, although it's only use is to make interfacing to foreign code easier.  Note also that omitted mappings are  identity mappings (so for x there is an omitted (0 0) mapping and y has an omitted (7 7)).

Now

? (describe (make-instance 'foo :length 8))

[...]
its class foo has a valid layout
it has 2 arrays:
 index 0, element type (signed-byte 64), 2 slots
  permutation 0 -> 0, 4 -> 1, 1 -> 2, 5 -> 3, 2 -> 4, 6 -> 5, 3 -> 6, 7 -> 7
  allocation unit 4
 index 1, element type t, 1 slot
it has 3 array slots:
 x                  index 0, offset 0
 y                  index 0, offset 1
 i                  index 1, offset 0

And note that you now have to make the length (or last dimension) be a multiple of 4, or you get a continuable error:

? (soa-dimensions (make-instance 'foo :dimensions '(10 2)))

Error: Last dimension 2 is not divisible by class allocation unit 4
  1 (continue) Use 4
  2 (abort) Return to top loop level 0.

Type :b for backtrace, or :c <option number> to proceed, or :a to abort.
Type :bug-form "<subject>" for a bug report template or :? for other options.

1 ? :c
(10 4)

So now

? (soa-array (make-instance 'foo :length 4) 0)
#2A((12 12) (12 12) (13 13) (13 13))

So there are all the x's and then all the y's and repeat.  You can define completely arbitrary permutations this way: the index mappings get large, but that's what macros are for.  Lisp access to objects with permuted indices will be significantly slower, but that's not the point: they exist so that foreign SIMD code can eat them in a format it likes.

Not mentioned above is that you can also add padding, so if you have 3 slots and need groups of 4 you can pad the arrays.

So the question is: before I redo the macro level (which is going to be fiddly), is something like this likely to be helpful for SIMD code (or any other foreign code purpose).

Thanks

--tim




_______________________________________________
Lisp Hug - the mailing list for LispWorks users
[email protected]
http://www.lispworks.com/support/lisp-hug.html