{-# LANGUAGE CPP, MagicHash #-}
{-# OPTIONS_GHC -optc-DNON_POSIX_SOURCE #-}
--
--  (c) The University of Glasgow 2002-2006
--

-- | ByteCodeItbls: Generate infotables for interpreter-made bytecodes
module ETA.Interactive.ByteCodeItbls ( ItblEnv, ItblPtr(..), itblCode, mkITbls, peekItbl
                     , StgInfoTable(..)
                     ) where

import ETA.Main.DynFlags
import ETA.Utils.Panic
import ETA.Utils.Platform
import ETA.BasicTypes.Name             ( Name, getName )
import ETA.BasicTypes.NameEnv
import ETA.BasicTypes.DataCon          ( DataCon, dataConIdentity )
import ETA.Types.TyCon            ( TyCon, tyConFamilySize, isDataTyCon, tyConDataCons )
-- import ETA.Types.Type             ( flattenRepType, repType, typePrimRep )
import ETA.Utils.Util

import Control.Monad
import Control.Monad.Trans.Class
import Control.Monad.Trans.State.Strict
import Data.Maybe
import Foreign
import Foreign.C

import GHC.Exts         ( Int(I#), addr2Int# )
import GHC.Ptr          ( FunPtr(..) )

{-
  Manufacturing of info tables for DataCons
-}

newtype ItblPtr = ItblPtr (Ptr ()) deriving Show

itblCode :: DynFlags -> ItblPtr -> Ptr ()
itblCode dflags (ItblPtr ptr)
 | ghciTablesNextToCode = castPtr ptr `plusPtr` conInfoTableSizeB dflags
 | otherwise            = castPtr ptr

-- XXX bogus
conInfoTableSizeB :: DynFlags -> Int
conInfoTableSizeB _ = 12

type ItblEnv = NameEnv (Name, ItblPtr)
        -- We need the Name in the range so we know which
        -- elements to filter out when unloading a module

mkItblEnv :: [(Name,ItblPtr)] -> ItblEnv
mkItblEnv pairs = mkNameEnv [(n, (n,p)) | (n,p) <- pairs]


-- Make info tables for the data decls in this module
mkITbls :: DynFlags -> [TyCon] -> IO ItblEnv
mkITbls _ [] = return emptyNameEnv
mkITbls dflags (tc:tcs) = do itbls  <- mkITbl dflags tc
                             itbls2 <- mkITbls dflags tcs
                             return (itbls `plusNameEnv` itbls2)

mkITbl :: DynFlags -> TyCon -> IO ItblEnv
mkITbl dflags tc
   | not (isDataTyCon tc)
   = return emptyNameEnv
   | dcs `lengthIs` n -- paranoia; this is an assertion.
   = make_constr_itbls dflags dcs
     where
        dcs = tyConDataCons tc
        n   = tyConFamilySize tc

mkITbl _ _ = error "Unmatched patter in mkITbl: assertion failed!"

cONSTR :: Int
cONSTR = 1

-- Assumes constructors are numbered from zero, not one
make_constr_itbls :: DynFlags -> [DataCon] -> IO ItblEnv
make_constr_itbls dflags cons
   = do is <- mapM mk_dirret_itbl (zip cons [0..])
        return (mkItblEnv is)
     where
        mk_dirret_itbl (dcon, conNo)
           = mk_itbl dcon conNo stg_interp_constr_entry

        mk_itbl :: DataCon -> Int -> EntryFunPtr -> IO (Name,ItblPtr)
        mk_itbl dcon conNo entry_addr = do
           let --rep_args = [ (typePrimRep rep_arg,rep_arg) | arg <- dataConRepArgTys dcon, rep_arg <- flattenRepType (repType arg) ]
               (_, ptr_wds, _) = error "mkVirtHeapOffsets: unimplemented"
               --mkVirtHeapOffsets dflags False{-not a THUNK-} rep_args
               ptrs'  = ptr_wds
              -- nptrs' = tot_wds - ptr_wds
               nptrs_really = error "unimplemented nptrs_really"
               -- nptrs_really
               --    | ptrs' + nptrs' >= mIN_PAYLOAD_SIZE dflags = nptrs'
               --    | otherwise = mIN_PAYLOAD_SIZE dflags - ptrs'
               code' = mkJumpToAddr dflags entry_addr
               itbl  = StgInfoTable {
                           entry = if ghciTablesNextToCode
                                   then Nothing
                                   else Just entry_addr,
                           ptrs  = ptrs',
                           nptrs = nptrs_really,
                           tipe  = fromIntegral cONSTR,
                           srtlen = fromIntegral conNo,
                           code  = if ghciTablesNextToCode
                                   then Just code'
                                   else Nothing
                        }

               -- Make a piece of code to jump to "entry_label".
               -- This is the only arch-dependent bit.
           addrCon <- newExecConItbl dflags itbl (dataConIdentity dcon)
                    --putStrLn ("SIZE of itbl is " ++ show (sizeOf itbl))
                    --putStrLn ("# ptrs  of itbl is " ++ show ptrs)
                    --putStrLn ("# nptrs of itbl is " ++ show nptrs_really)
           return (getName dcon, ItblPtr (castFunPtrToPtr addrCon))


-- Make code which causes a jump to the given address.  This is the
-- only arch-dependent bit of the itbl story.
type ItblCodes = Either [Word8] [Word32]

funPtrToInt :: FunPtr a -> Int
funPtrToInt (FunPtr a#) = I# (addr2Int# a#)

mkJumpToAddr :: DynFlags -> EntryFunPtr -> ItblCodes
mkJumpToAddr dflags a = case platformArch (targetPlatform dflags) of
    ArchSPARC ->
        -- After some consideration, we'll try this, where
        -- 0x55555555 stands in for the address to jump to.
        -- According to includes/rts/MachRegs.h, %g3 is very
        -- likely indeed to be baggable.
        --
        --   0000 07155555              sethi   %hi(0x55555555), %g3
        --   0004 8610E155              or      %g3, %lo(0x55555555), %g3
        --   0008 81C0C000              jmp     %g3
        --   000c 01000000              nop

        let w32 = fromIntegral (funPtrToInt a)

            hi22, lo10 :: Word32 -> Word32
            lo10 x = x .&. 0x3FF
            hi22 x = (x `shiftR` 10) .&. 0x3FFFF

        in Right [ 0x07000000 .|. hi22 w32,
                   0x8610E000 .|. lo10 w32,
                   0x81C0C000,
                   0x01000000 ]

    ArchPPC ->
        -- We'll use r12, for no particular reason.
        -- 0xDEADBEEF stands for the address:
        -- 3D80DEAD lis r12,0xDEAD
        -- 618CBEEF ori r12,r12,0xBEEF
        -- 7D8903A6 mtctr r12
        -- 4E800420 bctr

        let w32 = fromIntegral (funPtrToInt a)
            hi16 x = (x `shiftR` 16) .&. 0xFFFF
            lo16 x = x .&. 0xFFFF
        in Right [ 0x3D800000 .|. hi16 w32,
                   0x618C0000 .|. lo16 w32,
                   0x7D8903A6, 0x4E800420 ]

    ArchX86 ->
        -- Let the address to jump to be 0xWWXXYYZZ.
        -- Generate   movl $0xWWXXYYZZ,%eax  ;  jmp *%eax
        -- which is
        -- B8 ZZ YY XX WW FF E0

        let w32 = fromIntegral (funPtrToInt a) :: Word32
            insnBytes :: [Word8]
            insnBytes
               = [0xB8, byte0 w32, byte1 w32,
                        byte2 w32, byte3 w32,
                  0xFF, 0xE0]
        in
            Left insnBytes

    ArchX86_64 ->
        -- Generates:
        --      jmpq *.L1(%rip)
        --      .align 8
        -- .L1:
        --      .quad <addr>
        --
        -- which looks like:
        --     8:   ff 25 02 00 00 00     jmpq   *0x2(%rip)      # 10 <f+0x10>
        -- with addr at 10.
        --
        -- We need a full 64-bit pointer (we can't assume the info table is
        -- allocated in low memory).  Assuming the info pointer is aligned to
        -- an 8-byte boundary, the addr will also be aligned.

        let w64 = fromIntegral (funPtrToInt a) :: Word64
            insnBytes :: [Word8]
            insnBytes
               = [0xff, 0x25, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
                  byte0 w64, byte1 w64, byte2 w64, byte3 w64,
                  byte4 w64, byte5 w64, byte6 w64, byte7 w64]
        in
            Left insnBytes

    ArchAlpha ->
        let w64 = fromIntegral (funPtrToInt a) :: Word64
        in Right [ 0xc3800000      -- br   at, .+4
                 , 0xa79c000c      -- ldq  at, 12(at)
                 , 0x6bfc0000      -- jmp  (at)    # with zero hint -- oh well
                 , 0x47ff041f      -- nop
                 , fromIntegral (w64 .&. 0x0000FFFF)
                 , fromIntegral ((w64 `shiftR` 32) .&. 0x0000FFFF) ]

    ArchARM { } ->
        -- Generates Arm sequence,
        --      ldr r1, [pc, #0]
        --      bx r1
        --
        -- which looks like:
        --     00000000 <.addr-0x8>:
        --     0:       00109fe5    ldr    r1, [pc]      ; 8 <.addr>
        --     4:       11ff2fe1    bx     r1
        let w32 = fromIntegral (funPtrToInt a) :: Word32
        in Left [ 0x00, 0x10, 0x9f, 0xe5
                , 0x11, 0xff, 0x2f, 0xe1
                , byte0 w32, byte1 w32, byte2 w32, byte3 w32]

    arch ->
        panic ("mkJumpToAddr not defined for " ++ show arch)

byte0, byte1, byte2, byte3 :: (Integral w, Bits w) => w -> Word8
byte0 w = fromIntegral w
byte1 w = fromIntegral (w `shiftR` 8)
byte2 w = fromIntegral (w `shiftR` 16)
byte3 w = fromIntegral (w `shiftR` 24)
byte4, byte5, byte6, byte7 :: (Integral w, Bits w) => w -> Word8
byte4 w = fromIntegral (w `shiftR` 32)
byte5 w = fromIntegral (w `shiftR` 40)
byte6 w = fromIntegral (w `shiftR` 48)
byte7 w = fromIntegral (w `shiftR` 56)

-- entry point for direct returns for created constr itbls
foreign import ccall "&stg_interp_constr_entry"
    stg_interp_constr_entry :: EntryFunPtr

-- Ultra-minimalist version specially for constructors
type HalfWord = Word16

data StgConInfoTable = StgConInfoTable {
   conDesc   :: Ptr Word8,
   infoTable :: StgInfoTable
}

sizeOfConItbl :: DynFlags -> StgConInfoTable -> Int
sizeOfConItbl dflags conInfoTable
      = sum [ fieldSz conDesc conInfoTable
            , sizeOfItbl dflags (infoTable conInfoTable) ]

pokeConItbl :: DynFlags -> Ptr StgConInfoTable -> Ptr StgConInfoTable
            -> StgConInfoTable
            -> IO ()
pokeConItbl dflags wr_ptr ex_ptr itbl
      = flip evalStateT (castPtr wr_ptr) $ do
           when ghciTablesNextToCode $ do
               let con_desc = conDesc itbl `minusPtr`
                      (ex_ptr `plusPtr` conInfoTableSizeB dflags)
               store (fromIntegral con_desc :: Word32)
           store' (sizeOfItbl dflags) (pokeItbl dflags) (infoTable itbl)
           unless ghciTablesNextToCode $ store (conDesc itbl)

type EntryFunPtr = FunPtr (Ptr () -> IO (Ptr ()))

data StgInfoTable = StgInfoTable {
   entry  :: Maybe EntryFunPtr, -- Just <=> not ghciTablesNextToCode
   ptrs   :: HalfWord,
   nptrs  :: HalfWord,
   tipe   :: HalfWord,
   srtlen :: HalfWord,
   code   :: Maybe ItblCodes -- Just <=> ghciTablesNextToCode
  }

sizeOfItbl :: DynFlags -> StgInfoTable -> Int
sizeOfItbl dflags itbl
      = sum
        [
         if ghciTablesNextToCode then 0 else fieldSz (fromJust . entry) itbl,
         fieldSz ptrs itbl,
         fieldSz nptrs itbl,
         fieldSz tipe itbl,
         fieldSz srtlen itbl,
         if ghciTablesNextToCode then case mkJumpToAddr dflags undefined of
                                      Left  xs -> sizeOf (head xs) * length xs
                                      Right xs -> sizeOf (head xs) * length xs
                                 else 0
        ]

pokeItbl :: DynFlags -> Ptr StgInfoTable -> StgInfoTable -> IO ()
pokeItbl _ a0 itbl
      = flip evalStateT (castPtr a0)
      $ do
           case entry itbl of
               Nothing -> return ()
               Just e  -> store e
           store (ptrs   itbl)
           store (nptrs  itbl)
           store (tipe   itbl)
           store (srtlen itbl)
           case code itbl of
               Nothing -> return ()
               Just (Left  xs) -> mapM_ store xs
               Just (Right xs) -> mapM_ store xs

peekItbl :: DynFlags -> Ptr StgInfoTable -> IO StgInfoTable
peekItbl dflags a0
      = flip evalStateT (castPtr a0)
      $ do
           entry'  <- if ghciTablesNextToCode
                      then return Nothing
                      else liftM Just load
           ptrs'   <- load
           nptrs'  <- load
           tipe'   <- load
           srtlen' <- load
           code'   <- if ghciTablesNextToCode
                      then liftM Just $ case mkJumpToAddr dflags undefined of
                                        Left xs ->
                                            liftM Left $ sequence (replicate (length xs) load)
                                        Right xs ->
                                            liftM Right $ sequence (replicate (length xs) load)
                      else return Nothing
           return
              StgInfoTable {
                 entry  = entry',
                 ptrs   = ptrs',
                 nptrs  = nptrs',
                 tipe   = tipe',
                 srtlen = srtlen'
                ,code   = code'
              }

fieldSz :: Storable b => (a -> b) -> a -> Int
fieldSz sel x = sizeOf (sel x)

type PtrIO = StateT (Ptr Word8) IO

advance :: Storable a => PtrIO (Ptr a)
advance = advance' sizeOf

advance' :: (a -> Int) -> PtrIO (Ptr a)
advance' fSizeOf = state adv
    where adv addr = case castPtr addr of
                     addrCast ->
                         (addrCast,
                          addr `plusPtr` sizeOfPointee fSizeOf addrCast)

sizeOfPointee :: (a -> Int) -> Ptr a -> Int
sizeOfPointee fSizeOf addr = fSizeOf (typeHack addr)
    where typeHack = undefined :: Ptr a -> a

store :: Storable a => a -> PtrIO ()
store = store' sizeOf poke

store' :: (a -> Int) -> (Ptr a -> a -> IO ()) -> a -> PtrIO ()
store' fSizeOf fPoke x = do addr <- advance' fSizeOf
                            lift (fPoke addr x)

load :: Storable a => PtrIO a
load = do addr <- advance
          lift (peek addr)

newExecConItbl :: DynFlags -> StgInfoTable -> [Word8] -> IO (FunPtr ())
newExecConItbl dflags obj con_desc
   = alloca $ \pcode -> do
        let lcon_desc = length con_desc + 1{- null terminator -}
            dummy_cinfo = StgConInfoTable { conDesc = nullPtr, infoTable = obj }
            sz = fromIntegral (sizeOfConItbl dflags dummy_cinfo)
               -- Note: we need to allocate the conDesc string next to the info
               -- table, because on a 64-bit platform we reference this string
               -- with a 32-bit offset relative to the info table, so if we
               -- allocated the string separately it might be out of range.
        wr_ptr <- _allocateExec (sz + fromIntegral lcon_desc) pcode
        ex_ptr <- peek pcode
        let cinfo = StgConInfoTable { conDesc = ex_ptr `plusPtr` fromIntegral sz
                                    , infoTable = obj }
        pokeConItbl dflags wr_ptr ex_ptr cinfo
        pokeArray0 0 (castPtr wr_ptr `plusPtr` fromIntegral sz) con_desc
        _flushExec sz ex_ptr -- Cache flush (if needed)
        return (castPtrToFunPtr ex_ptr)

foreign import ccall unsafe "allocateExec"
  _allocateExec :: CUInt -> Ptr (Ptr a) -> IO (Ptr a)

foreign import ccall unsafe "flushExec"
  _flushExec :: CUInt -> Ptr a -> IO ()
