tala-core-0.1.0: src/Progmasoft/Tala/Core/Optimizer/IntegerFacts.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
{- | Path-sensitive integer facts for Core optimization and effect analysis.
Facts form a small abstract domain. Each known integer has an interval and an
independent "zero is excluded" bit; the latter preserves useful information
for disjoint intervals such as x < 0 || x > 0. The whole environment also has
a bottom element, used when a branch condition is contradictory. Unknown
calls invalidate the environment because Core does not yet carry a complete
mod/ref summary for captured mutable storage.
-}
module Progmasoft.Tala.Core.Optimizer.IntegerFacts
( IntegerFact (..)
, IntegerFacts
, emptyIntegerFacts
, unreachableIntegerFacts
, isUnreachableFacts
, lookupIntegerFact
, forgetIntegerFact
, factOfIntegerExpression
, factProvesNonzero
, transferExpressionFacts
, transferStatementFacts
, transferStatementsFacts
, LoopFactSummary (..)
, loopFactSummary
, refineConditionFacts
, joinIntegerFacts
, conditionTruthFromFacts
, expressionInvokesCallable
) where
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Progmasoft.Tala.AST (ResolvedName, SymbolId, Type, resolvedSymbol)
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.Scalar
( coreIntegerBitWidth
, coreIntegerIsSigned
, integerFitsCoreType
, isCoreIntegerType
)
-- | An inclusive interval plus the useful non-convex fact that zero is absent.
data IntegerFact = IntegerFact
{ integerMinimum :: Maybe Integer
-- ^ Inclusive lower bound, if established.
, integerMaximum :: Maybe Integer
-- ^ Inclusive upper bound, if established.
, integerExcludesZero :: Bool
-- ^ True when every represented value is nonzero.
}
deriving (Eq, Ord, Read, Show)
{- | Facts valid at one program point, or bottom when that point is unreachable.
Missing symbol entries mean no path-specific refinement is stored.
-}
data IntegerFacts
= ReachableFacts (Map SymbolId IntegerFact)
| UnreachableFacts
deriving (Eq, Ord, Read, Show)
{- | Fixed-point facts for one structured loop.
The header is the state before a pre-test loop's condition (or before a
post-test loop's first body execution). Condition, body, and update inputs are
reported separately because a @continue@ reaches different program points in
the three Core loop forms. Exit facts join the false-condition edge with every
reachable @break@ edge. Return facts are retained for clients which need to
account for effects in paths that do not leave the function normally.
-}
data LoopFactSummary = LoopFactSummary
{ loopHeaderFacts :: IntegerFacts
-- ^ Widened input facts at the loop's back-edge join.
, loopConditionFacts :: IntegerFacts
-- ^ Facts immediately before evaluating the condition.
, loopBodyFacts :: IntegerFacts
-- ^ Facts on entry to the body after a true condition, or at do-loop entry.
, loopUpdateFacts :: IntegerFacts
-- ^ Facts at a @for@ update; unreachable for loops without an update.
, loopExitFacts :: IntegerFacts
-- ^ Facts valid after normal loop termination, including breaks.
, loopReturnFacts :: IntegerFacts
-- ^ Facts on function-returning paths inside the loop.
, loopFixedPointIterations :: Int
-- ^ Transfer rounds used to reach stability, or the configured cap.
, loopAnalysisWidened :: Bool
-- ^ True when the conservative cap fallback was needed.
}
deriving (Eq, Ord, Read, Show)
-- | Reachable input state with no path-specific symbol constraints.
emptyIntegerFacts :: IntegerFacts
emptyIntegerFacts = ReachableFacts Map.empty
-- | Bottom state used for a contradictory or infeasible control-flow edge.
unreachableIntegerFacts :: IntegerFacts
unreachableIntegerFacts = UnreachableFacts
-- | Test whether no execution reaches the represented program point.
isUnreachableFacts :: IntegerFacts -> Bool
isUnreachableFacts UnreachableFacts = True
isUnreachableFacts _ = False
-- | Look up the current refinement for a semantic symbol identity.
lookupIntegerFact :: IntegerFacts -> SymbolId -> Maybe IntegerFact
lookupIntegerFact (ReachableFacts facts) symbol = Map.lookup symbol facts
lookupIntegerFact UnreachableFacts _ = Nothing
-- | Forget one symbol after a write whose resulting value is not modeled.
forgetIntegerFact :: IntegerFacts -> SymbolId -> IntegerFacts
forgetIntegerFact (ReachableFacts facts) symbol = ReachableFacts (Map.delete symbol facts)
forgetIntegerFact UnreachableFacts _ = UnreachableFacts
setIntegerFact :: IntegerFacts -> SymbolId -> Maybe IntegerFact -> IntegerFacts
setIntegerFact UnreachableFacts _ _ = UnreachableFacts
setIntegerFact (ReachableFacts facts) symbol value =
case value of
Nothing -> ReachableFacts (Map.delete symbol facts)
Just fact
| impossibleFact fact -> UnreachableFacts
| otherwise -> ReachableFacts (Map.insert symbol fact facts)
unknownFact :: IntegerFact
unknownFact = IntegerFact Nothing Nothing False
exactFact :: Integer -> IntegerFact
exactFact value = IntegerFact (Just value) (Just value) (value /= 0)
factForLiteral :: Integer -> IntegerFact
factForLiteral = exactFact
typeRange :: Type -> IntegerFact
typeRange valueType = case (coreIntegerBitWidth valueType, coreIntegerIsSigned valueType) of
(Just width, Just True) ->
let limit = 2 ^ (width - 1)
in IntegerFact (Just (negate limit)) (Just (limit - 1)) False
(Just width, Just False) -> IntegerFact (Just 0) (Just (2 ^ width - 1)) False
_ -> unknownFact
factIsExact :: IntegerFact -> Maybe Integer
factIsExact fact = case (integerMinimum fact, integerMaximum fact) of
(Just lower, Just upper) | lower == upper -> Just lower
_ -> Nothing
factIsZero :: IntegerFact -> Bool
factIsZero fact = factIsExact fact == Just 0
factContains :: IntegerFact -> Integer -> Bool
factContains fact value =
maybe True (<= value) (integerMinimum fact)
&& maybe True (>= value) (integerMaximum fact)
&& not (value == 0 && integerExcludesZero fact)
impossibleFact :: IntegerFact -> Bool
impossibleFact fact =
maybe
False
(\lower -> maybe False (lower >) (integerMaximum fact))
(integerMinimum fact)
|| (integerExcludesZero fact && factIsZero fact)
-- | Prove that every value represented by this fact is different from zero.
factProvesNonzero :: IntegerFact -> Bool
factProvesNonzero fact =
integerExcludesZero fact
|| maybe False (> 0) (integerMinimum fact)
|| maybe False (< 0) (integerMaximum fact)
factTruth :: IntegerFact -> Maybe Bool
factTruth fact
| factIsZero fact = Just False
| factProvesNonzero fact = Just True
| otherwise = Nothing
{- | Derive an interval for the supported pure integer subset of a Core expression.
Unsupported expressions return 'Nothing'; callers must not treat that as a
proof of safety or as an empty set of values.
-}
factOfIntegerExpression :: IntegerFacts -> CoreExpression -> Maybe IntegerFact
factOfIntegerExpression facts expression = case expression of
CoreLiteral (CoreInteger value) valueType
| isCoreIntegerType valueType -> Just (factForLiteral value)
CoreVariable name valueType
| isCoreIntegerType valueType ->
Just (maybe (typeRange valueType) id (lookupIntegerFact facts (resolvedSymbol name)))
CorePrimitive CoreNegate [value] valueType
| isCoreIntegerType valueType -> negateFact valueType <$> factOfIntegerExpression facts value
CorePrimitive primitive [left, right] valueType
| isCoreIntegerType valueType -> do
leftFact <- factOfIntegerExpression facts left
rightFact <- factOfIntegerExpression facts right
combineIntegerFacts primitive valueType leftFact rightFact
CoreLet name _ value body resultType
| isCoreIntegerType resultType ->
let afterValue = transferExpressionFacts facts value
bound = setIntegerFact afterValue (resolvedSymbol name) (factOfIntegerExpression afterValue value)
in factOfIntegerExpression bound body
CoreConditional condition whenTrue whenFalse resultType
| isCoreIntegerType resultType ->
let afterCondition = transferExpressionFacts facts condition
trueFacts = refineConditionFacts True condition afterCondition
falseFacts = refineConditionFacts False condition afterCondition
in case (isUnreachableFacts trueFacts, isUnreachableFacts falseFacts) of
(True, True) -> Nothing
(True, False) -> factOfIntegerExpression falseFacts whenFalse
(False, True) -> factOfIntegerExpression trueFacts whenTrue
(False, False) ->
joinFact
<$> factOfIntegerExpression trueFacts whenTrue
<*> factOfIntegerExpression falseFacts whenFalse
_ -> Nothing
combineIntegerFacts :: CorePrimitive -> Type -> IntegerFact -> IntegerFact -> Maybe IntegerFact
combineIntegerFacts primitive resultType left right = case primitive of
CoreAdd -> intervalResult resultType (+) left right
CoreSubtract -> intervalResult resultType (-) left right
CoreMultiply -> multiplyResult resultType left right
_ -> Nothing
intervalResult :: Type -> (Integer -> Integer -> Integer) -> IntegerFact -> IntegerFact -> Maybe IntegerFact
intervalResult resultType operation left right = do
lowerLeft <- integerMinimum left
upperLeft <- integerMaximum left
lowerRight <- integerMinimum right
upperRight <- integerMaximum right
let candidates =
[ operation lowerLeft lowerRight
, operation lowerLeft upperRight
, operation upperLeft lowerRight
, operation upperLeft upperRight
]
checkedInterval resultType (minimum candidates) (maximum candidates)
multiplyResult :: Type -> IntegerFact -> IntegerFact -> Maybe IntegerFact
multiplyResult resultType left right =
intervalResultWithCandidates
resultType
left
right
[ (*)
]
where
intervalResultWithCandidates valueType leftFact rightFact operations = do
lowerLeft <- integerMinimum leftFact
upperLeft <- integerMaximum leftFact
lowerRight <- integerMinimum rightFact
upperRight <- integerMaximum rightFact
let products =
[ operation leftValue rightValue
| operation <- operations
, leftValue <- [lowerLeft, upperLeft]
, rightValue <- [lowerRight, upperRight]
]
result <- checkedInterval valueType (minimum products) (maximum products)
pure result {integerExcludesZero = factProvesNonzero leftFact && factProvesNonzero rightFact}
checkedInterval :: Type -> Integer -> Integer -> Maybe IntegerFact
checkedInterval valueType lower upper
| not (integerFitsCoreType valueType lower && integerFitsCoreType valueType upper) = Nothing
| otherwise =
let excludesZero = lower > 0 || upper < 0
in Just (IntegerFact (Just lower) (Just upper) excludesZero)
negateFact :: Type -> IntegerFact -> IntegerFact
negateFact valueType fact = case (integerMaximum fact, integerMinimum fact) of
(Just upper, Just lower)
| integerFitsCoreType valueType (negate upper)
, integerFitsCoreType valueType (negate lower) ->
IntegerFact
(Just (negate upper))
(Just (negate lower))
(integerExcludesZero fact || lower > 0 || upper < 0)
_ -> unknownFact
{- | Account for expression evaluation, invalidating refinements at calls.
Calls can mutate captured cells; Core has no complete read/write summary yet.
-}
transferExpressionFacts :: IntegerFacts -> CoreExpression -> IntegerFacts
transferExpressionFacts UnreachableFacts _ = UnreachableFacts
transferExpressionFacts facts (CorePrimitive CoreLogicalAnd [left, right] _) =
transferShortCircuitFacts True facts left right
transferExpressionFacts facts (CorePrimitive CoreLogicalOr [left, right] _) =
transferShortCircuitFacts False facts left right
transferExpressionFacts facts (CoreConditional condition whenTrue whenFalse _) =
let afterCondition = transferExpressionFacts facts condition
afterTrue = transferExpressionFacts (refineConditionFacts True condition afterCondition) whenTrue
afterFalse = transferExpressionFacts (refineConditionFacts False condition afterCondition) whenFalse
in joinIntegerFacts afterTrue afterFalse
transferExpressionFacts facts expression
| expressionInvokesCallable expression = emptyIntegerFacts
| otherwise = facts
transferShortCircuitFacts :: Bool -> IntegerFacts -> CoreExpression -> CoreExpression -> IntegerFacts
transferShortCircuitFacts isAnd facts left right =
let afterLeft = transferExpressionFacts facts left
leftTruth = conditionTruthFromFacts facts left
rightInput = refineConditionFacts isAnd left afterLeft
afterRight = transferExpressionFacts rightInput right
evaluatesRight = if isAnd then leftTruth /= Just False else leftTruth /= Just True
in if not evaluatesRight
then afterLeft
else case leftTruth of
Just _ -> afterRight
Nothing -> joinIntegerFacts afterLeft afterRight
-- | Whether evaluating an expression can invoke a callable.
expressionInvokesCallable :: CoreExpression -> Bool
expressionInvokesCallable expression = case expression of
CoreVariable {} -> False
CoreLiteral {} -> False
CoreApply {} -> True
CorePrimitive _ arguments _ -> any expressionInvokesCallable arguments
CoreLet _ _ value body _ -> expressionInvokesCallable value || expressionInvokesCallable body
CoreConditional condition whenTrue whenFalse _ ->
any expressionInvokesCallable [condition, whenTrue, whenFalse]
-- A closure body is deferred; only its capture initializers run now.
CoreClosure captures _ _ _ _ -> any (expressionInvokesCallable . coreCaptureValue) captures
{- | Transfer one statement's normal-completion facts to the next program point.
Break, continue, and return edges are consumed by the nearest owning flow
construct and therefore do not appear as normal continuation here.
-}
transferStatementFacts :: IntegerFacts -> CoreStatement -> IntegerFacts
transferStatementFacts facts = flowNormalFacts . analyzeStatementFlow facts
-- | Transfer statements in source order until no normal path remains.
transferStatementsFacts :: IntegerFacts -> [CoreStatement] -> IntegerFacts
transferStatementsFacts facts = flowNormalFacts . analyzeSequenceFlow facts
{- | Summarize the data-flow behavior of a structured Core loop. A bounded
ascending iteration discovers loop-carried facts; interval widening prevents
induction variables from requiring one analysis round per runtime iteration.
If an unusual transfer shape does not stabilize within the cap, the pass
recomputes one sound summary from the reachable top state rather than exposing
the last, potentially under-approximating iteration.
-}
loopFactSummary :: IntegerFacts -> CoreStatement -> Maybe LoopFactSummary
loopFactSummary input statement = case statement of
CoreWhile condition body ->
Just $ solve body (whileStep condition body [])
CoreDoWhile body condition ->
Just $ solve body (doWhileStep body condition)
CoreFor condition body update ->
Just $ solve (body ++ update) (whileStep condition body update)
_ -> Nothing
where
-- Every round of the fixed point analyses the loop body, and with it
-- every loop nested in the body, each of which iterates in turn: the
-- work is the product of the rounds of all enclosing loops, which is
-- exponential in the depth of the nest. Only the innermost levels
-- iterate; a loop with a deeper nest inside it is summarized in one
-- pass from the state that assumes nothing, which is always sound
-- and is what the iteration falls back to when it does not settle.
solve nested step
| loopNestingHeight nested >= iteratedLoopNesting = summarize 1 True emptyIntegerFacts (step emptyIntegerFacts)
| otherwise = solveLoop input step
{- | How many levels of nested loops are iterated to a fixed point. A loop
whose body holds this many levels of loops or more is summarized in a single
pass, so the cost of a nest grows with its size and not exponentially with
its depth.
-}
iteratedLoopNesting :: Int
iteratedLoopNesting = 2
-- | The deepest nest of loops in the statements: 0 when they hold no loop.
loopNestingHeight :: [CoreStatement] -> Int
loopNestingHeight = foldr (max . statementHeight) 0
where
statementHeight statement = case statement of
CoreIf _ whenTrue whenFalse -> max (loopNestingHeight whenTrue) (loopNestingHeight whenFalse)
CoreWhile _ body -> 1 + loopNestingHeight body
CoreDoWhile body _ -> 1 + loopNestingHeight body
CoreFor _ body update -> 1 + max (loopNestingHeight body) (loopNestingHeight update)
_ -> 0
loopIterationLimit :: Int
loopIterationLimit = 8
data LoopStep = LoopStep
{ stepConditionFacts :: IntegerFacts
, stepBodyFacts :: IntegerFacts
, stepUpdateFacts :: IntegerFacts
, stepBackEdgeFacts :: IntegerFacts
, stepExitFacts :: IntegerFacts
, stepReturnFacts :: IntegerFacts
}
solveLoop :: IntegerFacts -> (IntegerFacts -> LoopStep) -> LoopFactSummary
solveLoop UnreachableFacts step = summarize 0 False UnreachableFacts (step UnreachableFacts)
solveLoop entry step = iterateHeader 0 entry
where
iterateHeader rounds header =
let current = step header
candidate = joinIntegerFacts header (stepBackEdgeFacts current)
widened = widenIntegerFacts header candidate
nextRound = rounds + 1
in if widened == header
then summarize nextRound False header current
else
if nextRound >= loopIterationLimit
then conservativeSummary nextRound
else iterateHeader nextRound widened
conservativeSummary rounds =
let top = step emptyIntegerFacts
in summarize rounds True emptyIntegerFacts top
summarize :: Int -> Bool -> IntegerFacts -> LoopStep -> LoopFactSummary
summarize rounds widened header step =
LoopFactSummary
{ loopHeaderFacts = header
, loopConditionFacts = stepConditionFacts step
, loopBodyFacts = stepBodyFacts step
, loopUpdateFacts = stepUpdateFacts step
, loopExitFacts = stepExitFacts step
, loopReturnFacts = stepReturnFacts step
, loopFixedPointIterations = rounds
, loopAnalysisWidened = widened
}
whileStep :: CoreExpression -> [CoreStatement] -> [CoreStatement] -> IntegerFacts -> LoopStep
whileStep condition body update header =
let conditionTrue = refineConditionFacts True condition header
conditionFalse = refineConditionFacts False condition header
bodyFlow = analyzeSequenceFlow conditionTrue body
updateInput = joinIntegerFacts (flowNormalFacts bodyFlow) (flowContinueFacts bodyFlow)
updateFlow = analyzeSequenceFlow updateInput update
loopBackEdge
| null update = updateInput
| otherwise = joinIntegerFacts (flowNormalFacts updateFlow) (flowContinueFacts updateFlow)
exits =
joinIntegerFacts
conditionFalse
( joinIntegerFacts
(flowBreakFacts bodyFlow)
(flowBreakFacts updateFlow)
)
returns = joinIntegerFacts (flowReturnFacts bodyFlow) (flowReturnFacts updateFlow)
in LoopStep
{ stepConditionFacts = header
, stepBodyFacts = conditionTrue
, stepUpdateFacts = if null update then unreachableIntegerFacts else updateInput
, stepBackEdgeFacts = loopBackEdge
, stepExitFacts = exits
, stepReturnFacts = returns
}
doWhileStep :: [CoreStatement] -> CoreExpression -> IntegerFacts -> LoopStep
doWhileStep body condition header =
let bodyFlow = analyzeSequenceFlow header body
conditionInput = joinIntegerFacts (flowNormalFacts bodyFlow) (flowContinueFacts bodyFlow)
conditionTrue = refineConditionFacts True condition conditionInput
conditionFalse = refineConditionFacts False condition conditionInput
in LoopStep
{ stepConditionFacts = conditionInput
, stepBodyFacts = header
, stepUpdateFacts = unreachableIntegerFacts
, stepBackEdgeFacts = conditionTrue
, stepExitFacts = joinIntegerFacts (flowBreakFacts bodyFlow) conditionFalse
, stepReturnFacts = flowReturnFacts bodyFlow
}
{- | Widen an ascending interval sequence. A lower bound that moves down or
an upper bound that moves up becomes unknown; stable and narrowing endpoints
are retained. A fact missing from either state is discarded, and zero
exclusion survives only if both states prove it. These rules guarantee the
result contains both represented value sets and force expanding induction
intervals to stabilize after at most one widening round.
-}
widenIntegerFacts :: IntegerFacts -> IntegerFacts -> IntegerFacts
widenIntegerFacts UnreachableFacts next = next
widenIntegerFacts previous UnreachableFacts = previous
widenIntegerFacts (ReachableFacts previous) (ReachableFacts next) =
ReachableFacts (Map.mergeWithKey widenExisting (const Map.empty) (const Map.empty) previous next)
where
widenExisting _ old new = Just (widenIntegerFact old new)
widenIntegerFact :: IntegerFact -> IntegerFact -> IntegerFact
widenIntegerFact previous next =
IntegerFact
{ integerMinimum = widenLower (integerMinimum previous) (integerMinimum next)
, integerMaximum = widenUpper (integerMaximum previous) (integerMaximum next)
, integerExcludesZero = integerExcludesZero previous && integerExcludesZero next
}
where
widenLower (Just old) (Just new)
| new >= old = Just old
| otherwise = Nothing
widenLower _ _ = Nothing
widenUpper (Just old) (Just new)
| new <= old = Just old
| otherwise = Nothing
widenUpper _ _ = Nothing
data StatementFlowFacts = StatementFlowFacts
{ flowNormalFacts :: IntegerFacts
, flowBreakFacts :: IntegerFacts
, flowContinueFacts :: IntegerFacts
, flowReturnFacts :: IntegerFacts
}
unreachableFlow :: StatementFlowFacts
unreachableFlow =
StatementFlowFacts
{ flowNormalFacts = unreachableIntegerFacts
, flowBreakFacts = unreachableIntegerFacts
, flowContinueFacts = unreachableIntegerFacts
, flowReturnFacts = unreachableIntegerFacts
}
analyzeStatementFlow :: IntegerFacts -> CoreStatement -> StatementFlowFacts
analyzeStatementFlow UnreachableFacts _ = unreachableFlow
analyzeStatementFlow facts statement = case statement of
CoreBind binding ->
let value = coreBindingValue binding
afterValue = transferExpressionFacts facts value
known
| expressionInvokesCallable value = Nothing
| isCoreIntegerType (coreBindingType binding) = factOfIntegerExpression facts value
| otherwise = Nothing
in normalFlow (setIntegerFact afterValue (resolvedSymbol (coreBindingName binding)) known)
CoreAssign name value ->
let afterValue = transferExpressionFacts facts value
known
| expressionInvokesCallable value = Nothing
| otherwise = factOfIntegerExpression facts value
in normalFlow (setIntegerFact afterValue (resolvedSymbol name) known)
CoreReturn value ->
let afterValue = transferExpressionFacts facts value
in unreachableFlow {flowReturnFacts = afterValue}
CoreEvaluate value -> normalFlow (transferExpressionFacts facts value)
CoreIf condition whenTrue whenFalse ->
let trueInput = refineConditionFacts True condition facts
falseInput = refineConditionFacts False condition facts
trueFlow = analyzeSequenceFlow trueInput whenTrue
falseFlow = analyzeSequenceFlow falseInput whenFalse
in joinFlowFacts trueFlow falseFlow
CoreWhile {} -> loopNormalFlow facts statement
CoreDoWhile {} -> loopNormalFlow facts statement
CoreFor {} -> loopNormalFlow facts statement
CoreBreak -> unreachableFlow {flowBreakFacts = facts}
CoreContinue -> unreachableFlow {flowContinueFacts = facts}
loopNormalFlow :: IntegerFacts -> CoreStatement -> StatementFlowFacts
loopNormalFlow facts statement = case loopFactSummary facts statement of
Nothing -> normalFlow emptyIntegerFacts
Just summary ->
StatementFlowFacts
{ flowNormalFacts = loopExitFacts summary
, flowBreakFacts = unreachableIntegerFacts
, flowContinueFacts = unreachableIntegerFacts
, flowReturnFacts = loopReturnFacts summary
}
normalFlow :: IntegerFacts -> StatementFlowFacts
normalFlow facts = unreachableFlow {flowNormalFacts = facts}
joinFlowFacts :: StatementFlowFacts -> StatementFlowFacts -> StatementFlowFacts
joinFlowFacts left right =
StatementFlowFacts
{ flowNormalFacts = joinIntegerFacts (flowNormalFacts left) (flowNormalFacts right)
, flowBreakFacts = joinIntegerFacts (flowBreakFacts left) (flowBreakFacts right)
, flowContinueFacts = joinIntegerFacts (flowContinueFacts left) (flowContinueFacts right)
, flowReturnFacts = joinIntegerFacts (flowReturnFacts left) (flowReturnFacts right)
}
{- | Sequence a block while carrying each abrupt edge independently. In
particular, a break or continue in one conditional arm is not accidentally
joined with the normal state and mistaken for a loop exit or back-edge.
-}
analyzeSequenceFlow :: IntegerFacts -> [CoreStatement] -> StatementFlowFacts
analyzeSequenceFlow input statements = sequenceFrom input statements
where
sequenceFrom facts [] = normalFlow facts
sequenceFrom facts (statement : remaining) =
let current = analyzeStatementFlow facts statement
later = sequenceFrom (flowNormalFacts current) remaining
in StatementFlowFacts
{ flowNormalFacts = flowNormalFacts later
, flowBreakFacts = joinIntegerFacts (flowBreakFacts current) (flowBreakFacts later)
, flowContinueFacts = joinIntegerFacts (flowContinueFacts current) (flowContinueFacts later)
, flowReturnFacts = joinIntegerFacts (flowReturnFacts current) (flowReturnFacts later)
}
-- At control-flow joins, form an interval hull and retain zero exclusion only
-- when every continuing path excludes zero. Unreachable arms are identities.
-- | Merge feasible paths, retaining only facts valid on every incoming path.
joinIntegerFacts :: IntegerFacts -> IntegerFacts -> IntegerFacts
joinIntegerFacts UnreachableFacts reachable = reachable
joinIntegerFacts reachable UnreachableFacts = reachable
joinIntegerFacts (ReachableFacts left) (ReachableFacts right) =
ReachableFacts (Map.mergeWithKey combine (const Map.empty) (const Map.empty) left right)
where
combine _ first second = Just (joinFact first second)
joinFact :: IntegerFact -> IntegerFact -> IntegerFact
joinFact left right =
IntegerFact
{ integerMinimum = lowerHull (integerMinimum left) (integerMinimum right)
, integerMaximum = upperHull (integerMaximum left) (integerMaximum right)
, integerExcludesZero = factProvesNonzero left && factProvesNonzero right
}
where
lowerHull (Just first) (Just second) = Just (min first second)
lowerHull _ _ = Nothing
upperHull (Just first) (Just second) = Just (max first second)
upperHull _ _ = Nothing
{- | Refine facts for the requested truth edge of a condition.
True and false edges may carry different intervals or reachability.
-}
refineConditionFacts :: Bool -> CoreExpression -> IntegerFacts -> IntegerFacts
refineConditionFacts _ _ UnreachableFacts = UnreachableFacts
refineConditionFacts desired expression facts = refine expression
where
refine value = case value of
CoreLiteral (CoreBoolean literal) _
| literal /= desired -> UnreachableFacts
CoreLiteral (CoreInteger literal) valueType
| isCoreIntegerType valueType
, (literal /= 0) /= desired ->
UnreachableFacts
CoreVariable name valueType
| isCoreIntegerType valueType ->
constrainVariable name valueType (if desired then nonzeroConstraint else zeroConstraint) facts
CorePrimitive CoreLogicalNot [nested] _ -> refineConditionFacts (not desired) nested facts
CorePrimitive CoreLogicalAnd [left, right] _
| desired ->
refineConditionFacts True right (refineConditionFacts True left facts)
| otherwise ->
joinIntegerFacts
(refineConditionFacts False left facts)
(refineConditionFacts False right (refineConditionFacts True left facts))
CorePrimitive CoreLogicalOr [left, right] _
| desired ->
joinIntegerFacts
(refineConditionFacts True left facts)
(refineConditionFacts True right (refineConditionFacts False left facts))
| otherwise ->
refineConditionFacts False right (refineConditionFacts False left facts)
CorePrimitive primitive [left, right] _
| any expressionInvokesCallable [left, right] -> emptyIntegerFacts
| otherwise -> refineComparison desired primitive left right facts
CoreApply {} -> emptyIntegerFacts
CoreLet name valueType initializer body _ ->
let afterInitializer = transferExpressionFacts facts initializer
binding = CoreBind (CoreBinding name valueType False initializer)
afterBinding = transferStatementFacts afterInitializer binding
afterBody = refineConditionFacts desired body afterBinding
in forgetIntegerFact afterBody (resolvedSymbol name)
-- The requested truth can come from either arm; each arm is
-- reached only along its own edge of the selecting condition.
CoreConditional condition whenTrue whenFalse _ ->
let afterCondition = transferExpressionFacts facts condition
throughTrue =
refineConditionFacts desired whenTrue (refineConditionFacts True condition afterCondition)
throughFalse =
refineConditionFacts desired whenFalse (refineConditionFacts False condition afterCondition)
in joinIntegerFacts throughTrue throughFalse
_
| expressionInvokesCallable value -> emptyIntegerFacts
| otherwise -> facts
data IntegerConstraint
= LowerBound Integer
| UpperBound Integer
| ExactValue Integer
| ExcludeZero
zeroConstraint, nonzeroConstraint :: IntegerConstraint
zeroConstraint = ExactValue 0
nonzeroConstraint = ExcludeZero
constrainVariable :: ResolvedName -> Type -> IntegerConstraint -> IntegerFacts -> IntegerFacts
constrainVariable name valueType constraint facts =
let symbol = resolvedSymbol name
current = maybe (typeRange valueType) id (lookupIntegerFact facts symbol)
refined = applyConstraint constraint current
in setIntegerFact facts symbol (Just refined)
applyConstraint :: IntegerConstraint -> IntegerFact -> IntegerFact
applyConstraint constraint fact = case constraint of
LowerBound value ->
fact {integerMinimum = Just (maybe value (max value) (integerMinimum fact))}
UpperBound value ->
fact {integerMaximum = Just (maybe value (min value) (integerMaximum fact))}
ExactValue value
| factContains fact value -> IntegerFact (Just value) (Just value) (integerExcludesZero fact)
| otherwise -> IntegerFact (Just 1) (Just 0) True
ExcludeZero -> fact {integerExcludesZero = True}
refineComparison :: Bool -> CorePrimitive -> CoreExpression -> CoreExpression -> IntegerFacts -> IntegerFacts
refineComparison desired primitive left right facts =
case (integerVariable left, integerVariable right) of
(Just (leftName, leftType), Just (rightName, rightType)) ->
refineVariableAgainstVariable desired primitive leftName leftType rightName rightType facts
_ -> case (integerVariable left, integerLiteral right) of
(Just (name, valueType), Just value) -> refineVariableAgainstLiteral desired primitive name valueType value facts
_ -> case (integerLiteral left, integerVariable right) of
(Just value, Just (name, valueType)) ->
refineVariableAgainstLiteral desired (reversePrimitive primitive) name valueType value facts
_ -> facts
-- Relational comparisons transfer only interval information already proved on
-- the opposite operand. Equality additionally carries the zero-exclusion bit:
-- if equal values are known to avoid zero, both symbols must avoid zero.
refineVariableAgainstVariable ::
Bool -> CorePrimitive -> ResolvedName -> Type -> ResolvedName -> Type -> IntegerFacts -> IntegerFacts
refineVariableAgainstVariable desired primitive leftName leftType rightName rightType facts
| resolvedSymbol leftName == resolvedSymbol rightName =
if comparisonAlwaysTrueForSelf primitive == desired then facts else UnreachableFacts
| otherwise = case (primitive, desired) of
(CoreEqual, True) -> refineEquality
(CoreNotEqual, False) -> refineEquality
(CoreEqual, False) -> refineInequality
(CoreNotEqual, True) -> refineInequality
(CoreLessThan, True) -> refineStrictLessThan
(CoreLessThan, False) -> refineGreaterEqual
(CoreLessEqual, True) -> refineLessEqual
(CoreLessEqual, False) -> refineStrictGreaterThan
(CoreGreaterThan, True) -> refineStrictGreaterThan
(CoreGreaterThan, False) -> refineLessEqual
(CoreGreaterEqual, True) -> refineGreaterEqual
(CoreGreaterEqual, False) -> refineStrictLessThan
_ -> facts
where
leftSymbol = resolvedSymbol leftName
rightSymbol = resolvedSymbol rightName
leftFact = factForSymbol facts leftSymbol leftType
rightFact = factForSymbol facts rightSymbol rightType
refineEquality =
case intersectFacts leftFact rightFact of
Nothing -> UnreachableFacts
Just common ->
setIntegerFact
(setIntegerFact facts leftSymbol (Just common))
rightSymbol
(Just common)
refineInequality =
case (factIsZero leftFact, factIsZero rightFact) of
(True, _) -> constrainVariable rightName rightType ExcludeZero facts
(_, True) -> constrainVariable leftName leftType ExcludeZero facts
_ -> facts
refineStrictLessThan =
applyPairConstraints
(upperConstraint ((subtract 1) <$> integerMaximum rightFact))
(lowerConstraint ((+ 1) <$> integerMinimum leftFact))
refineLessEqual =
applyPairConstraints
(upperConstraint (integerMaximum rightFact))
(lowerConstraint (integerMinimum leftFact))
refineStrictGreaterThan =
applyPairConstraints
(lowerConstraint ((+ 1) <$> integerMinimum rightFact))
(upperConstraint ((subtract 1) <$> integerMaximum leftFact))
refineGreaterEqual =
applyPairConstraints
(lowerConstraint (integerMinimum rightFact))
(upperConstraint (integerMaximum leftFact))
applyPairConstraints leftConstraint rightConstraint =
let afterLeft = maybe facts (\constraint -> constrainVariable leftName leftType constraint facts) leftConstraint
in maybe afterLeft (\constraint -> constrainVariable rightName rightType constraint afterLeft) rightConstraint
upperConstraint = fmap UpperBound
lowerConstraint = fmap LowerBound
comparisonAlwaysTrueForSelf :: CorePrimitive -> Bool
comparisonAlwaysTrueForSelf primitive = case primitive of
CoreEqual -> True
CoreNotEqual -> False
CoreLessThan -> False
CoreLessEqual -> True
CoreGreaterThan -> False
CoreGreaterEqual -> True
_ -> False
factForSymbol :: IntegerFacts -> SymbolId -> Type -> IntegerFact
factForSymbol facts symbol valueType = maybe (typeRange valueType) id (lookupIntegerFact facts symbol)
intersectFacts :: IntegerFact -> IntegerFact -> Maybe IntegerFact
intersectFacts left right =
let common =
IntegerFact
{ integerMinimum = maxLower (integerMinimum left) (integerMinimum right)
, integerMaximum = minUpper (integerMaximum left) (integerMaximum right)
, integerExcludesZero = integerExcludesZero left || integerExcludesZero right
}
in if impossibleFact common then Nothing else Just common
where
maxLower (Just first) (Just second) = Just (max first second)
maxLower (Just value) Nothing = Just value
maxLower Nothing (Just value) = Just value
maxLower Nothing Nothing = Nothing
minUpper (Just first) (Just second) = Just (min first second)
minUpper (Just value) Nothing = Just value
minUpper Nothing (Just value) = Just value
minUpper Nothing Nothing = Nothing
refineVariableAgainstLiteral ::
Bool -> CorePrimitive -> ResolvedName -> Type -> Integer -> IntegerFacts -> IntegerFacts
refineVariableAgainstLiteral desired primitive name valueType value facts =
case constraintFor primitive desired value of
Just constraint -> constrainVariable name valueType constraint facts
Nothing -> facts
constraintFor :: CorePrimitive -> Bool -> Integer -> Maybe IntegerConstraint
constraintFor primitive desired value = case (primitive, desired) of
(CoreEqual, True) -> Just (ExactValue value)
(CoreEqual, False) | value == 0 -> Just ExcludeZero
(CoreNotEqual, True) | value == 0 -> Just ExcludeZero
(CoreNotEqual, False) -> Just (ExactValue value)
(CoreLessThan, True) -> Just (UpperBound (value - 1))
(CoreLessThan, False) -> Just (LowerBound value)
(CoreLessEqual, True) -> Just (UpperBound value)
(CoreLessEqual, False) -> Just (LowerBound (value + 1))
(CoreGreaterThan, True) -> Just (LowerBound (value + 1))
(CoreGreaterThan, False) -> Just (UpperBound value)
(CoreGreaterEqual, True) -> Just (LowerBound value)
(CoreGreaterEqual, False) -> Just (UpperBound (value - 1))
_ -> Nothing
integerVariable :: CoreExpression -> Maybe (ResolvedName, Type)
integerVariable (CoreVariable name valueType)
| isCoreIntegerType valueType = Just (name, valueType)
integerVariable _ = Nothing
integerLiteral :: CoreExpression -> Maybe Integer
integerLiteral (CoreLiteral (CoreInteger value) valueType)
| isCoreIntegerType valueType = Just value
integerLiteral _ = Nothing
reversePrimitive :: CorePrimitive -> CorePrimitive
reversePrimitive primitive = case primitive of
CoreLessThan -> CoreGreaterThan
CoreLessEqual -> CoreGreaterEqual
CoreGreaterThan -> CoreLessThan
CoreGreaterEqual -> CoreLessEqual
_ -> primitive
-- | Determine truth only when one condition edge is provably infeasible.
conditionTruthFromFacts :: IntegerFacts -> CoreExpression -> Maybe Bool
conditionTruthFromFacts UnreachableFacts _ = Nothing
conditionTruthFromFacts facts expression
| expressionInvokesCallable expression = Nothing
| isUnreachableFacts (refineConditionFacts True expression facts) = Just False
| isUnreachableFacts (refineConditionFacts False expression facts) = Just True
| otherwise = truth expression
where
truth value = case value of
CoreLiteral (CoreBoolean boolean) _ -> Just boolean
CoreLiteral (CoreInteger integerValue) valueType
| isCoreIntegerType valueType -> Just (integerValue /= 0)
CoreVariable name valueType
| isCoreIntegerType valueType ->
factTruth =<< factOfIntegerExpression facts (CoreVariable name valueType)
CorePrimitive CoreLogicalNot [nested] _ -> not <$> conditionTruthFromFacts facts nested
CorePrimitive CoreLogicalAnd [left, right] _ ->
(&&) <$> conditionTruthFromFacts facts left <*> conditionTruthFromFacts facts right
CorePrimitive CoreLogicalOr [left, right] _ ->
(||) <$> conditionTruthFromFacts facts left <*> conditionTruthFromFacts facts right
CorePrimitive primitive [left, right] _ -> comparisonTruth facts primitive left right
CoreConditional condition whenTrue whenFalse _ ->
case conditionTruthFromFacts facts condition of
Just True -> conditionTruthFromFacts (refineConditionFacts True condition facts) whenTrue
Just False -> conditionTruthFromFacts (refineConditionFacts False condition facts) whenFalse
Nothing -> Nothing
_ -> Nothing
comparisonTruth :: IntegerFacts -> CorePrimitive -> CoreExpression -> CoreExpression -> Maybe Bool
comparisonTruth facts primitive leftExpression rightExpression =
case (integerVariable leftExpression, integerVariable rightExpression) of
(Just (leftName, leftType), Just (rightName, rightType)) ->
compareFacts
primitive
(factForSymbol facts (resolvedSymbol leftName) leftType)
(factForSymbol facts (resolvedSymbol rightName) rightType)
_ -> case (integerVariable leftExpression, integerLiteral rightExpression) of
(Just (name, valueType), Just literal) ->
compareInterval
primitive
(Just (maybe (typeRange valueType) id (lookupIntegerFact facts (resolvedSymbol name))))
literal
_ -> case (integerLiteral leftExpression, integerVariable rightExpression) of
(Just literal, Just (name, valueType)) ->
compareInterval
(reversePrimitive primitive)
(Just (maybe (typeRange valueType) id (lookupIntegerFact facts (resolvedSymbol name))))
literal
_ -> case (integerLiteral leftExpression, integerLiteral rightExpression) of
(Just left, Just right) -> evaluateIntegerComparison primitive left right
_ -> Nothing
compareFacts :: CorePrimitive -> IntegerFact -> IntegerFact -> Maybe Bool
compareFacts primitive left right
| factIsExact left == factIsExact right
, Just leftValue <- factIsExact left
, Just rightValue <- factIsExact right =
evaluateIntegerComparison primitive leftValue rightValue
| otherwise = case primitive of
CoreEqual
| disjointFacts left right -> Just False
CoreNotEqual
| disjointFacts left right -> Just True
CoreLessThan
| bothBounded (integerMaximum left) (integerMinimum right) (<) -> Just True
| bothBounded (integerMinimum left) (integerMaximum right) (>=) -> Just False
CoreLessEqual
| bothBounded (integerMaximum left) (integerMinimum right) (<=) -> Just True
| bothBounded (integerMinimum left) (integerMaximum right) (>) -> Just False
CoreGreaterThan
| bothBounded (integerMinimum left) (integerMaximum right) (>) -> Just True
| bothBounded (integerMaximum left) (integerMinimum right) (<=) -> Just False
CoreGreaterEqual
| bothBounded (integerMinimum left) (integerMaximum right) (>=) -> Just True
| bothBounded (integerMaximum left) (integerMinimum right) (<) -> Just False
_ -> Nothing
where
bothBounded (Just first) (Just second) relation = relation first second
bothBounded _ _ _ = False
disjointFacts :: IntegerFact -> IntegerFact -> Bool
disjointFacts left right =
maybe False (\leftUpper -> maybe False (leftUpper <) (integerMinimum right)) (integerMaximum left)
|| maybe False (\rightUpper -> maybe False (rightUpper <) (integerMinimum left)) (integerMaximum right)
|| (factIsZero left && factProvesNonzero right)
|| (factIsZero right && factProvesNonzero left)
compareInterval :: CorePrimitive -> Maybe IntegerFact -> Integer -> Maybe Bool
compareInterval primitive maybeFact literal = do
fact <- maybeFact
let lower = integerMinimum fact
upper = integerMaximum fact
alwaysTrue = case primitive of
CoreEqual -> factIsExact fact == Just literal
CoreNotEqual -> not (factContains fact literal)
CoreLessThan -> maybe False (< literal) upper
CoreLessEqual -> maybe False (<= literal) upper
CoreGreaterThan -> maybe False (> literal) lower
CoreGreaterEqual -> maybe False (>= literal) lower
_ -> False
alwaysFalse = case primitive of
CoreEqual -> not (factContains fact literal)
CoreNotEqual -> factIsExact fact == Just literal
CoreLessThan -> maybe False (>= literal) lower
CoreLessEqual -> maybe False (> literal) lower
CoreGreaterThan -> maybe False (<= literal) upper
CoreGreaterEqual -> maybe False (< literal) upper
_ -> False
if alwaysTrue then Just True else if alwaysFalse then Just False else Nothing
evaluateIntegerComparison :: CorePrimitive -> Integer -> Integer -> Maybe Bool
evaluateIntegerComparison primitive left right = case primitive of
CoreEqual -> Just (left == right)
CoreNotEqual -> Just (left /= right)
CoreLessThan -> Just (left < right)
CoreLessEqual -> Just (left <= right)
CoreGreaterThan -> Just (left > right)
CoreGreaterEqual -> Just (left >= right)
_ -> Nothing