VerifiedCompilation.Purity

Definitions of Purity for Terms

module Untyped.Purity where

Imports

open import Untyped using (_⊢; case; builtin; _·_; force; `; ƛ; delay; con; constr; error)
open import Relation.Nullary using (Dec; yes; no; ¬_; _×-dec_)
open import Builtin using (Builtin; arity; arity₀)
open import Utils as U using (Maybe;nothing;just)
open import RawU using (TmCon)
open import Data.Product using (_,_; _×_)
open import Data.Nat using (; zero; suc; _>_; _>?_)
open import Data.List using (List; _∷_; [])
open import Data.List.Relation.Unary.All using (All)
open import Data.Maybe using (Maybe; just; nothing; from-just)
open import Data.Maybe.Properties using (just-injective)
open import Agda.Builtin.Equality using (_≡_; refl)
open import Relation.Nullary.Negation using (contradiction)
open import Relation.Binary.PropositionalEquality.Core using (trans; _≢_; subst; sym; cong)
open import Data.Empty using ()
open import Function.Base using (case_of_)
open import Untyped.CEK using (lookup?)
open import VerifiedCompilation.UntypedViews using (isDelay?; isTerm?; isLambda?; isdelay; isterm; islambda)
-- FIXME: This moves to Untyped.Reduction in a PR #7008...
open import VerifiedCompilation.UCaseReduce using (iterApp)

Saturation

The sat function is used to measure whether a builtin at the bottom of a sub-tree of force and applications is now saturated and ready to reduce.

-- TODO: This code will move to Untyped.Reduction once we merge
-- PR #7008.

data Arity : Set where
  no-builtin : Arity
  want :     Arity

-- This is a Phil Wadler approved hack...
postulate
  interleave-error :  {A : Set}  A

sat : {X : Set}  X   Arity
sat (` x) = no-builtin
sat (ƛ t) = no-builtin
sat (t · t₁) with sat t
... | no-builtin = no-builtin
... | want zero zero = want zero zero -- This will reduce the left first...
... | want zero (suc a₁) = want zero a₁
... | want (suc a₀) a₁ = interleave-error
sat (force t) with sat t
... | no-builtin = no-builtin
... | want zero a₁ = interleave-error
... | want (suc a₀) a₁ = want a₀ a₁
sat (delay t) = no-builtin
sat (con x) = no-builtin
sat (constr i xs) = no-builtin
sat (case t ts) = no-builtin
-- We assume no spontaneously reducing builtins!
sat (builtin b) = want (arity₀ b) (arity b)
sat error = no-builtin

data Pure {X : Set} : (X )  Set where
    force : {t : X }  Pure t  Pure (force (delay t))

    constr : {i : } {xs : List (X )}  All Pure xs  Pure (constr i xs)

    -- case applied to constr would reduce, and possibly be pure.
    case : {i : } {t : X }{vs ts : List (X )}
            lookup? i ts  just t
            Pure (iterApp t vs)
            Pure (case (constr i vs) ts)
    -- case applied to anything else is Unknown

    -- This assumes there are no builtins with arity 0
    -- Or, if there are, they can just be replaced by a
    -- constant before this stage!
    builtin : {b : Builtin}  Pure (builtin b)

    -- To be pure, a term needs to be still unsaturated
    -- after it has been force'd or had something applied
    -- hence, unsat-builtin₀ and unsat-builtin₁ have
    -- (suc (suc _)) requirements.
    unsat-builtin₀ : {t : X } {a₀ a₁ : }
             sat t  want (suc (suc a₀)) a₁
             Pure t
             Pure (force t)

    -- unsat-builtin₀₋₁ handles the case where
    -- we consume the last type argument but
    -- still have some unsaturated term args.
    unsat-builtin₀₋₁ : {t : X } {a₁ : }
             sat t  want (suc zero) (suc a₁)
             Pure t
             Pure (force t)

    unsat-builtin₁ : {t t₁ : X } {a₁ : }
             sat t  want zero (suc (suc a₁))
             Pure t
             Pure t₁
             Pure (t · t₁)

    -- This is deliberately not able to cover all applications
    -- ƛ (error) · t -- not pure
    -- ƛ ƛ (error) · t · n -- not pure
    -- ƛ ƛ ( (` nothing) · (` just nothing) ) · (ƛ error) · t -- not pure
    -- Double application is considered impure (Unknown) by
    -- the Haskell implementation at the moment.
    app : {l : Maybe X } {r : X }
             Pure l
             Pure r
             Pure ((ƛ l) · r)

    var : {v : X}  Pure (` v)
    delay : {t : X }  Pure (delay t)
    ƛ : {t : (Maybe X) }  Pure (ƛ t)
    con : {c : TmCon}  Pure (con c)
    -- errors are not pure ever.

isPure? : {X : Set}  (t : X )  Dec (Pure t)

allPure? : {X : Set}  (ts : List (X ))  Dec (All Pure ts)
allPure? [] = yes All.[]
allPure? (t  ts) with (isPure? t) ×-dec (allPure? ts)
... | yes (p , ps) = yes (p All.∷ ps)
... | no ¬p = no λ { (px All.∷ x)  ¬p (px , x) }

{-# TERMINATING #-}
isPure? (` x) = yes var
isPure? (ƛ t) = yes ƛ
isPure? (l · r) with isLambda? (isTerm?) l
... | yes (islambda (isterm l₁)) with (isPure? l₁) ×-dec (isPure? r)
...   | yes (pl , pr) = yes (app pl pr)
...   | no ¬pl-pr = no λ { (app pl pr)  ¬pl-pr (pl , pr) }
isPure? (l · r) | no ¬lambda with sat l in sat-l
... | no-builtin = no  { (unsat-builtin₁ sat-l₁ pl pr)  contradiction (trans (sym sat-l) sat-l₁)  ()) ; (app xx xx₁)  ¬lambda (islambda (isterm _)) })
... | want zero zero = no  { (unsat-builtin₁ sat-l₁ xx xx₁)  contradiction ((trans (sym sat-l) sat-l₁))  ()) })
... | want zero (suc zero) = no  { (unsat-builtin₁ sat-l₁ xx xx₁)  contradiction ((trans (sym sat-l) sat-l₁))  ()) })
... | want (suc x) x₁ = no  { (unsat-builtin₁ sat-l₁ xx xx₁)  contradiction ((trans (sym sat-l) sat-l₁))  ()) })
... | want zero (suc (suc a₁)) with (isPure? l) ×-dec (isPure? r)
...   | yes (pl , pr) = yes (unsat-builtin₁ sat-l pl pr)
...   | no ¬pl-pr = no  { (unsat-builtin₁ x xx xx₁)  ¬pl-pr (xx , xx₁) })
isPure? (force t) with isDelay? (isTerm?) t
... | no ¬delay with sat t in sat-t
...   | no-builtin = no  {
                     (force xx)  ¬delay (isdelay (isterm _)) ;
                     (unsat-builtin₀ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁) λ ();
                     (unsat-builtin₀₋₁ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁) λ ()
                     })
