diff options
author | Herbert Valerio Riedel <hvr@gnu.org> | 2014-11-18 16:52:02 +0100 |
---|---|---|
committer | Herbert Valerio Riedel <hvr@gnu.org> | 2014-11-19 11:02:09 +0100 |
commit | 42244668af6d8c1dd6a2d64af90ed57d8ecd8d88 (patch) | |
tree | 9455cec1be6abbe8a1d2c6a1da4ed7d3838fc8a4 | |
parent | 7c748d9fcf12bd16e0de56187fa6fcf3d6dbf39a (diff) | |
download | haskell-42244668af6d8c1dd6a2d64af90ed57d8ecd8d88.tar.gz |
Reimplement im/export primitives for integer-gmp2
The import/export operations were available in `integer-gmp-0.5.1`
already, but need to be reimplemented from scratch for the
`integer-gmp-1.0.0` rewrite.
This also adds a few more operations than were previously available for
use w/ the `BigNat` type (which will be useful for implementing
serialisation for the upcoming `Natural` type)
Specifically, the following operations are (re)added (albeit with
slightly different type-signatures):
- `sizeInBaseBigNat`
- `sizeInBaseInteger`
- `sizeInBaseWord#`
- `exportBigNatToAddr`
- `exportIntegerToAddr`
- `exportWordToAddr`
- `exportBigNatToMutableByteArray`
- `exportIntegerToMutableByteArray`
- `exportWordToMutableByteArray`
- `importBigNatFromAddr`
- `importIntegerFromAddr`
- `importBigNatFromByteArray`
- `importIntegerFromByteArray`
NOTE: The `integerGmpInternals` test-case is updated but not yet
re-enabled as it contains tests for other primitives which aren't
yet reimplemented.
This addresses #9281
Reviewed By: austin, duncan
Differential Revision: https://phabricator.haskell.org/D480
-rw-r--r-- | libraries/integer-gmp2/cbits/wrappers.c | 161 | ||||
-rw-r--r-- | libraries/integer-gmp2/src/GHC/Integer/GMP/Internals.hs | 154 | ||||
-rw-r--r-- | libraries/integer-gmp2/src/GHC/Integer/Type.hs | 99 | ||||
-rw-r--r-- | testsuite/tests/lib/integer/integerGmpInternals.hs | 36 |
4 files changed, 431 insertions, 19 deletions
diff --git a/libraries/integer-gmp2/cbits/wrappers.c b/libraries/integer-gmp2/cbits/wrappers.c index 930f5b8508..9aac390493 100644 --- a/libraries/integer-gmp2/cbits/wrappers.c +++ b/libraries/integer-gmp2/cbits/wrappers.c @@ -288,3 +288,164 @@ integer_gmp_mpn_tdiv_r (mp_limb_t r[], mpn_tdiv_qr(q, r, 0, n, nn, d, dn); } } + + +/* Wraps GMP's 'mpz_sizeinbase()' function */ +HsWord +integer_gmp_mpn_sizeinbase(const mp_limb_t s[], const mp_size_t sn, + const HsInt base) +{ + assert (2 <= base && base <= 256); + + if (!sn) return 1; + + const mpz_t zs = {{ + ._mp_alloc = sn, + ._mp_size = sn, + ._mp_d = (mp_limb_t*)s + }}; + + return mpz_sizeinbase(zs, base); +} + +/* Single-limb version of 'integer_gmp_mpn_sizeinbase()' */ +HsWord +integer_gmp_mpn_sizeinbase1(const mp_limb_t s, const HsInt base) +{ + return s ? integer_gmp_mpn_sizeinbase(&s, 1, base) : 1; +} + +/* Wrapper around GMP's 'mpz_export()' function */ +HsWord +integer_gmp_mpn_export(const mp_limb_t s[], const mp_size_t sn, + void *destptr, HsInt destofs, HsInt msbf) +{ + /* TODO: implement w/o GMP, c.f. 'integer_gmp_mpn_import()' */ + assert (msbf == 0 || msbf == 1); + + if (!sn || (sn == 1 && !s[0])) + return 0; + + const mpz_t zs = {{ + ._mp_alloc = sn, + ._mp_size = sn, + ._mp_d = (mp_limb_t*)s + }}; + + size_t written = 0; + + // mpz_export (void *rop, size_t *countp, int order, size_t size, int endian, + // size_t nails, const mpz_t op) + (void) mpz_export(((char *)destptr)+destofs, &written, !msbf ? -1 : 1, + /* size */ 1, /* endian */ 0, /* nails */ 0, zs); + + return written; +} + +/* Single-limb version of 'integer_gmp_mpn_export()' */ +HsWord +integer_gmp_mpn_export1(const mp_limb_t s, + void *destptr, const HsInt destofs, const HsInt msbf) +{ + /* TODO: implement w/o GMP */ + return integer_gmp_mpn_export(&s, 1, destptr, destofs, msbf); +} + +/* Import single limb from memory location + * + * We can't use GMP's 'mpz_import()' + */ +inline HsWord +integer_gmp_mpn_import1(const uint8_t *srcptr, const HsWord srcofs, + const HsWord srclen, const HsInt msbf) +{ + assert (msbf == 0 || msbf == 1); + assert (srclen <= SIZEOF_HSWORD); + + srcptr += srcofs; + + HsWord result = 0; + + if (msbf) + for (unsigned i = 0; i < srclen; ++i) + result |= (HsWord)srcptr[i] << ((srclen-i-1)*8); + else // lsbf + for (unsigned i = 0; i < srclen; ++i) + result |= (HsWord)srcptr[i] << (i*8); + + return result; +} + +/* import into mp_limb_t[] from memory location */ +void +integer_gmp_mpn_import(mp_limb_t * restrict r, const uint8_t * restrict srcptr, + const HsWord srcofs, const HsWord srclen, + const HsInt msbf) +{ + assert (msbf == 0 || msbf == 1); + + srcptr += srcofs; + + const unsigned limb_cnt_rem = srclen % SIZEOF_HSWORD; + const mp_size_t limb_cnt = srclen / SIZEOF_HSWORD; + + if (msbf) { + if (limb_cnt_rem) { // partial limb + r[limb_cnt] = integer_gmp_mpn_import1(srcptr, 0, limb_cnt_rem, 1); + srcptr += limb_cnt_rem; + } + + for (unsigned ri = 0; ri < limb_cnt; ++ri) { + r[limb_cnt-ri-1] = integer_gmp_mpn_import1(srcptr, 0, SIZEOF_HSWORD, 1); + srcptr += SIZEOF_HSWORD; + } + } else { // lsbf + for (unsigned ri = 0; ri < limb_cnt; ++ri) { + r[ri] = integer_gmp_mpn_import1(srcptr, 0, SIZEOF_HSWORD, 0); + srcptr += SIZEOF_HSWORD; + } + + if (limb_cnt_rem) // partial limb + r[limb_cnt] = integer_gmp_mpn_import1(srcptr, 0, limb_cnt_rem, 0); + } +} + +/* Scan for first non-zero byte starting at srcptr[srcofs], ending at + * srcptr[srcofs+srclen-1]; + * + * If no non-zero byte found, returns srcofs+srclen; otherwise returns + * index of srcptr where first non-zero byte was found. + */ +HsWord +integer_gmp_scan_nzbyte(const uint8_t *srcptr, + const HsWord srcofs, const HsWord srclen) +{ + // TODO: consider implementing this function in Haskell-land + srcptr += srcofs; + + for (unsigned i = 0; i < srclen; ++i) + if (srcptr[i]) + return srcofs+i; + + return srcofs+srclen; +} + +/* Reverse scan for non-zero byte + * starting at srcptr[srcofs+srclen-1], ending at srcptr[srcofs]. + * + * Returns new length srclen1 such that srcptr[srcofs+i] == 0 for + * srclen1 <= i < srclen. + */ +HsWord +integer_gmp_rscan_nzbyte(const uint8_t *srcptr, + const HsWord srcofs, const HsWord srclen) +{ + // TODO: consider implementing this function in Haskell-land + srcptr += srcofs; + + for (unsigned i = srclen; i > 0; --i) + if (srcptr[i-1]) + return i; + + return 0; +} diff --git a/libraries/integer-gmp2/src/GHC/Integer/GMP/Internals.hs b/libraries/integer-gmp2/src/GHC/Integer/GMP/Internals.hs index d119adb9f8..1f9c4b7ec8 100644 --- a/libraries/integer-gmp2/src/GHC/Integer/GMP/Internals.hs +++ b/libraries/integer-gmp2/src/GHC/Integer/GMP/Internals.hs @@ -118,9 +118,163 @@ module GHC.Integer.GMP.Internals -- * Miscellaneous GMP-provided operations , gcdInt + -- * Import/export functions + -- ** Compute size of serialisation + , sizeInBaseBigNat + , sizeInBaseInteger + , sizeInBaseWord# + + -- ** Export + , exportBigNatToAddr + , exportIntegerToAddr + , exportWordToAddr + + , exportBigNatToMutableByteArray + , exportIntegerToMutableByteArray + , exportWordToMutableByteArray + + -- ** Import + + , importBigNatFromAddr + , importIntegerFromAddr + + , importBigNatFromByteArray + , importIntegerFromByteArray ) where import GHC.Integer.Type import GHC.Integer +import GHC.Prim +import GHC.Types default () + + +-- | Compute number of digits (without sign) in given @/base/@. +-- +-- This function wraps @mpz_sizeinbase()@ which has some +-- implementation pecularities to take into account: +-- +-- * \"@'sizeInBaseInteger' 0 /base/ = 1@\" +-- (see also comment in 'exportIntegerToMutableByteArray'). +-- +-- * This function is only defined if @/base/ >= 2#@ and @/base/ <= 256#@ +-- (Note: the documentation claims that only @/base/ <= 62#@ is +-- supported, however the actual implementation supports up to base 256). +-- +-- * If @/base/@ is a power of 2, the result will be exact. In other +-- cases (e.g. for @/base/ = 10#@), the result /may/ be 1 digit too large +-- sometimes. +-- +-- * \"@'sizeInBaseInteger' /i/ 2#@\" can be used to determine the most +-- significant bit of @/i/@. +-- +-- /Since: 0.5.1.0/ +sizeInBaseInteger :: Integer -> Int# -> Word# +sizeInBaseInteger (S# i#) = sizeInBaseWord# (int2Word# (absI# i#)) +sizeInBaseInteger (Jp# bn) = sizeInBaseBigNat bn +sizeInBaseInteger (Jn# bn) = sizeInBaseBigNat bn + +-- | Version of 'sizeInBaseInteger' operating on 'BigNat' +-- +-- /Since: 1.0.0.0/ +sizeInBaseBigNat :: BigNat -> Int# -> Word# +sizeInBaseBigNat bn@(BN# ba#) = c_mpn_sizeinbase# ba# (sizeofBigNat# bn) + +foreign import ccall unsafe "integer_gmp_mpn_sizeinbase" + c_mpn_sizeinbase# :: ByteArray# -> GmpSize# -> Int# -> Word# + +-- | Version of 'sizeInBaseInteger' operating on 'Word#' +-- +-- /Since: 1.0.0.0/ +foreign import ccall unsafe "integer_gmp_mpn_sizeinbase1" + sizeInBaseWord# :: Word# -> Int# -> Word# + +-- | Dump 'Integer' (without sign) to @/addr/@ in base-256 representation. +-- +-- @'exportIntegerToAddr' /i/ /addr/ /e/@ +-- +-- See description of 'exportIntegerToMutableByteArray' for more details. +-- +-- /Since: 1.0.0.0/ +exportIntegerToAddr :: Integer -> Addr# -> Int# -> IO Word +exportIntegerToAddr (S# i#) = exportWordToAddr (W# (int2Word# (absI# i#))) +exportIntegerToAddr (Jp# bn) = exportBigNatToAddr bn +exportIntegerToAddr (Jn# bn) = exportBigNatToAddr bn + +-- | Version of 'exportIntegerToAddr' operating on 'BigNat's. +exportBigNatToAddr :: BigNat -> Addr# -> Int# -> IO Word +exportBigNatToAddr bn@(BN# ba#) addr e + = c_mpn_exportToAddr# ba# (sizeofBigNat# bn) addr 0# e + +foreign import ccall unsafe "integer_gmp_mpn_export" + c_mpn_exportToAddr# :: ByteArray# -> GmpSize# -> Addr# -> Int# -> Int# + -> IO Word + +-- | Version of 'exportIntegerToAddr' operating on 'Word's. +exportWordToAddr :: Word -> Addr# -> Int# -> IO Word +exportWordToAddr (W# w#) addr + = c_mpn_export1ToAddr# w# addr 0# -- TODO: we don't calling GMP for that + +foreign import ccall unsafe "integer_gmp_mpn_export1" + c_mpn_export1ToAddr# :: GmpLimb# -> Addr# -> Int# -> Int# + -> IO Word + +-- | Dump 'Integer' (without sign) to mutable byte-array in base-256 +-- representation. +-- +-- The call +-- +-- @'exportIntegerToMutableByteArray' /i/ /mba/ /offset/ /msbf/@ +-- +-- writes +-- +-- * the 'Integer' @/i/@ +-- +-- * into the 'MutableByteArray#' @/mba/@ starting at @/offset/@ +-- +-- * with most significant byte first if @msbf@ is @1#@ or least +-- significant byte first if @msbf@ is @0#@, and +-- +-- * returns number of bytes written. +-- +-- Use \"@'sizeInBaseInteger' /i/ 256#@\" to compute the exact number of +-- bytes written in advance for @/i/ /= 0@. In case of @/i/ == 0@, +-- 'exportIntegerToMutableByteArray' will write and report zero bytes +-- written, whereas 'sizeInBaseInteger' report one byte. +-- +-- It's recommended to avoid calling 'exportIntegerToMutableByteArray' for small +-- integers as this function would currently convert those to big +-- integers in msbf to call @mpz_export()@. +-- +-- /Since: 1.0.0.0/ +exportIntegerToMutableByteArray :: Integer -> MutableByteArray# RealWorld + -> Word# -> Int# -> IO Word +exportIntegerToMutableByteArray (S# i#) + = exportWordToMutableByteArray (W# (int2Word# (absI# i#))) +exportIntegerToMutableByteArray (Jp# bn) = exportBigNatToMutableByteArray bn +exportIntegerToMutableByteArray (Jn# bn) = exportBigNatToMutableByteArray bn + +-- | Version of 'exportIntegerToMutableByteArray' operating on 'BigNat's. +-- +-- /Since: 1.0.0.0/ +exportBigNatToMutableByteArray :: BigNat -> MutableByteArray# RealWorld -> Word# + -> Int# -> IO Word +exportBigNatToMutableByteArray bn@(BN# ba#) + = c_mpn_exportToMutableByteArray# ba# (sizeofBigNat# bn) + +foreign import ccall unsafe "integer_gmp_mpn_export" + c_mpn_exportToMutableByteArray# :: ByteArray# -> GmpSize# + -> MutableByteArray# RealWorld -> Word# + -> Int# -> IO Word + +-- | Version of 'exportIntegerToMutableByteArray' operating on 'Word's. +-- +-- /Since: 1.0.0.0/ +exportWordToMutableByteArray :: Word -> MutableByteArray# RealWorld -> Word# + -> Int# -> IO Word +exportWordToMutableByteArray (W# w#) = c_mpn_export1ToMutableByteArray# w# + +foreign import ccall unsafe "integer_gmp_mpn_export1" + c_mpn_export1ToMutableByteArray# :: GmpLimb# -> MutableByteArray# RealWorld + -> Word# -> Int# -> IO Word diff --git a/libraries/integer-gmp2/src/GHC/Integer/Type.hs b/libraries/integer-gmp2/src/GHC/Integer/Type.hs index a143160b6b..a36d756aea 100644 --- a/libraries/integer-gmp2/src/GHC/Integer/Type.hs +++ b/libraries/integer-gmp2/src/GHC/Integer/Type.hs @@ -1559,6 +1559,105 @@ byteArrayToBigNat# ba# n0# | isTrue# (neWord# (indexWordArray# ba# i#) 0##) = i# +# 1# | True = fmssl (i# -# 1#) +-- | Read 'Integer' (without sign) from memory location at @/addr/@ in +-- base-256 representation. +-- +-- @'importIntegerFromAddr' /addr/ /size/ /msbf/@ +-- +-- See description of 'importIntegerFromByteArray' for more details. +-- +-- /Since: 1.0.0.0/ +importIntegerFromAddr :: Addr# -> Word# -> Int# -> IO Integer +importIntegerFromAddr addr len msbf = IO $ do + bn <- liftIO (importBigNatFromAddr addr len msbf) + return (bigNatToInteger bn) + +-- | Version of 'importIntegerFromAddr' constructing a 'BigNat' +importBigNatFromAddr :: Addr# -> Word# -> Int# -> IO BigNat +importBigNatFromAddr _ 0## _ = IO (\s -> (# s, zeroBigNat #)) +importBigNatFromAddr addr len0 1# = IO $ do -- MSBF + W# ofs <- liftIO (c_scan_nzbyte_addr addr 0## len0) + let len = len0 `minusWord#` ofs + addr' = addr `plusAddr#` (word2Int# ofs) + importBigNatFromAddr# addr' len 1# +importBigNatFromAddr addr len0 _ = IO $ do -- LSBF + W# len <- liftIO (c_rscan_nzbyte_addr addr 0## len0) + importBigNatFromAddr# addr len 0# + +foreign import ccall unsafe "integer_gmp_scan_nzbyte" + c_scan_nzbyte_addr :: Addr# -> Word# -> Word# -> IO Word + +foreign import ccall unsafe "integer_gmp_rscan_nzbyte" + c_rscan_nzbyte_addr :: Addr# -> Word# -> Word# -> IO Word + +-- | Helper for 'importBigNatFromAddr' +importBigNatFromAddr# :: Addr# -> Word# -> Int# -> S RealWorld BigNat +importBigNatFromAddr# _ 0## _ = return zeroBigNat +importBigNatFromAddr# addr len msbf = do + mbn@(MBN# mba#) <- newBigNat# n# + () <- liftIO (c_mpn_import_addr mba# addr 0## len msbf) + unsafeFreezeBigNat# mbn + where + -- n = ceiling(len / SIZEOF_HSWORD), i.e. number of limbs required + n# = (word2Int# len +# (SIZEOF_HSWORD# -# 1#)) `quotInt#` SIZEOF_HSWORD# + +foreign import ccall unsafe "integer_gmp_mpn_import" + c_mpn_import_addr :: MutableByteArray# RealWorld -> Addr# -> Word# -> Word# + -> Int# -> IO () + +-- | Read 'Integer' (without sign) from byte-array in base-256 representation. +-- +-- The call +-- +-- @'importIntegerFromByteArray' /ba/ /offset/ /size/ /msbf/@ +-- +-- reads +-- +-- * @/size/@ bytes from the 'ByteArray#' @/ba/@ starting at @/offset/@ +-- +-- * with most significant byte first if @/msbf/@ is @1#@ or least +-- significant byte first if @/msbf/@ is @0#@, and +-- +-- * returns a new 'Integer' +-- +-- /Since: 1.0.0.0/ +importIntegerFromByteArray :: ByteArray# -> Word# -> Word# -> Int# -> Integer +importIntegerFromByteArray ba ofs len msbf + = bigNatToInteger (importBigNatFromByteArray ba ofs len msbf) + +-- | Version of 'importIntegerFromByteArray' constructing a 'BigNat' +importBigNatFromByteArray :: ByteArray# -> Word# -> Word# -> Int# -> BigNat +importBigNatFromByteArray _ _ 0## _ = zeroBigNat +importBigNatFromByteArray ba ofs0 len0 1# = runS $ do -- MSBF + W# ofs <- liftIO (c_scan_nzbyte_bytearray ba ofs0 len0) + let len = (len0 `plusWord#` ofs0) `minusWord#` ofs + importBigNatFromByteArray# ba ofs len 1# +importBigNatFromByteArray ba ofs len0 _ = runS $ do -- LSBF + W# len <- liftIO (c_rscan_nzbyte_bytearray ba ofs len0) + importBigNatFromByteArray# ba ofs len 0# + +foreign import ccall unsafe "integer_gmp_scan_nzbyte" + c_scan_nzbyte_bytearray :: ByteArray# -> Word# -> Word# -> IO Word + +foreign import ccall unsafe "integer_gmp_rscan_nzbyte" + c_rscan_nzbyte_bytearray :: ByteArray# -> Word# -> Word# -> IO Word + +-- | Helper for 'importBigNatFromByteArray' +importBigNatFromByteArray# :: ByteArray# -> Word# -> Word# -> Int# + -> S RealWorld BigNat +importBigNatFromByteArray# _ _ 0## _ = return zeroBigNat +importBigNatFromByteArray# ba ofs len msbf = do + mbn@(MBN# mba#) <- newBigNat# n# + () <- liftIO (c_mpn_import_bytearray mba# ba ofs len msbf) + unsafeFreezeBigNat# mbn + where + -- n = ceiling(len / SIZEOF_HSWORD), i.e. number of limbs required + n# = (word2Int# len +# (SIZEOF_HSWORD# -# 1#)) `quotInt#` SIZEOF_HSWORD# + +foreign import ccall unsafe "integer_gmp_mpn_import" + c_mpn_import_bytearray :: MutableByteArray# RealWorld -> ByteArray# -> Word# + -> Word# -> Int# -> IO () + -- | Test whether all internal invariants are satisfied by 'BigNat' value -- -- Returns @1#@ if valid, @0#@ otherwise. diff --git a/testsuite/tests/lib/integer/integerGmpInternals.hs b/testsuite/tests/lib/integer/integerGmpInternals.hs index 3abb14031a..c709a22cee 100644 --- a/testsuite/tests/lib/integer/integerGmpInternals.hs +++ b/testsuite/tests/lib/integer/integerGmpInternals.hs @@ -9,7 +9,7 @@ import Control.Monad import GHC.Word import GHC.Base -import GHC.Integer.GMP.Internals (Integer(S#,J#)) +import GHC.Integer.GMP.Internals (Integer(S#,Jp#,Jn#)) import qualified GHC.Integer.GMP.Internals as I gcdExtInteger :: Integer -> Integer -> (Integer, Integer) @@ -19,18 +19,16 @@ powInteger :: Integer -> Word -> Integer powInteger b (W# w#) = I.powInteger b w# exportInteger :: Integer -> MutableByteArray# RealWorld -> Word# -> Int# -> IO Word -exportInteger i mba o e = IO $ \s -> case I.exportIntegerToMutableByteArray i mba o e s of - (# s', l #) -> (# s', W# l #) +exportInteger = I.exportIntegerToMutableByteArray exportIntegerAddr :: Integer -> Addr# -> Int# -> IO Word -exportIntegerAddr i a e = IO $ \s -> case I.exportIntegerToAddr i a e s of - (# s', l #) -> (# s', W# l #) +exportIntegerAddr = I.exportIntegerToAddr +importInteger :: ByteArray# -> Word# -> Word# -> Int# -> Integer importInteger = I.importIntegerFromByteArray importIntegerAddr :: Addr# -> Word# -> Int# -> IO Integer -importIntegerAddr a l e = IO $ \s -> case I.importIntegerFromAddr a l e s of - (# s', i #) -> (# s', i #) +importIntegerAddr a l e = I.importIntegerFromAddr a l e {- Reference implementation for 'powModInteger' @@ -109,41 +107,41 @@ main = do let a = byteArrayContents# (unsafeCoerce# mba) print =<< importIntegerAddr a 0## 1# - print =<< importIntegerAddr a 0## -1# + print =<< importIntegerAddr a 0## 0# - print =<< importIntegerAddr (plusAddr# a 22#) 1## 1# - print =<< importIntegerAddr (plusAddr# a 97#) 1## -1# + print =<< importIntegerAddr (plusAddr# a 22#) 1## 1# + print =<< importIntegerAddr (plusAddr# a 97#) 1## 0# print =<< importIntegerAddr a 23## 1# - print =<< importIntegerAddr a 23## -1# + print =<< importIntegerAddr a 23## 0# -- no-op print =<< exportIntegerAddr 0 (plusAddr# a 0#) 1# -- write into array - print =<< exportIntegerAddr b (plusAddr# a 5#) 1# - print =<< exportIntegerAddr e (plusAddr# a 50#) -1# + print =<< exportIntegerAddr b (plusAddr# a 5#) 1# + print =<< exportIntegerAddr e (plusAddr# a 50#) 0# print =<< exportInteger m mba 85## 1# - print =<< exportInteger m mba 105## -1# + print =<< exportInteger m mba 105## 0# print =<< importIntegerAddr (plusAddr# a 85#) 17## 1# - print =<< importIntegerAddr (plusAddr# a 105#) 17## -1# + print =<< importIntegerAddr (plusAddr# a 105#) 17## 0# -- read back full array print =<< importIntegerAddr a 128## 1# - print =<< importIntegerAddr a 128## -1# + print =<< importIntegerAddr a 128## 0# freezeByteArray mba print $ importInteger ba 0## 0## 1# - print $ importInteger ba 0## 0## -1# + print $ importInteger ba 0## 0## 0# print $ importInteger ba 5## 29## 1# - print $ importInteger ba 50## 29## -1# + print $ importInteger ba 50## 29## 0# print $ importInteger ba 0## 128## 1# - print $ importInteger ba 0## 128## -1# + print $ importInteger ba 0## 128## 0# return () where |