{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
{-# LANGUAGE RecordWildCards #-}

--
--  (c) The University of Glasgow 2002-2006
--

-- | Create real byte-code objects from 'ResolvedBCO's.
module GHCi.CreateBCO (createBCOs) where

import GHCi.ResolvedBCO
import GHCi.RemoteTypes
import GHCi.BreakArray
import SizedSeq

import System.IO (fixIO)
import Control.Monad
import Data.Array.Base
import Foreign hiding (newArray)
import GHC.Arr          ( Array(..) )
import GHC.Exts
import GHC.IO
import Control.Exception (throwIO, ErrorCall(..))

createBCOs :: [ResolvedBCO] -> IO [HValueRef]
createBCOs bcos = do
  let n_bcos = length bcos
  hvals <- fixIO $ \hvs -> do
     let arr = listArray (0, n_bcos-1) hvs
     mapM (createBCO arr) bcos
  mapM mkRemoteRef hvals

createBCO :: Array Int HValue -> ResolvedBCO -> IO HValue
createBCO _   ResolvedBCO{..} | resolvedBCOIsLE /= isLittleEndian
  = throwIO (ErrorCall $
        unlines [ "The endianness of the ResolvedBCO does not match"
                , "the systems endianness. Using ghc and iserv in a"
                , "mixed endianness setup is not supported!"
                ])
createBCO arr bco
   = do BCO bco# <- linkBCO' arr bco
        -- Why do we need mkApUpd0 here?  Otherwise top-level
        -- interpreted CAFs don't get updated after evaluation.  A
        -- top-level BCO will evaluate itself and return its value
        -- when entered, but it won't update itself.  Wrapping the BCO
        -- in an AP_UPD thunk will take care of the update for us.
        --
        -- Furthermore:
        --   (a) An AP thunk *must* point directly to a BCO
        --   (b) A zero-arity BCO *must* be wrapped in an AP thunk
        --   (c) An AP is always fully saturated, so we *can't* wrap
        --       non-zero arity BCOs in an AP thunk.
        --
        if (resolvedBCOArity bco > 0)
           then return (HValue (unsafeCoerce# bco#))
           else case mkApUpd0# bco# of { (# final_bco #) ->
                  return (HValue final_bco) }


toWordArray :: UArray Int Word64 -> UArray Int Word
toWordArray = amap fromIntegral

linkBCO' :: Array Int HValue -> ResolvedBCO -> IO BCO
linkBCO' arr ResolvedBCO{..} = do
  let
      ptrs   = ssElts resolvedBCOPtrs
      n_ptrs = sizeSS resolvedBCOPtrs

      !(I# arity#)  = resolvedBCOArity

      !(EmptyArr empty#) = emptyArr -- See Note [BCO empty array]

      barr a = case a of UArray _lo _hi n b -> if n == 0 then empty# else b
      insns_barr = barr resolvedBCOInstrs
      bitmap_barr = barr (toWordArray resolvedBCOBitmap)
      literals_barr = barr (toWordArray resolvedBCOLits)

  PtrsArr marr <- mkPtrsArray arr n_ptrs ptrs
  IO $ \s ->
    case unsafeFreezeArray# marr s of { (# s, arr #) ->
    case newBCO insns_barr literals_barr arr arity# bitmap_barr of { IO io ->
    io s
    }}


-- we recursively link any sub-BCOs while making the ptrs array
mkPtrsArray :: Array Int HValue -> Word -> [ResolvedBCOPtr] -> IO PtrsArr
mkPtrsArray arr n_ptrs ptrs = do
  marr <- newPtrsArray (fromIntegral n_ptrs)
  let
    fill (ResolvedBCORef n) i =
      writePtrsArrayHValue i (arr ! n) marr  -- must be lazy!
    fill (ResolvedBCOPtr r) i = do
      hv <- localRef r
      writePtrsArrayHValue i hv marr
    fill (ResolvedBCOStaticPtr r) i = do
      writePtrsArrayPtr i (fromRemotePtr r)  marr
    fill (ResolvedBCOPtrBCO bco) i = do
      BCO bco# <- linkBCO' arr bco
      writePtrsArrayBCO i bco# marr
    fill (ResolvedBCOPtrBreakArray r) i = do
      BA mba <- localRef r
      writePtrsArrayMBA i mba marr
  zipWithM_ fill ptrs [0..]
  return marr

data PtrsArr = PtrsArr (MutableArray# RealWorld HValue)

newPtrsArray :: Int -> IO PtrsArr
newPtrsArray (I# i) = IO $ \s ->
  case newArray# i undefined s of (# s', arr #) -> (# s', PtrsArr arr #)

writePtrsArrayHValue :: Int -> HValue -> PtrsArr -> IO ()
writePtrsArrayHValue (I# i) hv (PtrsArr arr) = IO $ \s ->
  case writeArray# arr i hv s of s' -> (# s', () #)

writePtrsArrayPtr :: Int -> Ptr a -> PtrsArr -> IO ()
writePtrsArrayPtr (I# i) (Ptr a#) (PtrsArr arr) = IO $ \s ->
  case writeArrayAddr# arr i a# s of s' -> (# s', () #)

-- This is rather delicate: convincing GHC to pass an Addr# as an Any but
-- without making a thunk turns out to be surprisingly tricky.
{-# NOINLINE writeArrayAddr# #-}
writeArrayAddr# :: MutableArray# s a -> Int# -> Addr# -> State# s -> State# s
writeArrayAddr# marr i addr s = unsafeCoerce# writeArray# marr i addr s

writePtrsArrayBCO :: Int -> BCO# -> PtrsArr -> IO ()
writePtrsArrayBCO (I# i) bco (PtrsArr arr) = IO $ \s ->
  case (unsafeCoerce# writeArray#) arr i bco s of s' -> (# s', () #)

data BCO = BCO BCO#

writePtrsArrayMBA :: Int -> MutableByteArray# s -> PtrsArr -> IO ()
writePtrsArrayMBA (I# i) mba (PtrsArr arr) = IO $ \s ->
  case (unsafeCoerce# writeArray#) arr i mba s of s' -> (# s', () #)

newBCO :: ByteArray# -> ByteArray# -> Array# a -> Int# -> ByteArray# -> IO BCO
newBCO instrs lits ptrs arity bitmap = IO $ \s ->
  case newBCO# instrs lits ptrs arity bitmap s of
    (# s1, bco #) -> (# s1, BCO bco #)

{- Note [BCO empty array]

Lots of BCOs have empty ptrs or nptrs, but empty arrays are not free:
they are 2-word heap objects.  So let's make a single empty array and
share it between all BCOs.
-}

data EmptyArr = EmptyArr ByteArray#

{-# NOINLINE emptyArr #-}
emptyArr :: EmptyArr
emptyArr = unsafeDupablePerformIO $ IO $ \s ->
  case newByteArray# 0# s of { (# s, arr #) ->
  case unsafeFreezeByteArray# arr s of { (# s, farr #) ->
  (# s, EmptyArr farr #)
  }}
