Wiki Agenda Contact Version française

A tiny register allocator for tree expressions


Authors: Jean-Christophe Filliâtre / Martin Clochard

Topics: Semantics of languages

Tools: Why3

see also the index (by topic, by tool, by reference, by year)


A tiny register allocator for tree expressions.

Authors: Martin Clochard (École Normale Supérieure) Jean-Christophe Filliâtre (CNRS)

module Spec

  use int.Int

  type addr

  type expr =
  | Evar addr
  | Eneg expr
  | Eadd expr expr

  type memory = addr -> int

  function eval (m: memory) (e: expr) : int =
    match e with
    | Evar x     -> m x
    | Eneg e     -> - (eval m e)
    | Eadd e1 e2 -> eval m e1 + eval m e2
    end

  type register = int

  type instr =
    | Iload addr register
    | Ineg  register
    | Iadd  register register
    | Ipush register
    | Ipop  register

  type registers = register -> int

  function update (reg: registers) (r: register) (v: int) : registers =
    fun r' -> if r' = r then v else reg r'

  use list.List

  type stack = list int

  type state = {
    mem: memory;
    reg: registers;
    st : stack;
  }

  function exec (i: instr) (s: state) : state =
    match i with
    | Iload x r   -> { s with reg = update s.reg r (s.mem x) }
    | Ineg  r     -> { s with reg = update s.reg r (- s.reg r) }
    | Iadd  r1 r2 -> { s with reg = update s.reg r2 (s.reg r1 + s.reg r2) }
    | Ipush r     -> { s with st = Cons (s.reg r) s.st }
    | Ipop  r     -> match s.st with
                     | Nil       -> s (* fails *)
                     | Cons v st -> { s with reg = update s.reg r v; st = st }
                     end
    end
  meta rewrite_def function exec

  type code = list instr

  function exec_list (c: code) (s: state) : state =
    match c with
    | Nil      -> s
    | Cons i l -> exec_list l (exec i s)
    end

  use list.Append

  let rec lemma exec_append (c1 c2: code) (s: state) : unit
    ensures { exec_list (c1 ++ c2) s = exec_list c2 (exec_list c1 s) }
    variant { c1 }
  = match c1 with
    | Nil        -> ()
    | Cons i1 l1 -> exec_append l1 c2 (exec i1 s)
    end

  function expr_post (e: expr) (r: register) : state -> state -> bool =
    fun s s' -> s'.mem = s.mem /\ s'.reg r = eval s.mem e /\ s'.st = s.st /\
      forall r'. r' < r -> s'.reg r' = s.reg r'
  meta rewrite_def function expr_post

specification of the forthcoming compilation: - value of expression e lies in register r in final state - all registers smaller than are preserved - memory and stack are preserved

end

Double WP technique

If you read French, see https://hal.inria.fr/hal-01094488

See also this other Why3 proof, from where this technique originates: http://toccata.lri.fr/gallery/double_wp.en.html

