The canonical operator for accessing memory (as such) is memref
:
defun memref (object offset &key (index 0) (type :lisp) localp (endian :host))
The object is any lisp-value whose 32-bit-pattern is taken as the base
address, to which the integer offset is added. Finally, the integer
index is multiplied with the byte-size of the type, and added to get
the final address.
For example, the lisp value 100 (a positive fixnum) is represented by
the bit-pattern #x00000400, which means that e.g.
(memref 100 6 :index 1 :type :unsigned-byte16)
is compiled into something like this pseudo-assembly:
movw (#x408) <destination>
For a second example, assume you have a cons-cell in the variable 'x'. Cons-cells are pointers to
two consecutive words, namely the car and the cdr of the cell, and such pointers are recongnized by
their having a low-tag of 1 (i.e. they are offset by 1 relative to the true memory location).
Therefore the following operation:
(memref x -1 :index 0)
will return (car x), while
(memref x -1 :index 1)
returns (cdr x). To the extent that (typep x 'list) is indeed true, the above operations are exactly
equivalent to the car and cdr operations. That is, they are GC safe and generally "well behaved".
A variant of memref
is memref-int
, which computes the address a bit
differently:
defun memref-int (address &key (offset 0) (index 0) (type :unsigned-byte32) (physicalp t))
Here, the address is provided explicitly as an integer, to which the
integer offset and the index multiplied by the type's size is
added. memref-int
tends to be more appropriate for accessing hardware
devices, while memref
is more of a primitive for the lisp system as
such.
I'll describe the remaining arguments which are (more or less) common
to the two operators. The type argument determines how the memory is
read and interpreted. To read a value of type :lisp
, you have to know
very well what you are doing, because otherwise you can easily end up
with an illegal lisp value. For hardware programming, you typically
want :unsigned-byte32
, 16
, or 8
. Some other types supported but less
useful are :character
(which is 8-bit and rather ill-specified in
terms of character-sets, which is true of Movitz in general), and
:location
which takes a 32-bit value and strips the lower two bits,
leaving a fixnum lisp-value.
The localp
argument is to do with some GC-related protocols that are
not really finalized. Just leave it at its default is safe.
The endian
argument determines the endianess of unsigned-byteXX bytes.
The physicalp
argument, finally, is important. Set this to true if the
address is to be interpreted as a physical address, as seen on the
memory bus and by hardware peripheral devices. Movitz currently sets
up the CPU such that the default memory space is shifted one MB or so
from the physical address space (using segmentation). This leaves most
of the "interesting" hardware memory space unavailable, unless you
have :physicalp t
(which is the default for memref-int
).