A flat reachability-based measure for CakeML’s cost semantics


🗣Alejandro GΓ³mez-LondoΓ±o
Magnus O. Myreen
  • Functional language
  • Proven-correct compiler
  • Verified cost semantics

    fun yes = (print "yes"; yes)
  

\[\neg\]

πŸ’₯

πŸ™‚

You can prove it!

πŸ™ƒ

It's complicated...

The aim of this work is to simplify this process


is_safe(dataLang)
==>
sem(CakeML)

<->

sem(machine)
dataLang


         v = Number  int
           | Word64  word64
           | Block   ts tag (v list)  -- ts = tag = num
           | CodePtr num
           | RefPtr  num
    

γ€˜

[1,2,3]

γ€™



      Block 67 cons_tag [Number 1;
        Block 66 cons_tag [Number 2;
          Block 65 cons_tag [Number 3;
            Block 0 nil_tag []]]]
    
evaluate (prog,s) = (res,s')
evaluate (prog,s) = (res,s')


      state = <| clock            : num
               ; locals           : v num_map
               ; stack            : stack list
               ; refs             : v ref num_map
               ; global           : num option
               ; limits           : limits
               ; safe_for_space   : bool
               ...
               |>
    

A measurement of space


        safe_for_space :=
          s.safe_for_space
          ∧ size_of_heap s + k <=  s.limits.heap_limit
      

size_of_heap s
=
size_of reachabe_values s.refs {}


       <|s.locals|> ++ <|s.stack|> ++ <|s.global|>
      
size_of vals refs seen

Where:


    size_of lims []         refs seen = 0
    size_of lims (x::y::ys) refs seen =
      let (n1,refs1,seen1) = size_of' lims (y::ys) refs seen
      let (n2,refs2,seen2) = size_of' lims [x] refs1 seen1
      in n1+n2
    ...
  

    size_of [Block 65 some_tag [Number 1];
             Block 65 some_tag [Number 1]]
            refs seen
  

    2 +
    size_of [Block 65 some_tag [Number 1]]
            refs ({65} βˆͺ seen)
  

    2 + 0
  
size_of vals refs seen

How does one prove space safety?


    ftail []      = []
    ftail (x::xs) = ...
      ftail xs
  

     size_of_heap s + C s <= s.limits.heap_limit
     ∧ s.safe_for_space
     ∧ s.locals = ...
     ∧ ... ==>
     is_safe_for_space (ftail_body,s)
  

Where:

Proof recipe




      size_of_heap s + C s <= s.limits.heap_limit
      ∧ ... ⟹
      size_of_heap s' + C s' <= s'.limits.heap_limit
    

β”œβ”€β”€β”€ ftail [1,2,3] ──── <= heap_limit

β”œβ”€β”€β”€ ftail [2,3] ──── <= heap_limit

β”œβ”€β”€β”€ ftail [3]  ──── <= heap_limit

β”œβ”€β”€β”€ ftail []  ──── <= heap_limit
ftail [1,2,...,n]


    size_of (γ€˜[1,2,...,n]γ€™: rest) s.refs {}
      <= s.limits.heap_limit
    ∧ ...
    ⟹
    size_of (γ€˜[2,...,n]γ€™: rest) s'.refs {}
      <= s'.limits.heap_limit
  

    size_of (γ€˜[2,...,n]γ€™: rest) s.refs {}

    <=

    size_of (γ€˜[1,2,..,n]γ€™: rest) s.refs {}
  

    β”œβ”€β”€[2,...,n]──┼───rest────

    <=

    β”œβ”€β”€[1,2,...,n]──┼───rest────
  

    size_of' rest s.refs {} = (n,refs,seen)
  


    n + size_of γ€˜[2,...,n]γ€™ refs seen

    <=

    n + size_of γ€˜[1,2,..,n]γ€™ refs seen
  

    size_of' rest s.refs {} = (n,refs,seen)
  


    size_of [Block 66 cons_tag [Number 2;...]] refs seen

    <=

    size_of [Block 67 cons_tag [Number 1;
               Block 66 cons_tag [Number 2;...]]] refs seen
  
67 ∈ seen

?

67 ∈ seen
67 ∈ seen


      size_of [Block 67 ...] refs seen = 0
    

      size_of [Block 66 ...] refs seen
      <=
      size_of [Block 67 ...] refs seen
    

      size_of [Block 66 ...] refs seen <= 0
    
67 βˆ‰ seen
67 βˆ‰ seen


      size_of [Block 67 ...] ...
        = 3 + size_of [Block 66 ...] refs ({67} βˆͺ seen)
    
67 βˆ‰ seen


      size_of [Block 67 ...] ...
        = 3 + size_of [Block 66 ...] refs ({67} βˆͺ seen)
    

      size_of [Block 66 ...] refs seen
      <=
      size_of [Block 66 ...] refs ({67} βˆͺ seen)
    

      size_of [Block 66 ...] refs seen
      <=
      size_of [Block 66 ...] refs ({67} βˆͺ seen)
    

      size_of [Block 66 cons_tag [Number 2;
                Block 67 ...]]
              refs seen
      <=
      size_of [Block 66 cons_tag [Number 2;
                 Block 67 ...]]
              refs ({67} βˆͺ seen)
     

🀦

size_of vals refs seen

A flat reachability-based measure

flat_size_of refs blocks roots

Where:

The set of all reachable values


    <|s.locals|> ++ <|s.stack|> ++ <|s.global|>
  


    [
     Block 71 some_tag [Number 1; Block 74 ...];
     Number 57;
     Word64 0xF5CA15;
     Word64 0x01368E;
     Word64 0xC81026;
     RefPtr 45;
     Block 75 some_tag [RefPtr 84; ...];
    ]
  

The set of all reachable values addresses


  addr = TStamp num  -- Blocks
       | RStamp num  -- Pointers

  to_addrs [] = βˆ…
  to_addrs (Block ts _ _::xs) = {TStamp ts} βˆͺ to_addrs xs
  to_addrs (RefPtr ptr  ::xs) = {RStamp ts} βˆͺ to_addrs xs
  to_addrs (_           ::xs) =               to_addrs xs

  to_addrs ([
   Block 71 some_tag [Number 1; Block 74 ...];
   Number 57;
   Word64 0xF5CA15;
   Word64 0x01368E;
   Word64 0xC81026;
   RefPtr 45;
   Block 75 some_tag [RefPtr 84; ...];
  ])
  

    {TStamp 71; RStamp 45; TStamp 75}
  

    to_addrs ([
     Block 71 some_tag [Number 1; Block 74 ...];
     Number 57;
     Word64 0xF5CA15;
     Word64 0x01368E;
     Word64 0xC81026;
     RefPtr 45;
     Block 75 some_tag [RefPtr 84; ...];
    ])
    

      TStamp 71 -> TStamp 74
      TStamp 75 -> RStamp 84
      RStamp 45 -> ...
    

    next refs blocks (TStamp ts) addr =
      case lookup ts blocks of
        SOME (Block ts tag vs) => addr ∈ to_addrs vs
        NONE                   => F
    next refs blocks (RStamp ptr) addr =
      ...
  

The set of all reachable addresses

=


      reachable_addrs refs blocks roots =
        { y | βˆƒx. x ∈ roots ∧ RTC (next refs blocks) x y }
    

Now we measure!


  flat_measure (Word64 _) = 3
  flat_measure (Number i) =
     if small_num i
     then 0
     else ... -- big num stuff
  flat_measure _ = 0
 

  addrs_measure refs blocks (TStamp ts) =
    case lookup ts blocks of
    | SOME (Block _ _ vs) =>
        1 + LENGTH vs + SUM (MAP flat_measure vs)
    | _ => 0
  addrs_measure refs blocks (RStamp p) =
    ...
 

    flat_measure refs blocks roots =
      SUM (MAP flat_measure roots) +
        𝚺 (addrs_measure refs blocks)
          (reachable_addrs refs blocks (to_addrs roots))
  

    flat_size_of refs blocks
      [Block 65 some_tag [Number 1];
         Block 65 some_tag [Number 1]]
  

    𝚺 (addrs_measure refs blocks) {TStamp 65}
  

    addrs_measure refs blocks (TStamp 65)
  

    addrs_measure refs blocks (TStamp 65)
  

    lookup 65 blocks = SOME (Block 65 some_tag [Number 1])
    ->
    1 + LENGTH [Number 1] + flat_measure (Number 1)
  

    1 + 1 + 0
  

      flat_size_of (γ€˜[2,...,n]γ€™: rest) s.refs s.all_blocks

      <=

      flat_size_of (γ€˜[1,2,..,n]γ€™: rest) s.refs s.all_blocks
    

      a βŠ† b
      ==>
      𝚺 f a <= 𝚺 f b
    

      reachable_addrs refs blocks
        (Block 66 ... : rest)

      βŠ†

      reachable_addrs refs blocks
        (Block 67 cons_tag [Number 1;
           Block 66 ...] : rest)

    

      reachable_addrs refs blocks rest βˆͺ
        {TStamp 66; ...}

      βŠ†

      reachable_addrs refs blocks rest βˆͺ
        {TStamp 67;TStamp 66; ...}
    

πŸŽ‰

flat_size_of refs blocks roots