This module implements a CEK machine for UPLC which computes costs.
open import Cost.Base module Untyped.CEKWithCost {Budget : Set}(machineParameters : MachineParameters Budget) where
open import Data.Unit using (⊤;tt) open import Data.Nat using (ℕ;zero;suc) open import Data.List using (List;[];_∷_) open import Data.Vec using (Vec;[];_∷_;reverse) open import Data.Product using (_,_) open import Relation.Binary.PropositionalEquality using (_≡_;refl;sym) open import Untyped using (_⊢) open _⊢ open import Utils using (Writer;_>>_;_>>=_;return;maybe;Either;inj₁;inj₂;RuntimeError;gasError;either;_∔_≣_) open Writer open import RawU using (tmCon) open import Builtin using (Builtin;arity;signature) open import Builtin.Signature using (fv;args♯) open StepKind open ExBudgetCategory open import Untyped.CEK
We instantiate a Writer Monad with the operations from the machine parameters
open MachineParameters machineParameters renaming (ε to e) open Utils.WriterMonad e _∙_ CekM : Set → Set CekM = Writer Budget
As opposed to the production implemention, our spend
function does not implement slippage.
Slippage is an optimization that allows the interpreter to only add costs every n steps.
Slippage is only semantically relevant in restricting mode (not currently implemented in agda),
so we do not need to consider it here.
spend : StepKind → CekM ⊤ spend st = tell (cekMachineCost (BStep st)) spendStartupCost : CekM ⊤ spendStartupCost = tell startupCost
In order to calculate the cost of executing a builtin, we obtain a vector with
the size of each constant argument using extractArgSizes
.
Non-constant argument should only occur in places where the builtin is polymorphic and should be ignored by the cost model of the corresponding builtin. Therefore, they are added with a size of 0.
The function extractArgSizes
get the arguments in inverse order, so we reverse
them in the spendBuiltin
function.
extractConstants : ∀{b} → ∀{tn tm} → {pt : tn ∔ tm ≣ fv (signature b)} → ∀{an am} → {pa : an ∔ am ≣ args♯ (signature b)} → BApp b pt pa → Vec Value an extractConstants base = [] extractConstants (app bapp v) = v ∷ extractConstants bapp extractConstants (app⋆ bapp) = extractConstants bapp spendBuiltin : (b : Builtin) → fullyAppliedBuiltin b → CekM ⊤ spendBuiltin b bapp = tell (cekMachineCost (BBuiltinApp b argsizes)) where argsizes = reverse (extractConstants bapp)
Function step
performs a single step in the CEK machine.
stepC : State → CekM State -- computeCEK in the Haskell implementation stepC (s ; ρ ▻ ` x) = do spend BVar return (s ◅ lookup ρ x) stepC (s ; ρ ▻ ƛ t) = do spend BLamAbs return (s ◅ V-ƛ ρ t) stepC (s ; ρ ▻ (t · u)) = do spend BApply return ((s , -· ρ u) ; ρ ▻ t) stepC (s ; ρ ▻ force t) = do spend BForce return ((s , force-) ; ρ ▻ t) stepC (s ; ρ ▻ delay t) = do spend BDelay return (s ◅ V-delay ρ t) stepC (s ; ρ ▻ con (tmCon t c)) = do spend BConst return (s ◅ V-con t c) stepC (s ; ρ ▻ constr i []) = do spend BConstr return (s ◅ V-constr i ε) stepC (s ; ρ ▻ constr i (x ∷ xs)) = do spend BConstr return ((s , constr- i ε ρ xs); ρ ▻ x) stepC (s ; ρ ▻ case t ts) = do spend BCase return ((s , case- ρ ts) ; ρ ▻ t) stepC (s ; ρ ▻ builtin b) = do spend BBuiltin return (s ◅ ival b) stepC (s ; ρ ▻ error) = return ◆ -- return∁EK in the Haskell implementation stepC (ε ◅ v) = return (□ v) stepC ((s , -· ρ u) ◅ v) = return ((s , (v ·-)) ; ρ ▻ u) stepC ((s , -·v v) ◅ vf) = return ((s , vf ·-) ◅ v) stepC ((s , (V-ƛ ρ t ·-)) ◅ v) = return (s ; ρ ∷ v ▻ t) stepC ((s , (V-con _ _ ·-)) ◅ v) = return ◆ -- constant in function position stepC ((s , (V-delay _ _ ·-)) ◅ v) = return ◆ -- delay in function position stepC ((s , (V-IΠ b bapp ·-)) ◅ v) = return ◆ -- delayed builtin in function position stepC ((s , (V-constr i vs ·-)) ◅ v) = return ◆ -- SOP in function position stepC ((s , force-) ◅ V-IΠ b bapp) = return (s ◅ V-I b (app⋆ bapp)) stepC ((s , force-) ◅ V-delay ρ t) = stepC (s ; ρ ▻ t) -- this recursive call could be inlined -- in order to make evident that this is a single step -- but this would make the definition much longer. stepC ((s , force-) ◅ V-ƛ _ _) = return ◆ -- lambda in delay position stepC ((s , force-) ◅ V-con _ _) = return ◆ -- constant in delay position stepC ((s , force-) ◅ V-I⇒ b bapp) = return ◆ -- function in delay position stepC ((s , force-) ◅ V-constr i vs) = return ◆ -- SOP in delay position stepC ((s , constr- i vs ρ []) ◅ v) = return (s ◅ V-constr i (vs , v)) stepC ((s , constr- i vs ρ (x ∷ ts)) ◅ v) = return ((s , constr- i (vs , v) ρ ts); ρ ▻ x) stepC ((s , case- ρ ts) ◅ V-constr i vs) = return (maybe (pushValueFrames s vs ; ρ ▻_) ◆ (lookup? i ts)) stepC ((s , case- ρ ts) ◅ V-ƛ _ _) = return ◆ -- case of lambda stepC ((s , case- ρ ts) ◅ V-con _ _) = return ◆ -- case of constant stepC ((s , case- ρ ts) ◅ V-delay _ _) = return ◆ -- case of delay stepC ((s , case- ρ ts) ◅ V-I⇒ _ _) = return ◆ -- case of builtin value stepC ((s , case- ρ ts) ◅ V-IΠ _ _) = return ◆ -- case of delayed builtin stepC ((s , (V-I⇒ b {am = 0} bapp ·-)) ◅ v) = do --fully applied builtin function let bapp' = mkFullyAppliedBuiltin (app bapp v) -- proof that b is fully applied spendBuiltin b bapp' return (either (BUILTIN b bapp') (λ _ → ◆) (s ◅_)) stepC ((s , (V-I⇒ b {am = suc _} bapp ·-)) ◅ v) = --partially applied builtin function return (s ◅ V-I b (app bapp v)) stepC (□ v) = return (□ v) stepC ◆ = return ◆ stepperC-internal : ℕ → (s : State) → CekM (Either RuntimeError State) stepperC-internal 0 s = return (inj₁ gasError) stepperC-internal (suc n) s = do s' ← stepC s go s' where go : (t : State) → CekM (Either RuntimeError State) go (□ v) = return (inj₂ (□ v)) go ◆ = return (inj₂ ◆) go s' = stepperC-internal n s' stepperC : ℕ → (s : State) → CekM (Either RuntimeError State) stepperC n s = do spendStartupCost stepperC-internal n s
cekStepEquivalence : ∀ (s : State) → step s ≡ wrvalue (stepC s) cekStepEquivalence (s ; ρ ▻ ` _) = refl cekStepEquivalence (s ; ρ ▻ ƛ _) = refl cekStepEquivalence (s ; ρ ▻ (_ · _)) = refl cekStepEquivalence (s ; ρ ▻ force _) = refl cekStepEquivalence (s ; ρ ▻ delay _) = refl cekStepEquivalence (s ; ρ ▻ con (tmCon _ _)) = refl cekStepEquivalence (s ; ρ ▻ constr _ []) = refl cekStepEquivalence (s ; ρ ▻ constr _ (_ ∷ _)) = refl cekStepEquivalence (s ; ρ ▻ case _ _) = refl cekStepEquivalence (s ; ρ ▻ builtin _) = refl cekStepEquivalence (s ; ρ ▻ error) = refl cekStepEquivalence (ε ◅ _) = refl cekStepEquivalence ((s , -· _ _) ◅ _) = refl cekStepEquivalence ((s , -·v _) ◅ _) = refl cekStepEquivalence ((s , (V-ƛ _ _ ·-)) ◅ _) = refl cekStepEquivalence ((s , (V-con _ _ ·-)) ◅ _) = refl cekStepEquivalence ((s , (V-delay _ _ ·-)) ◅ _) = refl cekStepEquivalence ((s , (V-constr _ _ ·-)) ◅ _) = refl cekStepEquivalence ((s , (V-I⇒ b {am = zero} x ·-)) ◅ V) with BUILTIN' b (app x V) ... | inj₁ _ = refl ... | inj₂ _ = refl cekStepEquivalence ((s , (V-I⇒ _ {am = suc _} _ ·-)) ◅ _) = refl cekStepEquivalence ((s , (V-IΠ _ _ ·-)) ◅ _) = refl cekStepEquivalence ((s , force-) ◅ V-ƛ _ _) = refl cekStepEquivalence ((s , force-) ◅ V-con _ _) = refl cekStepEquivalence ((s , force-) ◅ V-delay ρ x) = cekStepEquivalence (s ; ρ ▻ x) cekStepEquivalence ((s , force-) ◅ V-constr _ _) = refl cekStepEquivalence ((s , force-) ◅ V-I⇒ _ _) = refl cekStepEquivalence ((s , force-) ◅ V-IΠ _ _) = refl cekStepEquivalence ((s , constr- _ _ _ []) ◅ _) = refl cekStepEquivalence ((s , constr- _ _ _ (_ ∷ _)) ◅ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-ƛ _ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-con _ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-delay _ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-constr _ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-I⇒ _ _) = refl cekStepEquivalence ((s , case- _ _) ◅ V-IΠ _ _) = refl cekStepEquivalence (□ _) = refl cekStepEquivalence ◆ = refl cekStepperEquivalence : ∀ n s → stepper n s ≡ wrvalue (stepperC n s) cekStepperEquivalence zero s = refl cekStepperEquivalence (suc n) s rewrite sym (cekStepEquivalence s) with step s ... | s ; ρ ▻ t = cekStepperEquivalence n (s ; ρ ▻ t) ... | s ◅ v = cekStepperEquivalence n (s ◅ v) ... | □ x = refl ... | ◆ = refl