...   | want zero x₁ = no λ {
                     (unsat-builtin₀ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁)  ());
                     (unsat-builtin₀₋₁ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁) λ ()
                     }
... | want (suc zero) zero = no λ {
                     (unsat-builtin₀ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁)  ());
                     (unsat-builtin₀₋₁ sat-t₁ pt)  contradiction (trans (sym sat-t) sat-t₁) λ ()
                     }
... | want (suc zero) (suc x₁) with isPure? t
...    | no ¬pt = no λ { (unsat-builtin₀ x xx)  ¬pt xx ; (unsat-builtin₀₋₁ x xx)  ¬pt xx }
...    | yes pt = yes (unsat-builtin₀₋₁ sat-t pt)
isPure? (force t) | no ¬delay | want (suc (suc x)) x₁ with isPure? t
...     | no ¬pt = no λ {(unsat-builtin₀ x pt)  ¬pt pt; (unsat-builtin₀₋₁ x xx)  ¬pt xx}
...     | yes pt = yes (unsat-builtin₀ sat-t pt)
isPure? (force t) | yes (isdelay (isterm tt)) with isPure? tt
...    | yes p = yes (force p)
...    | no ¬p = no λ { (force x)  ¬p x }
isPure? (delay t) = yes delay
isPure? (con x) = yes con
isPure? (constr i xs) with allPure? xs
... | yes pp = yes (constr pp)
... | no ¬pp = no λ { (constr x)  ¬pp x }
isPure? (case (` x) ts) =  no λ ()
isPure? (case (ƛ t) ts) =  no λ ()
isPure? (case (t · t₁) ts) =  no λ ()
isPure? (case (force t) ts) =  no λ ()
isPure? (case (delay t) ts) =  no λ ()
isPure? (case (con x) ts) =  no λ ()
isPure? (case (constr i vs) ts) with lookup? i ts in lookup-i
... | nothing = no λ { (case lookup≡just pt·vs)  contradiction (trans (sym lookup-i) lookup≡just) λ () }
... | just t with isPure? (iterApp t vs)
...   | yes pt·vs = yes (case lookup-i pt·vs)
...   | no ¬p = no λ { (case lookup≡just pt·vs)  contradiction (subst  x  Pure (iterApp x vs)) (just-injective (trans (sym lookup≡just) lookup-i)) pt·vs) ¬p }
isPure? (case (case t ts₁) ts) = no λ ()
isPure? (case (builtin b) ts) = no λ ()
isPure? (case error ts) = no λ ()
isPure? (builtin b) = yes builtin
isPure? error = no λ ()