-
Notifications
You must be signed in to change notification settings - Fork 27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding bytearray read/write/index constructs #20
base: master
Are you sure you want to change the base?
Changes from all commits
4c37c68
17bf52c
601283e
73afb33
9a89238
3ea6ab0
dec46f8
0d105b1
a09fa7b
5d00b0e
3e82758
4d48782
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -226,6 +226,15 @@ outputSpecial output (z@ZCursor {zCursor=Special pos@(SourcePos file line _) ke | |
(\i -> "(\\hsc_ptr -> peekByteOff hsc_ptr " ++ show i ++ ")") >> return False | ||
"poke" -> outputConst ("offsetof(" ++ value ++ ")") | ||
(\i -> "(\\hsc_ptr -> pokeByteOff hsc_ptr " ++ show i ++ ")") >> return False | ||
"readByteArray" -> outputByteArrayOperation True "readByteArray" | ||
"writeByteArray" -> outputByteArrayOperation True "writeByteArray" | ||
"indexByteArray" -> outputByteArrayOperation True "indexByteArray" | ||
"readByteArrayHash" -> outputByteArrayOperation False "readByteArray#" | ||
"writeByteArrayHash" -> outputByteArrayOperation False "writeByteArray#" | ||
"indexByteArrayHash" -> outputByteArrayOperation False "indexByteArray#" | ||
"readOffAddrHash" -> outputByteArrayOperation False "readOffAddr#" | ||
"writeOffAddrHash" -> outputByteArrayOperation False "writeOffAddr#" | ||
"indexOffAddrHash" -> outputByteArrayOperation False "indexOffAddr#" | ||
"ptr" -> outputConst ("offsetof(" ++ value ++ ")") | ||
(\i -> "(\\hsc_ptr -> hsc_ptr `plusPtr` " ++ show i ++ ")") >> return False | ||
"type" -> computeType z >>= output >> return False | ||
|
@@ -236,7 +245,28 @@ outputSpecial output (z@ZCursor {zCursor=Special pos@(SourcePos file line _) ke | |
"define" -> return True | ||
"undef" -> return True | ||
_ -> testFail pos ("directive " ++ key ++ " cannot be handled in cross-compilation mode") | ||
where outputConst value' formatter = computeConst z value' >>= (output . formatter) | ||
where | ||
outputConst value' formatter = computeConst z value' >>= (output . formatter) | ||
-- GHC's ByteArray# type only supports read/write/index in an aligned | ||
-- fashion. The index is always given in elements, not bytes. So, we | ||
-- must divide the field's offset in bytes by its size to get index in | ||
-- terms of elements. | ||
outputByteArrayOperation boxed operation = case break (== ',') value of | ||
(typ,',':field) -> do | ||
byteOffset <- computeConst z ("offsetof(" ++ value ++ ")") | ||
typSize <- computeConst z ("sizeof(" ++ typ ++ ")") | ||
-- This is the FIELD_SIZEOF macro as defined in the linux kernel. | ||
fieldSize <- computeConst z ("sizeof(((" ++ typ ++ "*)0)->" ++ field ++ ")") | ||
when (not (isPowerOfTwo fieldSize)) (testFail pos ("#error " ++ value)) | ||
let (elemOffset,r1) = divMod byteOffset fieldSize | ||
when (r1 /= 0) (testFail pos ("#error " ++ value)) | ||
let (typFieldRatio,r2) = divMod typSize fieldSize | ||
when (r2 /= 0) (testFail pos ("#error " ++ value)) | ||
if boxed | ||
then output ("(\\hsc_arr hsc_ix -> " ++ operation ++ " hsc_arr (" ++ show elemOffset ++ " + (hsc_ix * " ++ show typFieldRatio ++ ")))") | ||
else output ("(\\hsc_arr hsc_ix -> " ++ operation ++ " hsc_arr (" ++ show elemOffset ++ "# +# (hsc_ix *# " ++ show typFieldRatio ++ "#)))") | ||
return False | ||
_ -> testFail pos ("#error " ++ value) | ||
outputSpecial _ _ = error "outputSpecial's argument isn't a Special" | ||
|
||
outputText :: (Bool, Bool) -> (String -> TestMonad ()) -> SourcePos -> String | ||
|
@@ -406,6 +436,12 @@ binarySearch z nonNegative l u = do | |
u'-l' < u-l) -- @|u' - l'| < |u - l|@ | ||
(binarySearch z nonNegative l' u') | ||
|
||
isPowerOfTwo :: Integer -> Bool | ||
isPowerOfTwo 0 = False | ||
isPowerOfTwo 1 = True | ||
isPowerOfTwo n = case divMod n 2 of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. lets avoid the expensive divisions.. |
||
(m,r) -> if r == 0 then isPowerOfTwo m else False | ||
|
||
-- Establishes bounds on the unknown integer. By searching increasingly | ||
-- large powers of 2, it'll bracket an integer x by lower & upper | ||
-- such that lower <= x <= upper. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -195,6 +195,25 @@ Meanings of specific keywords: | |
Computes the alignment, in bytes, of ``struct_type``. It will have type | ||
``Int``. | ||
|
||
``#readByteArray ⟨struct_type⟩, ⟨field⟩`` | ||
Outputs a function that indexes into an array of a C struct. It will | ||
have the type ``Prim a => MutableByteArray (PrimState m) -> Int -> m a``. | ||
The context must ensure that ``a`` is a type that can be marshalled | ||
to the C field type. This only supports access to aligned fields and | ||
will fail at compile time if the field is not aligned. The source | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. but why? that seems like an arbitrary restriction. I'm guessing because some platforms don't allow unaligned access or have slow unaligned access, but in that case you should check the platform. |
||
expression ``#{readByteArray struct foo, bar} arr 42`` becomes an | ||
expression that has the same behavior as the C expression | ||
``((struct foo*) arr)[42].bar``. | ||
|
||
``#readByteArrayHash ⟨struct_type⟩, ⟨field⟩`` | ||
Variant of ``#readByteArray`` with unlifted argument and result types. | ||
It will have the type | ||
``Prim a => MutableByteArray# s -> Int# -> State# s -> (# State# s, a #)``. | ||
The macros ``#readByteArrayHash``, ``#writeByteArayHash``, | ||
``#indexByteArrayHash``, ``#readOffAddrHash``, ``#writeOffAddrHash``, | ||
and ``#indexOffAddrHash`` are intended to be used to implement instances | ||
of ``Prim`` (see `implementing-instances`_). | ||
|
||
``#enum ⟨type⟩, ⟨constructor⟩, ⟨value⟩, ⟨value⟩, ...`` | ||
A shortcut for multiple definitions which use ``#const``. Each | ||
``value`` is a name of a C integer constant, e.g. enumeration value. | ||
|
@@ -247,3 +266,121 @@ The following are unsupported: | |
- ``#{let}`` | ||
- ``#{def}`` | ||
- Custom constructs | ||
|
||
.. _implementing-instances: | ||
|
||
Implementing Instances | ||
~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
As an example, the ``Storable`` and ``Prim`` instances for a haskell | ||
data type corresponding to the POSIX ``struct pollfd`` are implemented | ||
below. `IEEE Std 1003.1-2017`_ describes ``struct pollfd`` as: | ||
|
||
.. _`IEEE Std 1003.1-2017`: http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/poll.h.html | ||
|
||
+-------+---------+----------------------------------------+ | ||
| int | fd | The following descriptor being polled. | | ||
+-------+---------+----------------------------------------+ | ||
| short | events | The input event flags. | | ||
+-------+---------+----------------------------------------+ | ||
| short | revents | The output event flags. | | ||
+-------+---------+----------------------------------------+ | ||
|
||
The `Linux poll man page`_ provides a concrete implementation, | ||
describing ``struct pollfd`` as: | ||
|
||
.. _`Linux poll man page`: http://man7.org/linux/man-pages/man2/poll.2.html | ||
|
||
:: | ||
|
||
struct pollfd { | ||
int fd; /* file descriptor */ | ||
short events; /* requested events */ | ||
short revents; /* returned events */ | ||
}; | ||
|
||
This type is most directly expressed in Haskell as: | ||
|
||
:: | ||
|
||
data PollFd = PollFd | ||
{ descriptor :: !Fd | ||
, request :: !CShort | ||
, response :: !CShort | ||
} | ||
|
||
We now use ``hsc2hs`` to help write a portable ``Storable`` instance. | ||
The GHC ``NamedFieldPuns`` extension is used for succinctness although | ||
it is not necessary:: | ||
|
||
instance Storable PollFd where | ||
sizeOf _ = #size struct pollfd | ||
alignment _ = #alignment struct pollfd | ||
peek ptr = do | ||
descriptor <- #{peek struct pollfd, fd} ptr | ||
request <- #{peek struct pollfd, events} ptr | ||
response <- #{peek struct pollfd, revents} ptr | ||
return (PollFd{descriptor,request,response}) | ||
poke ptr PollFd{descriptor,request,response} = do | ||
#{poke struct pollfd, fd} ptr descriptor | ||
#{poke struct pollfd, events} ptr request | ||
#{poke struct pollfd, revents} ptr response | ||
|
||
More verbosely, ``hsc2hs`` can also be used to help write portable ``Prim`` | ||
instances that are intended to marshall C data types. (Unlike ``Storable``, | ||
whose sole purpose is to help marshall C data types, ``Prim`` is not used | ||
exclusively for this purpose, but it is occassionally useful in this domain.) | ||
This example uses the GHC extensions ``MagicHash`` and ``UnboxedTuples``, | ||
which are required. It also uses the GHC extension ``NamedFieldPuns``, | ||
which is optional as it was in the ``Storable`` example. Notice that all | ||
the source Haskell hash characters escaped by doubling them:: | ||
|
||
import GHC.Exts | ||
import Data.Primitive.Types (Prim(..),defaultSetByteArray##,defaultSetOffAddr##) | ||
|
||
unInt :: Int -> Int## | ||
unInt (I## i) = i | ||
|
||
instance Prim PollFd where | ||
sizeOf## _ = unInt #{size struct pollfd} | ||
alignment## _ = unInt #{alignment struct pollfd} | ||
indexByteArray## arr i = PollFd | ||
{ descriptor = #{indexByteArrayHash struct pollfd, fd} arr i | ||
, request = #{indexByteArrayHash struct pollfd, events} arr i | ||
, response = #{indexByteArrayHash struct pollfd, revents} arr i | ||
} | ||
writeByteArray## arr i PollFd{descriptor,request,response} s0 = | ||
case #{writeByteArrayHash struct pollfd, fd} arr i descriptor s0 of | ||
s1 -> case #{writeByteArrayHash struct pollfd, events} arr i request s1 of | ||
s2 -> #{writeByteArrayHash struct pollfd, revents} arr i response s2 | ||
readByteArray## arr i s0 = case #{readByteArrayHash struct pollfd, fd} arr i s0 of | ||
(## s1, descriptor ##) -> case #{readByteArrayHash struct pollfd, events} arr i s1 of | ||
(## s2, request ##) -> case #{readByteArrayHash struct pollfd, revents} arr i s2 of | ||
(## s3, response ##) -> (## s3, PollFd{descriptor,request,response} ##) | ||
setByteArray## = defaultSetByteArray## | ||
indexOffAddr## arr i = PollFd | ||
{ descriptor = #{indexOffAddrHash struct pollfd, fd} arr i | ||
, request = #{indexOffAddrHash struct pollfd, events} arr i | ||
, response = #{indexOffAddrHash struct pollfd, revents} arr i | ||
} | ||
writeOffAddr## arr i PollFd{descriptor,request,response} s0 = | ||
case #{writeOffAddrHash struct pollfd, fd} arr i descriptor s0 of | ||
s1 -> case #{writeOffAddrHash struct pollfd, events} arr i request s1 of | ||
s2 -> #{writeOffAddrHash struct pollfd, revents} arr i response s2 | ||
readOffAddr## arr i s0 = case #{readOffAddrHash struct pollfd, fd} arr i s0 of | ||
(## s1, descriptor ##) -> case #{readOffAddrHash struct pollfd, events} arr i s1 of | ||
(## s2, request ##) -> case #{readOffAddrHash struct pollfd, revents} arr i s2 of | ||
(## s3, response ##) -> (## s3, PollFd{descriptor,request,response} ##) | ||
setOffAddr## = defaultSetOffAddr## | ||
|
||
Keep in mind that, unlike the code-generation for the ``Storable`` instance, | ||
this code-generation will cause a compile-time failure if the operating system | ||
has an esoteric implementation of ``struct pollfd`` with unaligned fields. | ||
Note that the ``Prim`` instances is necessarily more verbose that the | ||
``Storable`` instance. Two factors contribute to this: | ||
|
||
* ``Prim`` deals with both managed and unmanaged memory | ||
* The typeclass methods of ``Prim`` use unlifted types | ||
|
||
Despite the verbosity, this is a portable solution for Haskell code that needs | ||
to pass runtime-managed, unpinned memory to a C library using the unsafe FFI. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does it matter that the element be
fieldSize
aligned? regardless of the alignment the index doesn't change.