
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiWayIf #-}

-- | PrimOp's Ids
module GHC.Builtin.PrimOps.Ids
  ( primOpId
  , allThePrimOpIds
  )
where

import GHC.Prelude

-- primop rules are attached to primop ids
import {-# SOURCE #-} GHC.Core.Opt.ConstantFold (primOpRules)
import GHC.Core.TyCo.Rep ( scaledThing )
import GHC.Core.Type
import GHC.Core.FVs (mkRuleInfo)

import GHC.Builtin.PrimOps
import GHC.Builtin.Uniques
import GHC.Builtin.Names
import GHC.Builtin.Types.Prim

import GHC.Types.Basic
import GHC.Types.Cpr
import GHC.Types.Demand
import GHC.Types.Id
import GHC.Types.Id.Info
import GHC.Types.TyThing
import GHC.Types.Name
import GHC.Types.Name.Env
import GHC.Types.Var
import GHC.Types.Var.Set

import GHC.Tc.Types.Origin
import GHC.Tc.Utils.TcType ( ConcreteTvOrigin(..), ConcreteTyVars, TcType )

import GHC.Data.SmallArray

import Data.Maybe ( mapMaybe, listToMaybe, catMaybes, maybeToList )


-- | Build a PrimOp Id
mkPrimOpId :: PrimOp -> Id
mkPrimOpId prim_op
  = id
  where
    (tyvars,arg_tys,res_ty, arity, strict_sig) = primOpSig prim_op
    ty   = mkForAllTys tyvars (mkVisFunTysMany arg_tys res_ty)
    name = mkWiredInName gHC_PRIM (primOpOcc prim_op)
                         (mkPrimOpIdUnique (primOpTag prim_op))
                         (AnId id) UserSyntax
    id   = mkGlobalId (PrimOpId prim_op conc_tvs) name ty info

    conc_tvs = computePrimOpConcTyVarsFromType name tyvars arg_tys res_ty

    -- PrimOps don't ever construct a product, but we want to preserve bottoms
    cpr
      | isDeadEndDiv (snd (splitDmdSig strict_sig)) = botCpr
      | otherwise                                   = topCpr

    info = noCafIdInfo
           `setRuleInfo`           mkRuleInfo (maybeToList $ primOpRules name prim_op)
           `setArityInfo`          arity
           `setDmdSigInfo`         strict_sig
           `setCprSigInfo`         mkCprSig arity cpr
           `setInlinePragInfo`     neverInlinePragma
               -- We give PrimOps a NOINLINE pragma so that we don't
               -- get silly warnings from Desugar.dsRule (the inline_shadows_rule
               -- test) about a RULE conflicting with a possible inlining
               -- cf #7287

-- | Analyse the type of a primop to determine which of its outermost forall'd
-- type variables must be instantiated to concrete types when the primop is
-- instantiated.
--
-- These are the Levity and RuntimeRep kinded type-variables which appear in
-- negative position in the type of the primop.
computePrimOpConcTyVarsFromType :: Name -> [TyVarBinder] -> [Type] -> Type -> ConcreteTyVars
computePrimOpConcTyVarsFromType nm tyvars arg_tys _res_ty = mkNameEnv concs
  where
    concs = [ (tyVarName kind_tv, ConcreteFRR frr_orig)
            | Bndr tv _af <- tyvars
            , kind_tv    <- tyCoVarsOfTypeWellScoped $ tyVarKind tv
            , neg_pos    <- maybeToList $ frr_tyvar_maybe kind_tv
            , let frr_orig = FixedRuntimeRepOrigin
                           { frr_type    = mkTyVarTy tv
                           , frr_context = FRRRepPolyId nm RepPolyPrimOp neg_pos
                           }
            ]

    -- As per Note [Levity and representation polymorphic primops]
    -- in GHC.Builtin.Primops.txt.pp, we compute the ConcreteTyVars associated
    -- to a primop by inspecting the type variable names.
    frr_tyvar_maybe tv
      | tv `elem` [ runtimeRep1TyVar, runtimeRep2TyVar, runtimeRep3TyVar
                  , levity1TyVar, levity2TyVar ]
      = listToMaybe $
          mapMaybe (\ (i,arg) -> Argument i <$> positiveKindPos_maybe tv arg)
            (zip [1..] arg_tys)
      | otherwise
      = Nothing
      -- Compute whether the type variable occurs in the kind of a type variable
      -- in positive position in one of the argument types of the primop.

-- | Does this type variable appear in a kind in a negative position in the
-- type?
--
-- Returns the first such position if so.
--
-- NB: assumes the type is of a simple form, e.g. no foralls, no function
-- arrows nested in a TyCon other than a function arrow.
-- Just used to compute the set of ConcreteTyVars for a PrimOp by inspecting
-- its type, see 'computePrimOpConcTyVarsFromType'.
negativeKindPos_maybe :: TcTyVar -> TcType -> Maybe (Position Neg)
negativeKindPos_maybe tv ty
  | (args, res) <- splitFunTys ty
  = listToMaybe $ catMaybes $
      ( (if null args then Nothing else Result <$> negativeKindPos_maybe tv res)
      : map recur (zip [1..] args)
      )
  where
    recur (pos, scaled_ty)
      = Argument pos <$> positiveKindPos_maybe tv (scaledThing scaled_ty)
    -- (assumes we don't have any function types nested inside other types)

-- | Does this type variable appear in a kind in a positive position in the
-- type?
--
-- Returns the first such position if so.
--
-- NB: assumes the type is of a simple form, e.g. no foralls, no function
-- arrows nested in a TyCon other than a function arrow.
-- Just used to compute the set of ConcreteTyVars for a PrimOp by inspecting
-- its type, see 'computePrimOpConcTyVarsFromType'.
positiveKindPos_maybe :: TcTyVar -> TcType -> Maybe (Position Pos)
positiveKindPos_maybe tv ty
  | (args, res) <- splitFunTys ty
  = listToMaybe $ catMaybes $
      ( (if null args then finish res else Result <$> positiveKindPos_maybe tv res)
      : map recur (zip [1..] args)
      )
  where
    recur (pos, scaled_ty)
      = Argument pos <$> negativeKindPos_maybe tv (scaledThing scaled_ty)
    -- (assumes we don't have any function types nested inside other types)
    finish ty
      | tv `elemVarSet` tyCoVarsOfType (typeKind ty)
      = Just Top
      | otherwise
      = Nothing

-------------------------------------------------------------
-- Cache of PrimOp's Ids
-------------------------------------------------------------

-- | A cache of the PrimOp Ids, indexed by PrimOp tag (0 indexed)
primOpIds :: SmallArray Id
{-# NOINLINE primOpIds #-}
primOpIds = listToArray (maxPrimOpTag+1) primOpTag mkPrimOpId allThePrimOps

-- | Get primop id.
--
-- Retrieve it from `primOpIds` cache.
primOpId :: PrimOp -> Id
{-# INLINE primOpId #-}
primOpId op = indexSmallArray primOpIds (primOpTag op)

-- | All the primop ids, as a list
allThePrimOpIds :: [Id]
{-# INLINE allThePrimOpIds #-}
allThePrimOpIds = map (indexSmallArray primOpIds) [0..maxPrimOpTag]