module DWP

  use list.List
  use list.Append
  use Spec

  meta compute_max_steps 0x10000

  predicate (-->) (x y: 'a) = [@rewrite] x = y
  meta rewrite_def predicate (-->)

  type post = state -> state -> bool
  type hcode = {
    hcode : code;
    ghost post : post;
  }
  predicate hcode_ok (hc: hcode) = forall s. hc.post s (exec_list hc.hcode s)

  type trans = (state -> bool) -> state -> bool
  type wcode = {
    ghost trans : trans;
    wcode : code;
  }
  predicate wcode_ok (wc: wcode) = forall q s.
    wc.trans q s -> q (exec_list wc.wcode s)

  function to_wp (pst: post) : trans = fun q s1 -> forall s2. pst s1 s2 -> q s2
  meta rewrite_def function to_wp

  function rcompose : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c = fun f g x -> g (f x)
  meta rewrite_def function rcompose

  function exec_closure (i: instr) : state -> state = fun s -> exec i s
  function id : 'a -> 'a = fun x -> x

  let ($_) (hc: hcode) : wcode
    requires { hcode_ok hc }
    ensures { wcode_ok result }
    ensures { result.trans --> to_wp hc.post }
  = { wcode = hc.hcode; trans = to_wp hc.post }

  let wrap (wc: wcode) (ghost pst: post) : hcode
    requires { wcode_ok wc }
    requires { forall x. wc.trans (pst x) x }
    ensures { hcode_ok result }
    ensures { result.post --> pst }
  = { hcode = wc.wcode; post = pst }

  let (--) (w1 w2: wcode) : wcode
    requires { wcode_ok w1 /\ wcode_ok w2 }
    ensures { wcode_ok result }
    ensures { result.trans --> rcompose w2.trans w1.trans }
  = { wcode = w1.wcode ++ w2.wcode; trans = rcompose w2.trans w1.trans }

  let cons (i: instr) (w: wcode) : wcode
    requires { wcode_ok w }
    ensures { wcode_ok result }
    ensures { result.trans --> rcompose w.trans (rcompose (exec i)) }
  = { wcode = Cons i w.wcode;
      trans = rcompose w.trans (rcompose (exec_closure i)) }

  let nil () : wcode
    ensures { wcode_ok result }
    ensures { result.trans --> fun q -> q }
  = { wcode = Nil; trans = id }

end

module InfinityOfRegisters

  use int.Int
  use list.List
  use list.Append
  use Spec
  use DWP

compile e r returns a list of instructions that stores the value of e in register r, without modifying any register r' < r.

  let rec compile (e: expr) (r: register) : hcode
    variant { e }
    ensures { hcode_ok result }
    ensures { result.post --> expr_post e r }
  = wrap (
      match e with
      | Evar x -> cons (Iload x r) (nil ())
      | Eneg e -> $ compile e r -- cons (Ineg r) (nil ())
      | Eadd e1 e2 ->
          $ compile e1 r -- $ compile e2 (r + 1) -- cons (Iadd (r+1) r) (nil ())
      end) (expr_post e r)

  (* To recover usual specification. *)
  let ghost recover (e: expr) (r: register) (h: hcode) : unit
    requires { hcode_ok h /\ h.post --> expr_post e r }
    ensures  { forall s. let s' = exec_list h.hcode s in
               s'.mem = s.mem /\
               s'.reg r = eval s.mem e /\
               s'.st = s.st /\
               forall r'. r' < r -> s'.reg r' = s.reg r' }
  = ()

end

module FiniteNumberOfRegisters

  use int.Int
  use list.List
  use list.Append
  use Spec
  use DWP

  val constant k: int
    ensures { 2 <= result }

we have k registers, namely 0,1,...,k-1, and there are at least two of them, otherwise we can't add

compile e r returns a list of instructions that stores the value of e in register r, without modifying any register r' < r.

  let rec compile (e: expr) (r: register) : hcode
    requires { 0 <= r < k }
    variant  { e }
    ensures  { hcode_ok result }
    ensures  { result.post --> expr_post e r }
  = wrap (
      match e with
      | Evar x -> cons (Iload x r) (nil ())
      | Eneg e -> $ compile e r -- cons (Ineg r) (nil ())
      | Eadd e1 e2 ->
          if r < k-1 then
            $ compile e1 r -- $ compile e2 (r + 1) --
            cons (Iadd (r + 1) r) (nil ())
          else
            cons (Ipush (k - 2)) (
            $ compile e1 (k - 2) -- $ compile e2 (k - 1) --
            cons (Iadd (k - 2) (k - 1)) (
            cons (Ipop (k - 2)) (nil ())))
      end) (expr_post e r)

end

module OptimalNumberOfRegisters

  use int.Int
  use int.MinMax
  use list.List
  use list.Append
  use Spec
  use DWP

  val constant k: int
    ensures { 2 <= result }

we have k registers, namely 0,1,...,k-1, and there are at least two of them, otherwise we can't add

  let rec function n (e: expr) : int
  variant { e }
  = match e with
    | Evar _     -> 1
    | Eneg e     -> n e
    | Eadd e1 e2 -> let n1 = n e1 in let n2 = n e2 in
                    if n1 = n2 then 1 + n1 else max n1 n2
    end

the minimal number of registers needed to evaluate e

Note: This is of course inefficient to recompute function n many times. A realistic implementation would compute n e once for each sub-expression e, either with a first pass of tree decoration, or with function compile returning the value of n e as well, in a bottom-up way

  function measure (e: expr) : int =
    match e with
    | Evar _     -> 0
    | Eneg e     -> 1 + measure e
    | Eadd e1 e2 -> 1 + if n e1 >= n e2 then measure e1 + measure e2
                        else 1 + measure e1 + measure e2
    end

  lemma measure_nonneg: forall e. measure e >= 0

compile e r returns a list of instructions that stores the value of e in register r, without modifying any register r' < r.

  let rec compile (e: expr) (r: register) : hcode
    requires { 0 <= r < k }
    variant  { measure e }
    ensures  { hcode_ok result }
    ensures  { result.post --> expr_post e r }
  = wrap (
      match e with
      | Evar x -> cons (Iload x r) (nil ())
      | Eneg e -> $ compile e r -- cons (Ineg r) (nil ())
      | Eadd e1 e2 ->
          if n e1 >= n e2 then (* we must compile e1 first *)
            if r < k-1 then
              $ compile e1 r -- $ compile e2 (r + 1) --
              cons (Iadd (r + 1) r) (nil ())
            else
              cons (Ipush (k - 2)) (
              $ compile e1 (k - 2) -- $ compile e2 (k - 1) --
              cons (Iadd (k - 2) (k - 1)) (
              cons (Ipop (k - 2)) (nil ())))
          else
            $ compile (Eadd e2 e1) r (* compile e2 first *)
      end) (expr_post e r)

end

download ZIP archive

Why3 Proof Results for Project "register_allocation"

Theory "register_allocation.Spec": fully verified

ObligationsAlt-Ergo 2.1.0
VC for exec_append0.01

Theory "register_allocation.DWP": fully verified

ObligationsAlt-Ergo 2.1.0
VC for prefix $0.01
VC for wrap0.01
VC for infix --0.01
VC for cons0.11
VC for nil0.01

Theory "register_allocation.InfinityOfRegisters": fully verified

ObligationsAlt-Ergo 2.1.0
VC for compile---
split_goal_right
precondition0.01
precondition0.01
variant decrease0.01
precondition0.01
precondition0.01
precondition0.01
variant decrease0.02
precondition0.02
variant decrease0.02
precondition0.01
precondition0.02
precondition0.01
precondition0.00
precondition---
split_goal_right
precondition---
compute_specified
precondition---
simplify_trivial_quantification_in_goal
precondition---
introduce_premises
precondition---
compute_specified
precondition0.01
precondition---
compute_specified
precondition---
simplify_trivial_quantification_in_goal
precondition---
introduce_premises
precondition---
compute_specified
precondition0.02
precondition---
compute_specified
precondition---
simplify_trivial_quantification_in_goal
precondition---
introduce_premises
precondition---
compute_specified
precondition0.01
postcondition0.02
postcondition0.01
VC for recover0.02

Theory "register_allocation.FiniteNumberOfRegisters": fully verified

ObligationsAlt-Ergo 2.1.0Alt-Ergo 2.3.0
VC for k---0.01
VC for compile------
split_goal_right
precondition0.01---
precondition0.01---
variant decrease0.03---
precondition0.01---
precondition0.01---
precondition0.02---
precondition0.01---
variant decrease0.02---
precondition0.01---
precondition0.01---
variant decrease0.02---
precondition0.01---
precondition0.01---
precondition0.01---
precondition0.02---
precondition0.01---
precondition0.01---
variant decrease0.03---
precondition0.01---
precondition0.01---
variant decrease0.02---
precondition0.02---
precondition0.01---
precondition0.02---
precondition0.02---
precondition0.03---
precondition0.01---
precondition------
split_goal_right
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.01---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.02---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.05---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.01---
postcondition0.03---
postcondition0.03---

Theory "register_allocation.OptimalNumberOfRegisters": fully verified

ObligationsAlt-Ergo 2.1.0Alt-Ergo 2.3.0
VC for k---0.00
VC for n0.03---
measure_nonneg------
induction_ty_lex
measure_nonneg.00.02---
VC for compile------
split_goal_right
precondition0.01---
precondition0.01---
variant decrease0.03---
precondition0.01---
precondition0.01---
precondition0.02---
precondition0.02---
variant decrease0.03---
precondition0.01---
precondition0.01---
variant decrease0.03---
precondition0.02---
precondition0.02---
precondition0.01---
precondition0.02---
precondition0.01---
precondition0.01---
variant decrease0.02---
precondition0.01---
precondition0.01---
variant decrease0.02---
precondition0.01---
precondition0.01---
precondition0.01---
precondition0.01---
precondition0.02---
variant decrease0.02---
precondition0.01---
precondition0.01---
precondition0.02---
precondition------
split_goal_right
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.01---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.06---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.01---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.03---
precondition------
compute_specified
precondition------
simplify_trivial_quantification_in_goal
precondition------
introduce_premises
precondition------
compute_specified
precondition0.02---
postcondition0.02---
postcondition0.02---