packages feed

sbv-14.8: Data/SBV/Compilers/C/Table.hs

-----------------------------------------------------------------------------
-- |
-- Module    : Data.SBV.Compilers.C.Table
-- Copyright : (c) Levent Erkok
-- License   : BSD3
-- Maintainer: erkokl@gmail.com
-- Stability : experimental
--
-- Lowering of finite SBV lookup tables to C arrays.
-----------------------------------------------------------------------------

{-# OPTIONS_GHC -Wall -Werror #-}

module Data.SBV.Compilers.C.Table
  ( tableExpr
  , tableIndexAndBounds
  , tableMustBeLocal
  , tableNeedsBounds
  ) where

import Data.SBV.Compilers.C.Types (isConcreteADT)
import qualified Data.Set as Set

import Text.PrettyPrint.HughesPJ
import qualified Text.PrettyPrint.HughesPJ as P ((<>))

import Data.SBV.Compilers.C.Array    (arrayStoredLoad, arrayStoredValue)
import Data.SBV.Compilers.C.BV       (isWideBV, wideBVLookupIndex, wideBVLookupInRange)
import Data.SBV.Compilers.C.GMP      (isExactGMPKind)
import Data.SBV.Compilers.C.Lowering (CLowering(..), CRequirement(..), expressionLowering)
import Data.SBV.Compilers.C.Value    (valueNeedsOwnership)
import Data.SBV.Compilers.CodeGen    (CgConfig(..))
import Data.SBV.Core.Data
import Data.SBV.Core.Kind             (expandKinds)

-- | Lower a finite SBV table lookup. Bounds are checked when requested by the
-- code-generation configuration (always before wide/exact index narrowing),
-- and the default value is returned for every checked out-of-range index.
-- Only the integral index kinds accepted by SBV's
-- 'Data.SBV.select' operation can reach this function.
tableExpr :: CgConfig -> (SV -> Doc) -> Op -> SV -> Maybe CLowering
tableExpr cfg renderSV (LkUp (tableId, indexKind, _, tableLength) index defaultValue) resultSV
  | isArray resultKind
  = let lowering = arrayStoredLoad resultSV selectedValue
    in Just lowering {loweringRequirements = Set.fromList requirements}
  | True
  = Just $ expressionLowering requirements selectedValue
 where renderedIndex   = renderSV index
       renderedDefault = arrayStoredValue resultKind (renderSV defaultValue)
       lookupValue     = text "table" P.<> int tableId P.<> brackets nativeIndex
       selectedValue   = case outOfRange of
                           Just check | tableNeedsBounds cfg indexKind -> check <+> text "?" <+> renderedDefault <+> text ":" <+> lookupValue
                           _                      -> lookupValue

       resultKind = kindOf resultSV

       requirements =  [CRequiresGMP    | any (isExactGMPKind cfg) touchedKinds]
                    ++ [CRequiresWideBV | any isWideBV touchedKinds]
                    ++ [CRequiresLibBF  | any isFP touchedKinds]
                    ++ [CRequiresLibM   | any isFP touchedKinds]
                    ++ [CRequiresText   | any (`elem` [KChar, KString]) touchedKinds]
                    ++ [CRequiresLists  | any isList touchedKinds]
                    ++ [CRequiresSets   | any isSet touchedKinds]
                    ++ [CRequiresArrays | any isArray touchedKinds]

       touchedKinds = concatMap expandKinds [indexKind, resultKind]

       (nativeIndex, outOfRange) = tableIndexAndBounds cfg indexKind tableLength renderedIndex
tableExpr _ _ _ _ = Nothing

-- | Representation-changing index conversions must preserve out-of-range
-- values even when optional native-index checks are disabled.
tableNeedsBounds :: CgConfig -> Kind -> Bool
tableNeedsBounds cfg kind = cgRTC cfg || isWideBV kind || isExactGMPKind cfg kind

-- | Render a machine index together with its exact out-of-range predicate.
-- Check the original value before narrowing a wide bit-vector or GMP integer;
-- otherwise a large index could alias an in-range table entry.
tableIndexAndBounds :: CgConfig -> Kind -> Int -> Doc -> (Doc, Maybe Doc)
tableIndexAndBounds cfg indexKind tableLength renderedIndex = (nativeIndex, outOfRange)
 where nativeIndex
         | isWideBV indexKind              = wideBVLookupIndex indexKind renderedIndex
         | isExactGMPKind cfg indexKind     = namedCall "mpz_get_ui" [renderedIndex]
         | True                             = renderedIndex

       outOfRange
         | isWideBV indexKind
         = Just $ text "!" P.<> parens (wideBVLookupInRange indexKind tableLength renderedIndex)
         | isExactGMPKind cfg indexKind
         = Just $ text "!" P.<> parens (   namedCall "mpz_sgn" [renderedIndex] <+> text ">= 0"
                                        <+> text "&&"
                                        <+> namedCall "mpz_cmp_ui" [renderedIndex, int tableLength] <+> text "< 0"
                                      )
         | KBool <- indexKind
         = if maximumIndex indexKind >= fromIntegral tableLength
           then Just $ renderedIndex <+> text ">=" <+> int tableLength
           else Nothing
         | isBounded indexKind
         = case (hasSign indexKind, maximumIndex indexKind >= fromIntegral tableLength) of
             (True,  True)  -> Just . parens $ renderedIndex <+> text "< 0"
                                             <+> text "||"
                                             <+> renderedIndex <+> text ">=" <+> int tableLength
             (True,  False) -> Just $ renderedIndex <+> text "< 0"
             (False, True)  -> Just $ renderedIndex <+> text ">=" <+> int tableLength
             (False, False) -> Nothing
         | KUnbounded <- indexKind
         = case cgInteger cfg of
             Nothing    -> error "SBV->C: Internal error: exact SInteger table index escaped GMP lowering."
             Just width -> case maximumSignedIndex width >= fromIntegral tableLength of
                             True  -> Just . parens $ renderedIndex <+> text "< 0"
                                                     <+> text "||"
                                                     <+> renderedIndex <+> text ">=" <+> int tableLength
                             False -> Just $ renderedIndex <+> text "< 0"
         | True
         = error $ "SBV->C: Unsupported table index kind: " ++ show indexKind

       maximumIndex :: Kind -> Integer
       maximumIndex KBool = 1
       maximumIndex kind
         | hasSign kind = maximumSignedIndex (intSizeOf kind)
         | True         = 2 ^ intSizeOf kind - 1

       maximumSignedIndex :: Int -> Integer
       maximumSignedIndex width = 2 ^ (width - 1) - 1

       namedCall functionName args = text functionName P.<> parens (fsep (punctuate comma args))

-- | Test whether a table declaration must be emitted inside the generated
-- function. Exact GMP values use the function's ownership arena, while
-- managed descriptors point at compound-literal backing storage that is not
-- a portable static C initializer. ADTs are conservatively local because
-- their concrete ownership graph is resolved by the ADT lowering phase.
tableMustBeLocal :: CgConfig -> Kind -> Bool
tableMustBeLocal cfg = any mustBeLocal . expandKinds
 where mustBeLocal kind = isExactGMPKind cfg kind || valueNeedsOwnership cfg kind || isConcreteADT kind