(*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

(* Proofs about llvm to llair translation that don't involve the semantics *)

open HolKernel boolLib bossLib Parse lcsymtacs;
open listTheory arithmeticTheory pred_setTheory finite_mapTheory;
open optionTheory rich_listTheory alistTheory pairTheory sumTheory;
open settingsTheory miscTheory;
open llvmTheory llvm_propTheory llvm_ssaTheory llairTheory llair_propTheory llvm_to_llairTheory;

new_theory "llvm_to_llair_prop";

set_grammar_ancestry ["llvm", "llair", "llair_prop", "llvm_to_llair", "llvm_ssa"];

numLib.prefer_num ();

(* ----- Theorems about prog_ok ----- *)

Theorem prog_ok_terminator_last:
  ∀prog d f.
    prog_ok prog ∧
    alookup prog (Fn f) = Some d
    ⇒
    every (λ(l,b). ∀i. i < length b.body ∧ classify_instr (el i b.body) = Term ⇒ Suc i = length b.body) d.blocks
Proof
  rw [prog_ok_def] >> rpt (first_x_assum drule) >>
  rw [] >> fs [EVERY_MEM] >> rw [] >>
  pairarg_tac >> rw [] >>
  `mem (Fn f,d) prog` by metis_tac [ALOOKUP_MEM] >>
  last_x_assum drule >> rw [] >> fs [] >>
  `alookup d.blocks l = Some b` by metis_tac [ALOOKUP_ALL_DISTINCT_MEM] >>
  last_x_assum drule >> rw [] >>
  CCONTR_TAC >> fs [] >>
  `Suc i < length b.body` by decide_tac >> fs [] >>
  `terminator (el i b.body)`
  by (
    Cases_on `el i b.body` >> fs [terminator_def, classify_instr_def] >>
    Cases_on `p` >> fs [terminator_def, classify_instr_def]) >>
  `~mem (el i b.body) (front b.body)` by metis_tac [] >>
  metis_tac [mem_el_front]
QED

Theorem prog_ok_nonterm:
  ∀prog i ip.
    prog_ok prog ∧ get_instr prog ip (Inl i) ∧ ¬terminator i ⇒ inc_pc ip ∈ next_ips prog ip
Proof
  rw [next_ips_cases, IN_DEF, get_instr_cases, PULL_EXISTS] >>
  `terminator (last b.body) ∧ b.body ≠ []` by metis_tac [prog_ok_def] >>
  Cases_on `length b.body = idx + 1`
  >- (
    drule LAST_EL >>
    rw [] >> fs [DECIDE ``PRE (x + 1) = x``]) >>
  Cases_on `el idx b.body` >>
  fs [instr_next_ips_def, terminator_def] >>
  rw [EXISTS_OR_THM, inc_pc_def, inc_bip_def]
QED

(* ----- The invariant on emaps ----- *)
(* Essentially that the emap is what the llvm->llair translation generates in
  * the end. Such emaps enjoy the nice property that they don't change when used
  * as the input to a translation function, since they have all of the bindings
  * in them already. *)
Definition untranslate_reg_def:
  untranslate_reg (Var_name x t) = Reg x
End

Definition not_exp_def:
  not_exp x regs_to_keep ⇔
    (∀r t. x ≠ Exp r t) ∨ (∃r t. x = Exp r t ∧ r ∈ regs_to_keep)
End

(* This captures the variable->expression mapping that the compiler will have
 * built *)
Definition good_emap_def:
  good_emap f prog regs_to_keep gmap emap ⇔
    ∀ip.
      f = ip.f ⇒
      (∀r t inst.
        get_instr prog ip (Inl inst) ∧ classify_instr inst = Exp r t ∧ r ∉ regs_to_keep
        ⇒
        flookup emap r = Some (translate_instr_to_exp gmap emap inst)) ∧
      (∀inst phis.
        (get_instr prog ip (Inl inst) ∧ not_exp (classify_instr inst) regs_to_keep ∨
         get_instr prog ip (Inr phis))
        ⇒
        ∀rt. rt ∈ assigns prog ip ⇒
          flookup emap (fst rt) = Some (Var (translate_reg (fst rt) (snd rt)) F)) ∧
      (* for each r |-> e mapping in emap, each register e is dominated by an
       * assignment to that register for the entire live range of r *)
      (∀r e r'. flookup emap r = Some e ∧ r ∈ live prog ip ∧ r' ∈ exp_uses e ⇒
        ∃ip2. untranslate_reg r' ∈ image fst (assigns prog ip2) ∧ dominates prog ip2 ip)
End

Theorem good_emap_header_unchanged:
  ∀f prog r gmap emap phis land from l.
    good_emap f prog r gmap emap ∧
    get_instr prog <| f := f; b := l; i := Phi_ip from |> (Inr (from, phis))
    ⇒
    emap |++ header_to_emap_upd (Head phis land) = emap
Proof
  rw [good_emap_def] >>
  irule fupdate_list_elim >> simp [header_to_emap_upd_def, MEM_MAP, PULL_EXISTS] >>
  rpt gen_tac >> CASE_TAC >> rw [] >>
  first_x_assum (qspec_then `<|f := f; b := l; i := Phi_ip from|>` mp_tac) >>
  rw [] >> res_tac >>
  rename1 `flookup _ r1 = Some (Var (translate_reg r1 ty) _)` >>
  first_x_assum (qspec_then `(r1,ty)` mp_tac) >> simp [] >>
  disch_then irule >>
  rw [assigns_cases, IN_DEF] >> disj2_tac >>
  qexists_tac `from` >> qexists_tac `phis` >> rw [LIST_TO_SET_MAP] >>
  metis_tac [phi_assigns_def]
QED

Theorem fupdate_elim_lookup:
  ∀k v f. flookup f k = Some v ⇒ f |+ (k,v) = f
Proof
  rw [FLOOKUP_DEF] >> metis_tac [FUPDATE_ELIM]
QED

Theorem good_emap_translate_unchanged_lem:
  ∀f prog gmap emap regs_to_keep bl bs emap' l i d j.
    good_emap (Fn f) prog regs_to_keep gmap emap ∧
    alookup prog (Fn f) = Some d ∧
    alookup d.blocks l = Some bl ∧
    j ≤ length bl.body ∧
    translate_instrs (Lab_name f (option_map dest_label l) i) gmap emap regs_to_keep (drop j bl.body) = (bs,emap')
    ⇒
    emap = emap'
Proof
  Induct_on `length bl.body - j` >>
  rw []
  >- (
    `length bl.body = j` by decide_tac >>
    rw [] >> fs [DROP_LENGTH_NIL, translate_instrs_def]) >>
  first_x_assum (qspecl_then [`bl`, `j + 1`] mp_tac) >> simp [] >>
  rpt (disch_then drule) >>
  `j < length bl.body` by decide_tac >>
  fs [DROP_EL_CONS, translate_instrs_def] >>
  BasicProvers.EVERY_CASE_TAC >> fs [] >>
  TRY (pairarg_tac >> fs []) >> disch_then irule >>
  rw [] >> fs [inc_label_def]
  >- (
    `emap |+ (r,Var (translate_reg r t) F) = emap` suffices_by metis_tac [] >>
    irule fupdate_elim_lookup >>
    fs [good_emap_def] >>
    first_x_assum (qspec_then `<| f := Fn f; b := l; i := Offset j |>` mp_tac) >>
    rw [get_instr_cases, not_exp_def] >>
    first_x_assum (qspec_then `(r,t)` mp_tac) >> simp [] >>
    disch_then irule >> simp [assigns_cases, IN_DEF] >>
    disj1_tac >> qexists_tac `el j bl.body` >>
    rw [get_instr_cases] >>
    Cases_on `el j bl.body` >> fs [classify_instr_def, instr_assigns_def] >>
    Cases_on `p` >> fs [classify_instr_def, instr_assigns_def])
  >- (
    `emap |+ (r,translate_instr_to_exp gmap emap (el j bl.body)) = emap`
    suffices_by metis_tac [] >>
    irule fupdate_elim_lookup >>
    fs [good_emap_def] >>
    first_x_assum (qspec_then `<| f := Fn f; b := l; i := Offset j |>` mp_tac) >>
    rw [get_instr_cases, not_exp_def])
  >- (
    `extend_emap_non_exp emap (el j bl.body) = emap` suffices_by metis_tac [] >>
    Cases_on `el j bl.body` >> rw [extend_emap_non_exp_def] >>
    irule fupdate_elim_lookup >>
    fs [good_emap_def] >>
    first_x_assum (qspec_then `<| f := Fn f; b := l; i := Offset j |>` mp_tac) >>
    rw [get_instr_cases, not_exp_def] >>
    qmatch_goalsub_abbrev_tac `translate_reg _ t'` >>
    first_x_assum (qspec_then `(r,t')` mp_tac) >> simp [] >>
    disch_then irule >> simp [assigns_cases, IN_DEF] >>
    disj1_tac >> qexists_tac `el j bl.body` >>
    rw [get_instr_cases] >>
    fs [classify_instr_def, instr_assigns_def])
  >- (
    `extend_emap_non_exp emap  (el j bl.body) = emap` suffices_by metis_tac [] >>
    Cases_on `el j bl.body` >> rw [extend_emap_non_exp_def] >>
    irule fupdate_elim_lookup >>
    fs [good_emap_def] >>
    first_x_assum (qspec_then `<| f := Fn f; b := l; i := Offset j |>` mp_tac) >>
    rw [get_instr_cases, not_exp_def] >>
    qmatch_goalsub_abbrev_tac `translate_reg _ t'` >>
    first_x_assum (qspec_then `(r,t')` mp_tac) >> simp [] >>
    disch_then irule >> simp [assigns_cases, IN_DEF] >>
    disj1_tac >> qexists_tac `el j bl.body` >>
    rw [get_instr_cases] >>
    fs [classify_instr_def, instr_assigns_def])
QED

Theorem good_emap_translate_unchanged:
  ∀f prog gmap emap regs_to_keep bl bs emap' l i d.
    good_emap (Fn f) prog regs_to_keep gmap emap ∧
    alookup prog (Fn f) = Some d ∧
    alookup d.blocks l = Some bl ∧
    translate_instrs (Lab_name f (option_map dest_label l) i) gmap emap regs_to_keep bl.body = (bs,emap')
    ⇒
    emap = emap'
Proof
  metis_tac [good_emap_translate_unchanged_lem, DECIDE ``!x. 0 ≤ length x``, DROP_0]
QED

(* ------ Reasoning about emaps that are equal up to the live variables ----- *)
(* We can then prove that the translation is invariant under switching similar
 * emaps *)

Definition similar_emap_def:
  similar_emap live emap1 emap2 ⇔
    DRESTRICT emap1 live = DRESTRICT emap2 live
End

Theorem similar_emap_sym:
  ∀s emap1 emap2. similar_emap s emap1 emap2 ⇔ similar_emap s emap2 emap1
Proof
  rw [similar_emap_def] >> metis_tac []
QED

Theorem similar_emap_subset:
  ∀s1 s2 emap1 emap2.
    similar_emap s1 emap1 emap2 ∧
    s2 ⊆ s1
    ⇒
    similar_emap s2 emap1 emap2
Proof
  rw [similar_emap_def, SUBSET_DEF, fmap_eq_flookup, FLOOKUP_DRESTRICT] >> rw [] >>
  last_x_assum (qspec_then `x` mp_tac) >> rw []
QED

Theorem extend_similar_emap:
  ∀s emap1 emap2 s2 l.
    similar_emap s emap1 emap2 ∧
    set (map fst l) = s2
    ⇒
    similar_emap (s ∪ s2) (emap1 |++ l) (emap2 |++ l)
Proof
  rw [similar_emap_def, fmap_eq_flookup, FLOOKUP_DRESTRICT] >>
  rw []
  >- (first_x_assum (qspec_then `x` mp_tac) >> rw [flookup_fupdate_list]) >>
  rw [flookup_fupdate_list] >>
  CASE_TAC >> fs [ALOOKUP_NONE, MEM_MAP] >> rfs [] >>
  metis_tac []
QED

Theorem similar_emap_lemma:
  ∀s emap1 emap2 l.
    similar_emap s emap1 emap2
    ⇒
    similar_emap s (emap1 |++ l) (emap2 |++ l)
Proof
  rw [similar_emap_def]
QED

Theorem similar_emap_diff:
  ∀s emap1 emap2 l s2.
    similar_emap (s DIFF s2) emap1 emap2 ∧
    s2 = set (map fst l)
    ⇒
    similar_emap s (emap1 |++ l) (emap2 |++ l)
Proof
  rw [similar_emap_def, fmap_eq_flookup, FLOOKUP_DRESTRICT] >> rw [] >>
  first_x_assum (qspec_then `x` mp_tac) >>
  rw [flookup_fupdate_list] >>
  CASE_TAC >> rw [] >> fs [ALOOKUP_NONE, MEM_MAP] >>
  metis_tac []
QED

Theorem extend_similar_emap2:
  ∀inst is emap1 emap2 reg t e.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    instr_assigns inst = {(reg, t)}
    ⇒
    similar_emap (fst (instrs_live is)) (emap1 |+ (reg,e)) (emap2 |+ (reg,e))
Proof
  rw [similar_emap_def, instrs_live_def, DRESTRICT_FUPDATE, fmap_eq_flookup,
      FLOOKUP_UPDATE, FLOOKUP_DRESTRICT] >>
  CASE_TAC >> rw [] >>
  first_x_assum (qspec_then `x` mp_tac) >> rw [] >>
  pairarg_tac >> fs [] >> fs []
QED

Theorem extend_similar_emap3:
  ∀inst emap1 emap2 reg t e.
    similar_emap {reg} (emap1 |+ (reg,e)) (emap2 |+ (reg,e))
Proof
  rw [similar_emap_def, fmap_eq_flookup, FLOOKUP_UPDATE, FLOOKUP_DRESTRICT]
QED

Theorem extend_similar_emap4:
  ∀inst emap1 emap2 is.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    classify_instr inst = Non_exp
    ⇒
    similar_emap (fst (instrs_live is)) (extend_emap_non_exp emap1 inst) (extend_emap_non_exp emap2 inst)
Proof
  rw [similar_emap_def, instrs_live_def, DRESTRICT_FUPDATE, fmap_eq_flookup,
      FLOOKUP_DRESTRICT] >>
  CASE_TAC >> rw [] >>
  first_x_assum (qspec_then `x` mp_tac) >> rw [] >>
  pairarg_tac >> fs [] >> fs [] >>
  Cases_on `inst` >>
  rw [extend_emap_non_exp_def, FLOOKUP_UPDATE] >>
  fs [instr_assigns_def, instr_uses_def, classify_instr_def] >> rw [] >>
  fs [] >>
  Cases_on `p` >> fs [classify_instr_def]
QED

Theorem similar_emap_union:
  ∀s1 s2 emap1 emap2.
    similar_emap (s1 ∪ s2) emap1 emap2 ⇔
    similar_emap s1 emap1 emap2 ∧ similar_emap s2 emap1 emap2
Proof
  rw [similar_emap_def, fmap_eq_flookup, FLOOKUP_DRESTRICT] >>
  metis_tac []
QED

Theorem similar_emap_insert:
  ∀s1 s2 emap1 emap2.
    similar_emap (s1 INSERT s2) emap1 emap2 ⇔
    similar_emap {s1} emap1 emap2 ∧ similar_emap s2 emap1 emap2
Proof
  rw [similar_emap_def, fmap_eq_flookup, FLOOKUP_DRESTRICT] >>
  metis_tac []
QED

Theorem similar_emap_translate_arg:
  ∀gmap emap1 a s emap2.
    similar_emap s emap1 emap2 ∧
    arg_to_regs a ⊆ s
    ⇒
    translate_arg gmap emap1 a = translate_arg gmap emap2 a
Proof
  Cases_on `a` >> rw [translate_arg_def] >>
  fs [arg_to_regs_def, similar_emap_def, fmap_eq_flookup, FLOOKUP_DRESTRICT] >>
  first_x_assum (qspec_then `r` mp_tac) >> rw []
QED

Theorem similar_emap_translate_instr_to_exp:
  ∀gmap emap1 inst is emap2 r t.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    classify_instr inst = Exp r t ∧
    is_implemented inst
    ⇒
    translate_instr_to_exp gmap emap1 inst = translate_instr_to_exp gmap emap2 inst
Proof
  ho_match_mp_tac translate_instr_to_exp_ind >>
  rw [translate_instr_to_exp_def, classify_instr_def, instrs_live_def] >>
  pairarg_tac >> fs [instr_uses_def, instr_assigns_def] >>
  drule similar_emap_translate_arg >>
  TRY (disch_then irule) >>
  rw [SUBSET_DEF] >>
  fs [is_implemented_def]
QED

Theorem similar_emap_translate_instr_to_inst:
  ∀gmap emap1 inst is emap2 r t.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    classify_instr inst = Non_exp ∧
    is_implemented inst
    ⇒
    translate_instr_to_inst gmap emap1 inst = translate_instr_to_inst gmap emap2 inst
Proof
  ho_match_mp_tac translate_instr_to_inst_ind >>
  rw [translate_instr_to_inst_def, classify_instr_def, instrs_live_def] >>
  pairarg_tac >> fs [instr_uses_def, instr_assigns_def] >>
  drule similar_emap_translate_arg >>
  TRY (disch_then irule) >>
  rw [SUBSET_DEF]
  >- (rename1 `Extractvalue _ x _` >> Cases_on `x` >> fs [classify_instr_def])
  >- (rename1 `Insertvalue _ x _ _` >> Cases_on `x` >> fs [classify_instr_def]) >>
  fs [is_implemented_def]
QED

Theorem similar_emap_translate_instr_to_term:
  ∀inst l gmap emap1 is emap2 r t.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    classify_instr inst = Term ∧
    is_implemented inst
    ⇒
    translate_instr_to_term l gmap emap1 inst = translate_instr_to_term l gmap emap2 inst
Proof
  ho_match_mp_tac classify_instr_ind >>
  rw [translate_instr_to_term_def, classify_instr_def, instrs_live_def,
      instr_uses_def]
  >- fs [is_implemented_def]
  >- (
    CASE_TAC >> rw [] >>
    irule similar_emap_translate_arg >>
    pairarg_tac >>
    fs [similar_emap_union, SUBSET_DEF] >>
    metis_tac [])
  >- fs [is_implemented_def]
  >- (
    irule similar_emap_translate_arg >>
    pairarg_tac >>
    fs [similar_emap_union, SUBSET_DEF] >>
    metis_tac [])
  >- fs [is_implemented_def]
QED

Theorem similar_emap_translate_call:
  ∀inst gmap emap1 is emap2 ret exret.
    similar_emap (fst (instrs_live (inst::is))) emap1 emap2 ∧
    classify_instr inst = Call ∧
    is_implemented inst
    ⇒
    translate_call gmap emap1 ret exret inst = translate_call gmap emap2 ret exret inst
Proof
  ho_match_mp_tac classify_instr_ind >>
  rw [translate_call_def, classify_instr_def, instrs_live_def, instr_uses_def,
      instr_assigns_def] >>
  pairarg_tac >> fs [similar_emap_union]
  >- (
    irule LIST_EQ >> rw [EL_MAP] >>
    rename1 `el x l` >> Cases_on `el x l` >> fs [] >>
    irule similar_emap_translate_arg >>
    qexists_tac `bigunion (set (map (arg_to_regs ∘ snd) l))` >>
    rw [SUBSET_DEF, MEM_MAP, PULL_EXISTS] >>
    metis_tac [EL_MEM, SND])
  >- fs [is_implemented_def]
QED

Theorem translate_header_similar_emap:
  ∀f from l gmap emap1 emap2 h.
    similar_emap (header_uses h) emap1 emap2 ∧
    (l = None ⇔ h = Entry)
    ⇒
    translate_header f from l gmap emap1 h = translate_header f from l gmap emap2 h ∧
    similar_emap (header_uses h ∪ header_assigns h)
      (emap1 |++ header_to_emap_upd h)
      (emap2 |++ header_to_emap_upd h)
Proof
  rw [] >> Cases_on `h` >>
  rw [translate_header_def] >> fs [header_uses_def, header_assigns_def] >>
  rw [header_to_emap_upd_def, FUPDATE_LIST_THM] >>
  `?l2. l = Some l2` by metis_tac [option_nchotomy] >>
  rw [translate_header_def]
  >- (
    rw [LIST_EQ_REWRITE, EL_MAP, generate_move_block_def] >>
    rename1 `build_move_for_lab _ _ _ (el i phis)` >>
    Cases_on `el i phis` >> rw [build_move_for_lab_def] >> CASE_TAC >>
    drule similar_emap_translate_arg >>
    disch_then irule >>
    rw [SUBSET_DEF, PULL_EXISTS] >>
    qexists_tac `el x from` >>
    qexists_tac `el i phis` >> rw [phi_uses_def] >>
    metis_tac [EL_MEM])
  >- (
    irule extend_similar_emap >> rw [] >>
    pop_assum kall_tac >>
    Induct_on `l'` >> rw [EXTENSION] >>
    CASE_TAC >>
    eq_tac >> rw [phi_assigns_def])
QED

Theorem extend_emap_non_exp_assigns:
  ∀inst r t emap.
    instr_assigns inst = {(r,t)} ∧
    (classify_instr inst = Non_exp ∨ classify_instr inst = Call ∨ classify_instr inst = Term) ⇒
    extend_emap_non_exp emap inst =  emap |+ (r,Var (translate_reg r t) F)
Proof
  ho_match_mp_tac instr_assigns_ind >>
  rw [instr_assigns_def, extend_emap_non_exp_def, classify_instr_def]
QED

Theorem extend_emap_non_exp_no_assigns:
  ∀inst emap.
    (∀r t. instr_assigns inst ≠ {(r,t)}) ∧
    classify_instr inst = Non_exp ⇒
    extend_emap_non_exp emap inst = emap ∧
    instr_assigns inst = {}
Proof
  ho_match_mp_tac instr_assigns_ind >>
  rw [instr_assigns_def, extend_emap_non_exp_def, classify_instr_def]
QED

Theorem translate_instrs_similar_emap:
  ∀f l gmap emap1 emap2 regs_to_keep is i.
    similar_emap (fst (instrs_live is)) emap1 emap2 ∧
    (∀i. i < length is ∧ classify_instr (el i is) = Term ⇒ Suc i = length is) ∧
    every is_implemented is
    ⇒
    (λ(b1, emap1') (b2, emap2').
       b1 = b2 ∧
       ∃l.
         set (map fst l) = bigunion (set (map (image fst) (map instr_assigns is))) ∧
         emap1' = emap1 |++ l ∧ emap2' = emap2 |++ l ∧
         similar_emap (fst (instrs_live is) ∪ set (map fst l)) emap1' emap2')
      (translate_instrs (Lab_name f (option_map dest_label l) i) gmap emap1 regs_to_keep is)
      (translate_instrs (Lab_name f (option_map dest_label l) i) gmap emap2 regs_to_keep is)
Proof
  Induct_on `is` >> rw [translate_instrs_def]
  >- metis_tac [FUPDATE_LIST_THM]
  >- metis_tac [FUPDATE_LIST_THM] >>
  ntac 2 (pairarg_tac >> fs []) >>
  rename1 `classify_instr inst` >>
  Cases_on `classify_instr inst` >> fs []
  >- (
    rename1 `reg ∈ regs_to_keep` >>
    `instr_assigns inst = {(reg, t)}`
    by (
      Cases_on `inst` >> fs [classify_instr_def, instr_assigns_def] >>
      Cases_on `p` >> fs [classify_instr_def, instr_assigns_def]) >>
    Cases_on `reg ∈ regs_to_keep` >> fs []
    >- (
      ntac 2 (pairarg_tac >> fs []) >> rpt var_eq_tac >>
      fs [] >>
      `similar_emap (fst (instrs_live is))
        (emap1 |+ (reg,Var (translate_reg reg t) F))
        (emap2 |+ (reg,Var (translate_reg reg t) F))`
      by (
        irule extend_similar_emap2 >>
        qexists_tac `inst` >> rw [] >>
        Cases_on `inst` >> fs [classify_instr_def, instr_assigns_def] >>
        Cases_on `p` >> fs [classify_instr_def, instr_assigns_def]) >>
      first_x_assum drule >>
      disch_then (qspecl_then [`f`, `l`, `gmap`, `regs_to_keep`, `i`] mp_tac) >>
      pairarg_tac >> fs [] >>
      rw []
      >- metis_tac [similar_emap_translate_instr_to_exp] >>
      qexists_tac `(reg,Var (translate_reg reg t) F) :: l'` >> rw []
      >- rw [EXTENSION, MEM_MAP]
      >- metis_tac [FUPDATE_LIST_THM]
      >- metis_tac [FUPDATE_LIST_THM] >>
      fs [instrs_live_def] >> pairarg_tac >> simp [] >>
      fs [similar_emap_union] >> rw []
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM]
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM] >>
      simp [Once similar_emap_insert] >> rw [] >>
      metis_tac [extend_similar_emap3, similar_emap_lemma])
    >- (
      drule similar_emap_translate_instr_to_exp >> simp [] >>
      disch_then (qspec_then `gmap` mp_tac) >>
      disch_tac >> fs [] >>
      `similar_emap (fst (instrs_live is))
        (emap1 |+ (reg,translate_instr_to_exp gmap emap2 inst))
        (emap2 |+ (reg,translate_instr_to_exp gmap emap2 inst))`
      by (irule extend_similar_emap2 >> qexists_tac `inst` >> rw []) >>
      first_x_assum drule >>
      disch_then (qspecl_then [`f`, `l`, `gmap`, `regs_to_keep`, `i`] mp_tac) >>
      pairarg_tac >> fs [] >> rw [] >>
      qexists_tac `(reg,translate_instr_to_exp gmap emap2 inst) :: l'` >> rw []
      >- rw [EXTENSION, MEM_MAP]
      >- metis_tac [FUPDATE_LIST_THM]
      >- metis_tac [FUPDATE_LIST_THM] >>
      fs [instrs_live_def] >> pairarg_tac >> simp [] >>
      fs [similar_emap_union] >> rw []
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM]
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM] >>
      simp [Once similar_emap_insert] >> rw [] >>
      metis_tac [extend_similar_emap3, similar_emap_lemma]))
  >- (
    ntac 2 (pairarg_tac >> fs []) >> rpt var_eq_tac >>
    drule extend_similar_emap4 >> simp [] >> disch_tac >>
    first_x_assum drule >>
    disch_then (qspecl_then [`f`, `l`, `gmap`, `regs_to_keep`, `i`] mp_tac) >>
    pairarg_tac >> fs [] >>
    rw []
    >- metis_tac [similar_emap_translate_instr_to_inst] >>
    Cases_on `∃r t. instr_assigns inst = {(r,t)}` >> fs []
    >- (
      drule extend_emap_non_exp_assigns >> rw [] >>
      qexists_tac `(r,Var (translate_reg r t) F) :: l'` >> rw []
      >- rw [EXTENSION]
      >- metis_tac [FUPDATE_LIST_THM]
      >- metis_tac [FUPDATE_LIST_THM] >>
      fs [instrs_live_def] >> pairarg_tac >> simp [] >>
      fs [similar_emap_union] >> rw []
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM]
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM] >>
      simp [Once similar_emap_insert] >> rw [] >>
      metis_tac [extend_similar_emap3, similar_emap_lemma])
    >- (
      drule extend_emap_non_exp_no_assigns >> rw [] >>
      qexists_tac `l'` >> rw [] >>
      fs [instrs_live_def] >> pairarg_tac >> simp [] >>
      fs [similar_emap_union] >> rw [] >>
      metis_tac [similar_emap_lemma, FUPDATE_LIST_THM]))
  >- (
    rw []
    >- metis_tac [similar_emap_translate_instr_to_term] >>
    `instr_assigns inst = {}`
     by (
       Cases_on `inst` >> fs [classify_instr_def, instr_assigns_def] >>
       Cases_on `p` >> fs [classify_instr_def, instr_assigns_def]) >>
    `is = []`
    by (first_x_assum (qspec_then `0` mp_tac) >> simp []) >>
    rw [FUPDATE_LIST_THM])
  >- (
      ntac 2 (pairarg_tac >> fs []) >> rpt var_eq_tac >> fs [] >>
      `?r t. instr_assigns inst = {(r,t)}`
      by (
        Cases_on `inst` >> fs [classify_instr_def, instr_assigns_def] >>
        Cases_on `p` >> fs [classify_instr_def, instr_assigns_def]) >>
      drule extend_emap_non_exp_assigns >> simp [] >>
      disch_tac >> fs [] >>
      `similar_emap (fst (instrs_live is))
        (emap1 |+ (r,Var (translate_reg r t) F))
        (emap2 |+ (r,Var (translate_reg r t) F))`
      by metis_tac [extend_similar_emap2] >>
      first_x_assum drule >>
      disch_then (qspecl_then [`f`, `l`, `gmap`, `regs_to_keep`, `i + 1`] mp_tac) >>
      fs [inc_label_def] >> rw []
      >- metis_tac [similar_emap_translate_call] >>
      qexists_tac `(r,Var (translate_reg r t) F) :: l'` >> rw []
      >- rw [EXTENSION, MEM_MAP]
      >- metis_tac [FUPDATE_LIST_THM]
      >- metis_tac [FUPDATE_LIST_THM] >>
      fs [instrs_live_def] >> pairarg_tac >> simp [] >>
      fs [similar_emap_union] >> rw []
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM]
      >- metis_tac [similar_emap_lemma, FUPDATE_LIST_THM] >>
      simp [Once similar_emap_insert] >> rw [] >>
      metis_tac [extend_similar_emap3, similar_emap_lemma])
QED

Theorem header_to_emap_upd_fst:
  ∀b.
    set (map fst (header_to_emap_upd b.h)) =
    {fst (phi_assigns p) | (land,p,phis) | b.h = Head phis land ∧ mem p phis}
Proof
  rw [EXTENSION] >> eq_tac >> rw [MEM_MAP]
  >- (
    Cases_on `b.h` >> fs [header_to_emap_upd_def, MEM_MAP] >> rw [] >>
    CASE_TAC >> rw [] >>
    metis_tac [phi_assigns_def, FST])
  >- (
    rw [header_to_emap_upd_def, MEM_MAP, PULL_EXISTS] >>
    qexists_tac `p` >> rw [] >>
    CASE_TAC >> rw [phi_assigns_def])
QED

Theorem header_to_emap_upd_fst2:
  !b. header_assigns b.h = set (map fst (header_to_emap_upd b.h))
Proof
  Cases_on `b.h` >> rw [header_assigns_def, header_to_emap_upd_def] >>
  rw [EXTENSION, MEM_MAP, PULL_EXISTS] >>
  eq_tac >> rw []
  >- (Cases_on `y` >> rw [phi_assigns_def] >> qexists_tac `Phi r t l'` >> rw [])
  >- (CASE_TAC >> rw [] >> metis_tac [phi_assigns_def, FST])
QED

Definition block_assigns_def:
  block_assigns b =
    { fst (phi_assigns p) | land,p,phis | b.h = Head phis land ∧ mem p phis } ∪
    bigunion (image (λi. image fst (instr_assigns i)) (set b.body))
End

Theorem translate_block_emap_restr_live:
  ∀emap1 emap2 b f gmap r x l.
    similar_emap (linear_live [b]) emap1 emap2 ∧
    (l = None ⇔ b.h = Entry) ∧
    (∀i. i < length b.body ∧ classify_instr (el i b.body) = Term ⇒ Suc i = length b.body) ∧
    every is_implemented b.body
    ⇒
    (λ(b1, emap1') (b2, emap2').
        b1 = b2 ∧
        ∃l.
          set (map fst l) = block_assigns b ∧
          emap1' = emap1 |++ l ∧ emap2' = emap2 |++ l ∧
          similar_emap (linear_live [b] ∪ block_assigns b) emap1' emap2')
      (translate_block f gmap emap1 r x (l,b))
      (translate_block f gmap emap2 r x (l,b))
Proof
  rw [translate_block_def] >>
  rpt (pairarg_tac >> fs []) >>
  `similar_emap (header_uses b.h) emap1 emap2`
  by (
    fs [similar_emap_def, linear_live_def, fmap_eq_flookup, FLOOKUP_DRESTRICT, PULL_EXISTS] >>
    rw [] >> last_x_assum (qspec_then `x'` mp_tac) >> rw [] >>
    pairarg_tac >> fs []) >>
  drule translate_header_similar_emap >>
  disch_then drule >>
  disch_then (qspecl_then [`f`, `THE (alookup x l)`, `gmap`] mp_tac) >>
  strip_tac >>
  `similar_emap (fst (instrs_live b.body))
    (emap1 |++ header_to_emap_upd b.h)
    (emap2 |++ header_to_emap_upd b.h)`
  by (
    fs [linear_live_def] >>
    pairarg_tac >> fs [similar_emap_union] >>
    irule similar_emap_diff >>
    metis_tac [header_to_emap_upd_fst2]) >>
  drule translate_instrs_similar_emap >>
  disch_then (qspecl_then [`f`, `l`, `gmap`, `r`, `0`] mp_tac) >>
  rpt (pairarg_tac >> simp []) >>
  rw [] >> fs [] >> rw [] >>
  simp [GSYM FUPDATE_LIST_APPEND] >>
  qexists_tac `header_to_emap_upd b.h ++ l'` >> simp [] >>
  conj_asm1_tac
  >- (
    rw [block_assigns_def] >>
    rw [GSYM header_to_emap_upd_fst] >>
    rw [EXTENSION, PULL_EXISTS, MEM_MAP] >>
    metis_tac []) >>
  irule extend_similar_emap >> rw [block_assigns_def]
QED

Theorem translate_blocks_emap_restr_live:
  ∀emap1 emap2 blocks f gmap r x.
    similar_emap (linear_live (map snd blocks)) emap1 emap2 ∧
    every (\(l,b). l = None ⇔ b.h = Entry) blocks ∧
    every (\(l,b). (∀i. i < length b.body ∧ classify_instr (el i b.body) = Term ⇒ Suc i = length b.body))
      blocks ∧
    every (\(l,b). every is_implemented b.body) blocks
    ⇒
    translate_blocks f gmap emap1 r x blocks = translate_blocks f gmap emap2 r x blocks
Proof
  Induct_on `blocks` >> rw [translate_blocks_def] >>
  pairarg_tac >> rw [] >>
  pairarg_tac >> rw [] >>
  qpat_x_assum `translate_block _ _ _ _ _ _ = _` mp_tac >>
  rename1 `translate_block _ _ _ _ _ p = (b3, emap3)` >>
  rw [] >>
  rename1 `translate_block _ _ _ _ _ p = (b4, emap4)` >>
  rw [APPEND_EQ_APPEND] >>
  disj1_tac >> qexists_tac `[]` >>
  simp [] >>
  `?l b. p = (l,b)` by metis_tac [pair_CASES] >> fs [] >>
  `similar_emap (linear_live [b]) emap1 emap2`
  by (
    fs [linear_live_def] >>
    pairarg_tac >> fs [similar_emap_union, union_diff]) >>
  drule translate_block_emap_restr_live >>
  disch_then drule >>
  simp [] >>
  disch_then (qspecl_then [`f`, `gmap`, `r`, `x`] mp_tac) >>
  pairarg_tac >> simp [] >>
  fs [] >> rw [] >>
  first_x_assum irule >>
  fs [linear_live_def] >>
  pairarg_tac >> fs [similar_emap_union, union_diff, block_assigns_def] >>
  fs [GSYM header_to_emap_upd_fst, GSYM header_to_emap_upd_fst2] >>
  irule similar_emap_diff >> rw [DIFF_UNION] >>
  qmatch_goalsub_abbrev_tac `similar_emap (_ DIFF _ DIFF a)` >>
  `kill ⊆ a` by metis_tac [instrs_kill_subset_assigns, SND] >>
  ONCE_REWRITE_TAC [similar_emap_sym] >>
  irule similar_emap_subset >>
  qexists_tac `linear_live (map snd blocks) DIFF kill DIFF header_assigns b.h` >>
  fs [SUBSET_DEF] >> metis_tac []
QED

(* ------ Theorems about lookups in the translated program ----- *)
(* This uses the dominor_ordered theorems and the similar_emap theorems to
 * switch the empty emap that the translator starts the translation with to the
 * good_emap that it ends with. The good_emap theory then lets us know that it
 * doesn't change throughout the compilation. *)

Theorem alookup_translate_prog:
  ∀prog f d.
    alookup prog (Fn f) = Some d
    ⇒
    alookup (translate_prog prog).functions f = Some (translate_def f d (get_gmap prog))
Proof
  rw [translate_prog_def] >>
  qspec_tac (`get_gmap prog:glob_var |-> ty`, `gmap`) >>
  Induct_on `prog` >>
  rw [] >>
  pairarg_tac >> fs [] >> rw [] >> Cases_on `fname` >> fs [dest_fn_def]
QED

Triviality dest_label_11:
  dest_label x = dest_label y ⇔ x = y
Proof
  Cases_on `x` >> Cases_on `y` >> rw [dest_label_def]
QED

Theorem alookup_translate_instrs_mov:
  ∀l gmap emap r is bs emap' f from to.
    translate_instrs l gmap emap r is = (bs, emap') ∧
    (∀f from to. l ≠ Mov_name f from to)
    ⇒
    alookup bs (Mov_name f from to) = None
Proof
  Induct_on `is` >> rw [translate_instrs_def] >> rw [] >>
  BasicProvers.EVERY_CASE_TAC >> fs [] >>
  TRY pairarg_tac >> fs [] >> rw []
  >- (
    rename1 `add_to_first_block _ bs1` >>
    `bs1 = [] ∨ ∃x y bs2. bs1 = (x,y)::bs2` by metis_tac [list_CASES, pair_CASES] >>
    fs [add_to_first_block_def] >> rw [] >>
    first_x_assum drule >> Cases_on `l` >> fs [inc_label_def] >>
    rw [] >> metis_tac [NOT_SOME_NONE])
  >- (first_x_assum drule >> Cases_on `l` >> fs [inc_label_def])
  >- (
    rename1 `add_to_first_block _ bs1` >>
    `bs1 = [] ∨ ∃x y bs2. bs1 = (x,y)::bs2` by metis_tac [list_CASES, pair_CASES] >>
    fs [add_to_first_block_def] >> rw [] >>
    first_x_assum drule >> Cases_on `l` >> fs [inc_label_def] >>
    rw [] >> metis_tac [NOT_SOME_NONE])
  >- (first_x_assum drule >> Cases_on `l` >> fs [inc_label_def])
QED

Theorem alookup_translate_header_mov:
  ∀gmap r f emap to_l from_l x to_l' from_ls h.
    (to_l' = None ⇒ h = Entry) ∧
    alookup (translate_header f from_ls to_l' gmap emap h)
       (Mov_name f (option_map dest_label from_l) to_l) = Some x
    ⇒
    to_l' = Some (Lab to_l)
Proof
  rw [] >>
  Cases_on `to_l'` >> Cases_on `h` >> fs [translate_header_def] >>
  drule ALOOKUP_MEM >> simp [MEM_MAP] >>
  rw [] >>
  Cases_on `x'` >> fs [dest_label_def]
QED

Theorem lab_dest_lab[simp]:
  Lab (dest_label l) = l
Proof
  Cases_on `l` >> rw [dest_label_def]
QED

Theorem alookup_translate_header:
  ∀f to_l gmap emap phis l from_l edges from_ls.
    (mem (Some (Lab to_l), from_ls) edges ∧ mem from_l from_ls)
    ⇒
    alookup (translate_header f from_ls (Some (Lab to_l)) gmap emap (Head phis l)) (Mov_name f (option_map dest_label from_l) to_l) ≠ None
Proof
  rw [translate_header_def, ALOOKUP_NONE, MAP_MAP_o, combinTheory.o_DEF,
      MEM_MAP, PULL_EXISTS, dest_label_def, MEM_FLAT] >>
  Cases_on `from_l` >> fs [] >> rw [PULL_EXISTS] >>
  metis_tac []
QED

Theorem mem_get_from_ls:
  ∀to_l blocks from_l.
    mem from_l (get_from_ls to_l blocks) ⇔
    ∃b. mem (from_l, b) blocks ∧ mem to_l (map Some (instr_to_labs (last b.body)))
Proof
  ho_match_mp_tac get_from_ls_ind >> rw [get_from_ls_def] >> metis_tac []
QED

Theorem alookup_translate_blocks_mov:
  ∀blocks to_l f gmap regs_to_keep edges from_l phis block emap prog.
    good_emap (Fn f) prog regs_to_keep gmap emap ∧
    (∀l b. mem (l, b) blocks ⇒ ∃d. alookup prog (Fn f) = Some d ∧ alookup d.blocks l = Some b) ∧
    mem (Some (Lab to_l)) (map fst blocks) ∧
    (∃from_ls. alookup edges (Some (Lab to_l)) = Some from_ls ∧ mem from_l from_ls) ∧
    every (\b. (snd b).h = Entry ⇔ fst b = None) blocks ∧
    alookup blocks (Some (Lab to_l)) = Some block ∧ (∃l. block.h = Head phis l)
    ⇒
    alookup
      (translate_blocks f gmap emap regs_to_keep edges blocks)
      (Mov_name f (option_map dest_label from_l) to_l) =
    Some (generate_move_block f gmap emap phis from_l (Lab to_l))
Proof
  Induct_on `blocks` >> rw [translate_blocks_def] >>
  rename1 `alookup (bloc::blocks) (Some _) = Some _` >>
  `∃l bl. bloc = (l,bl)` by metis_tac [pair_CASES] >>
  fs [] >> rw [translate_block_def]
  >- (
    pairarg_tac >> fs [] >>
    pairarg_tac >> fs [] >> rw [] >>
    rw [ALOOKUP_APPEND] >>
    BasicProvers.EVERY_CASE_TAC >> fs []
    >- metis_tac [alookup_translate_header, ALOOKUP_MEM]
    >- metis_tac [label_distinct, alookup_translate_instrs_mov, NOT_NONE_SOME] >>
    fs [translate_header_def, alookup_some, MAP_EQ_APPEND] >> rw [] >>
    Cases_on `from_l` >> Cases_on `l_from` >> fs [] >>
    metis_tac [dest_label_11]) >>
  BasicProvers.EVERY_CASE_TAC >> fs [] >> rw []
  >- (
    pairarg_tac >> fs [] >>
    pairarg_tac >> fs [] >> rw [] >>
    rw [ALOOKUP_APPEND] >>
    BasicProvers.EVERY_CASE_TAC >> fs []
    >- metis_tac [alookup_translate_header, ALOOKUP_MEM]
    >- metis_tac [label_distinct, alookup_translate_instrs_mov, NOT_NONE_SOME] >>
    fs [translate_header_def, alookup_some, MAP_EQ_APPEND] >> rw [] >>
    Cases_on `from_l` >> Cases_on `l_from` >> fs [] >>
    metis_tac [dest_label_11]) >>
  first_x_assum drule >> simp [PULL_EXISTS] >>
  rpt (disch_then drule) >>
  pairarg_tac >> fs [] >>
  fs [ALOOKUP_APPEND] >>
  BasicProvers.EVERY_CASE_TAC >> fs [] >>
  pairarg_tac >> fs [] >> rw [] >>
  fs [ALOOKUP_APPEND] >>
  BasicProvers.EVERY_CASE_TAC >> fs []
  >- (
    `emap |++ header_to_emap_upd bl.h = emap`
    by (
      Cases_on `bl.h`
      >- rw [header_to_emap_upd_def, FUPDATE_LIST_THM] >>
      first_assum (qspecl_then [`l'`, `bl`] assume_tac) >> fs [] >>
      rename1 `bl.h = Head phis1 land` >>
      `get_instr prog <| f := Fn f; b := l'; i := Phi_ip from_l |>
         (Inr (from_l, phis1))`
      by rw [get_instr_cases] >>
      metis_tac [good_emap_header_unchanged]) >>
    `emap' = emap` by metis_tac [good_emap_translate_unchanged] >>
    metis_tac [label_distinct, alookup_translate_instrs_mov, NOT_NONE_SOME]) >>
  rw [] >>
  metis_tac [label_distinct, alookup_translate_instrs_mov, NOT_NONE_SOME, alookup_translate_header_mov]
QED

Theorem get_block_translate_prog_mov:
  ∀prog f from_l to_l d b block phis d emap.
    prog_ok prog ∧
    dominator_ordered prog ∧
    good_emap (Fn f) prog (get_regs_to_keep d) (get_gmap prog) emap ∧
    alookup prog (Fn f) = Some d ∧
    alookup d.blocks from_l = Some b ∧
    mem (Lab to_l) (instr_to_labs (last b.body)) ∧
    alookup d.blocks (Some (Lab to_l)) = Some block ∧ (∃l. block.h = Head phis l) ∧
    every (\(l,b). every is_implemented b.body) d.blocks
    ⇒
    get_block (translate_prog prog)
      (Mov_name f (option_map dest_label from_l) to_l)
      (generate_move_block f (get_gmap prog) emap phis from_l (Lab to_l))
Proof
  rw [get_block_cases, label_to_fname_def] >>
  drule alookup_translate_prog >>
  rw [] >> rw [translate_def_def] >>
  qmatch_goalsub_abbrev_tac `translate_blocks a1 b1 _ c1 d1 e1` >>
  `translate_blocks a1 b1 fempty c1 d1 e1 = translate_blocks a1 b1 emap c1 d1 e1`
  by (
    irule translate_blocks_emap_restr_live >>
    unabbrev_all_tac >> rw []
    >- metis_tac [prog_ok_terminator_last]
    >- (
      fs [prog_ok_def] >> fs [EVERY_MEM] >> rw [] >>
      pairarg_tac >> rw [] >> metis_tac [FST, SND])
    >- metis_tac [dominator_ordered_linear_live, similar_emap_def, DRESTRICT_IS_FEMPTY]) >>
  unabbrev_all_tac >> rw [] >>
  irule alookup_translate_blocks_mov >> rw []
  >- (
    simp [ALOOKUP_MAP_2, MEM_MAP, EXISTS_PROD] >>
    drule ALOOKUP_MEM >> rw [mem_get_from_ls, MEM_MAP] >>
    imp_res_tac ALOOKUP_MEM >>
    metis_tac [])
  >- (fs [prog_ok_def] >> res_tac >> fs [EVERY_MEM])
  >- (
    imp_res_tac ALOOKUP_MEM >>
    fs [MEM_MAP] >>
    metis_tac [FST])
  >- (
    fs [prog_ok_def] >>
    qexists_tac `prog` >> rw [] >>
    irule ALOOKUP_ALL_DISTINCT_MEM >> rw [] >>
    fs [EVERY_MEM] >>
    `(λ(fname,dec). all_distinct (map fst dec.blocks)) (Fn f, d)`
    by metis_tac [ALOOKUP_MEM] >>
    fs [])
QED

Theorem alookup_translate_header_lab:
  (l = None ⇒ h = Entry)
  ⇒
  alookup (translate_header f from_ls l gmap emap h) (translate_label f l' i) = None
Proof
  Cases_on `l` >> Cases_on `h` >> fs [translate_header_def] >>
  Cases_on `l'` >> fs [translate_label_def, ALOOKUP_NONE, MEM_MAP] >>
  rw [dest_label_def] >>
  CCONTR_TAC >> fs [] >> rw [] >> fs [] >>
  Cases_on `x'` >> fs [translate_label_def]
QED

Theorem alookup_translate_instrs_lab:
  ∀f l' i j gmap emap regs_to_keep b bs emap' l x.
    translate_instrs (Lab_name f (option_map dest_label l') i) gmap emap regs_to_keep b = (bs,emap') ∧
    alookup bs (translate_label f l j) = Some x
    ⇒
    l = l'
Proof
  Induct_on `b` >> rw [translate_instrs_def] >> fs [] >>
  rename1 `classify_instr ins` >>
  Cases_on `classify_instr ins` >> fs []
  >- (
    pairarg_tac >> fs [] >>
    Cases_on `r ∈ regs_to_keep` >> fs []
    >- (
      first_x_assum drule >>
      Cases_on `bs'` >> fs [add_to_first_block_def] >> rw [] >> fs [] >>
      rename1 `add_to_first_block _ (bl::_)` >>
      Cases_on `bl` >> fs [add_to_first_block_def] >>
      rename1 `lab = translate_label _ _ _` >>
      Cases_on `lab = translate_label f l j` >> fs [] >> metis_tac []) >>
    metis_tac [])
  >- (
    pairarg_tac >> fs [] >>
    first_x_assum drule >>
    Cases_on `bs'` >> fs [add_to_first_block_def] >> rw [] >> fs [] >>
    rename1 `add_to_first_block _ (bl::_)` >>
    Cases_on `bl` >> fs [add_to_first_block_def] >>
    rename1 `lab = translate_label _ _ _` >>
    Cases_on `lab = translate_label f l j` >> fs [] >> metis_tac [])
  >- (
    rw [] >> fs [ALOOKUP_def] >>
    Cases_on `l` >> Cases_on `l'` >> fs [translate_label_def] >>
    rename1 `translate_label _ (Some lname)` >>
    Cases_on `lname` >> fs [translate_label_def] >> rw [])
  >- (
    pairarg_tac >> fs [] >> rw [] >> fs [ALOOKUP_def] >>
    BasicProvers.EVERY_CASE_TAC >> fs [] >>
    rw []
    >- (
      Cases_on `l` >> fs [inc_label_def, translate_label_def] >>
      rename1 `translate_label _ (Some lb)` >>
      Cases_on `lb` >> fs [inc_label_def, translate_label_def]) >>
    fs [inc_label_def] >>
    metis_tac [])
QED

Triviality every_front:
  ∀P x y. y ≠ [] ∧ every P (front (x::y)) ⇒ every P (front y)
Proof
  Induct_on `y` >> rw []
QED

Theorem translate_instrs_first_lab:
  ∀dest_label l gmap emap regs_to_keep b bs emap l' b' emap'.
    translate_instrs l gmap emap regs_to_keep b = ((l',b')::bs,emap')
    ⇒
    l = l'
Proof
  Induct_on `b` >> rw [translate_instrs_def] >>
  BasicProvers.EVERY_CASE_TAC >> fs [] >>
  TRY pairarg_tac >> fs [] >> rw [] >>
  TRY (Cases_on `bs'`) >> fs [add_to_first_block_def] >>
  TRY (Cases_on `h'`) >> fs [add_to_first_block_def] >>
  metis_tac []
QED

Triviality lab_translate_label:
  ∀f l j f' l' j'.
    Lab_name f (option_map dest_label l) j = translate_label f' l' j'
    ⇔
    f = f' ∧ l = l' ∧ j = j'
Proof
  rw [] >> Cases_on `l` >> Cases_on `l'` >> fs [translate_label_def] >>
  Cases_on `x` >> fs [translate_label_def, dest_label_def] >>
  Cases_on `x'` >> fs [translate_label_def, dest_label_def]
QED

Definition num_calls_def:
  num_calls is = length (filter is_call is)
End

Theorem alookup_translate_instrs:
  ∀f l i j gmap emap regs_to_keep b bs emap' l x.
    b ≠ [] ∧
    terminator (last b) ∧
    every (λi. ¬terminator i) (front b) ∧
    i ≤ num_calls b ∧
    translate_instrs (Lab_name f (option_map dest_label l) j) gmap emap regs_to_keep b = (bs,emap')
    ⇒
    alookup bs (translate_label f l (i + j))
    =
    Some (snd (el i (fst (translate_instrs (Lab_name f (option_map dest_label l) j)
                             gmap emap regs_to_keep b))))
Proof
  Induct_on `b` >> rw [translate_instrs_def, num_calls_def] >>
  rename1 `classify_instr instr`
  >- (
    Cases_on `instr` >> fs [is_call_def, classify_instr_def] >>
    pairarg_tac >> fs [] >> rw [] >>
    fs [lab_translate_label]
    >- (
      `i = 0` by fs [] >>
      rw [] >> fs [] >>
      qexists_tac `emap` >> rw []) >>
    `b ≠ []` by (Cases_on `b` >> fs [terminator_def]) >>
    fs [LAST_DEF, inc_label_def] >>
    `0 < i` by fs [] >>
    `i - 1 ≤ num_calls b` by fs [num_calls_def] >>
    drule every_front >> disch_then drule >> rw [] >>
    first_x_assum drule >> disch_then drule >> disch_then drule >> rw [] >>
    rw [] >>
    rw [EL_CONS, PRE_SUB1]) >>
  Cases_on `classify_instr instr` >> fs [LAST_DEF]
  >- (
  `b ≠ []`
    by (
      Cases_on `b = []` >> Cases_on `instr` >> fs [is_call_def, classify_instr_def] >>
      rw [] >> fs [terminator_def]) >>
    fs [num_calls_def] >>
    pairarg_tac >> fs [] >>
    drule every_front >> disch_then drule >> rw [] >> fs [] >>
    Cases_on `r  ∉ regs_to_keep` >> fs []
    >- metis_tac [] >>
    Cases_on `bs'` >> fs [add_to_first_block_def] >>
    first_x_assum drule >> disch_then drule >> rw [] >> fs [] >>
    rename1 `add_to_first_block _ (i1::is)` >>
    Cases_on `i1` >> fs [add_to_first_block_def] >>
    rw [] >> fs [] >>
    drule translate_instrs_first_lab >> rw [] >>
    fs [lab_translate_label]
    >- (`i = 0` by fs [] >> rw []) >>
    `0 < i` by fs [] >>
    rw [EL_CONS])
  >- (
    `b ≠ []`
    by (
      Cases_on `b = []` >> Cases_on `instr` >> fs [is_call_def, classify_instr_def] >>
      rw [] >> fs [terminator_def]) >>
    fs [num_calls_def] >>
    pairarg_tac >> fs [] >>
    rw [] >>
    drule every_front >> disch_then drule >> rw [] >> fs [] >>
    Cases_on `bs'` >> fs [add_to_first_block_def] >>
    first_x_assum drule >> disch_then drule >> rw [] >> fs [] >>
    rename1 `add_to_first_block _ (i1::is)` >>
    Cases_on `i1` >> fs [add_to_first_block_def] >>
    rw [] >> fs [] >>
    drule translate_instrs_first_lab >> rw [] >>
    fs [lab_translate_label]
    >- (`i = 0` by fs [] >> rw []) >>
    `0 < i` by fs [] >>
    rw [EL_CONS])
  >- (
    `b = []`
    by (
      Cases_on `b` >> fs [] >>
      Cases_on `instr` >> fs [terminator_def, classify_instr_def] >>
      Cases_on `p` >> fs [classify_instr_def]) >>
    fs [] >> rw [] >>
    metis_tac [lab_translate_label])
  >- (
    `b = []`
    by (
      Cases_on `b` >> fs [] >>
      Cases_on `instr` >> fs [is_call_def, terminator_def, classify_instr_def] >>
      Cases_on `p` >> fs [classify_instr_def]) >>
    rw [] >> fs [] >>
    pairarg_tac >> fs [] >> rw [] >>
    Cases_on `l` >> fs [translate_label_def] >>
    Cases_on `x` >> fs [translate_label_def, dest_label_def])
QED

Theorem classify_instr_lem:
  (∀i. (terminator i ∨ is_call i) ⇔ classify_instr i = Term ∨ classify_instr i = Call)
Proof
  strip_tac >> Cases_on `i` >> rw [terminator_def, classify_instr_def, is_call_def] >>
  Cases_on `p` >> rw [classify_instr_def]
QED

Theorem translate_instrs_not_empty:
  ∀l gmap emap regs b.
    b ≠ [] ∧ terminator (last b) ⇒
    ∀emap2. translate_instrs l gmap emap regs b ≠ ([], emap2)
Proof
  Induct_on `b` >> rw [translate_instrs_def] >>
  CASE_TAC >> rw [] >> TRY pairarg_tac >> fs []
  >- (
    Cases_on `bs` >> fs [add_to_first_block_def] >>
    Cases_on `b` >> fs []
    >- metis_tac [classify_instr_lem, instr_class_distinct]
    >- metis_tac [] >>
    rename1 `add_to_first_block _ (b::bs)` >>
    Cases_on `b` >> fs [add_to_first_block_def]) >>
  Cases_on `b` >> fs []
  >- metis_tac [classify_instr_lem, instr_class_distinct]
  >- metis_tac [classify_instr_lem, instr_class_distinct] >>
  Cases_on `bs` >> fs [add_to_first_block_def]
  >- metis_tac [] >>
  rename1 `add_to_first_block _ (b::bs)` >>
  Cases_on `b` >> fs [add_to_first_block_def]
QED

Theorem alookup_translate_blocks:
  ∀blocks l f gmap emap regs_to_keep edges b i prog.
    b.body ≠ [] ∧
    terminator (last b.body) ∧
    every (λi. ¬terminator i) (front b.body) ∧
    every (\b. (snd b).h = Entry ⇔ fst b = None) blocks ∧
    alookup blocks l = Some b ∧
    i ≤ num_calls b.body ∧
    good_emap (Fn f) prog regs_to_keep gmap emap ∧
    (∀l b. mem (l, b) blocks ⇒ ∃d. alookup prog (Fn f) = Some d ∧ alookup d.blocks l = Some b)
    ⇒
    alookup (translate_blocks f gmap emap regs_to_keep edges blocks) (translate_label f l i)
    =
    Some (snd (el i (fst (translate_instrs (Lab_name f (option_map dest_label l) 0)
                            gmap emap regs_to_keep b.body))))
Proof
  ho_match_mp_tac ALOOKUP_ind >> simp [translate_blocks_def] >>
  rpt strip_tac >>
  pairarg_tac >> fs [ALOOKUP_APPEND] >>
  rename1 `(if l' = l then _ else _) = Some _` >>
  Cases_on `l = l'` >> fs [translate_block_def] >> rw []
  >- (
    pairarg_tac >> fs [] >> rw [] >> fs [ALOOKUP_APPEND] >>
    `l = None ⇒ b.h = Entry` by metis_tac [] >>
    rfs [alookup_translate_header_lab] >>
    imp_res_tac alookup_translate_instrs >>
    fs [] >> rw [] >> rfs [] >>
    `emap |++ header_to_emap_upd b.h = emap`
    by (
      drule good_emap_header_unchanged >>
      Cases_on `b.h` >> fs [header_to_emap_upd_def, FUPDATE_LIST_THM] >>
      disch_then irule >> rw [get_instr_cases] >>
      metis_tac []) >>
    fs [FUPDATE_LIST_THM])
  >- (
    pairarg_tac >> fs [ALOOKUP_APPEND] >> rw [] >>
    fs [ALOOKUP_APPEND] >>
    rename1 `header_to_emap_upd b1.h` >>
    `emap |++ header_to_emap_upd b1.h = emap`
    by (
      drule good_emap_header_unchanged >>
      Cases_on `b1.h` >> fs [header_to_emap_upd_def, FUPDATE_LIST_THM] >>
      disch_then irule >> rw [get_instr_cases] >>
      metis_tac []) >>
    rename1 `alookup (translate_header _ _ _ _ _ bloc.h)` >>
    `l' = None ⇒ bloc.h = Entry` by metis_tac [] >>
    fs [alookup_translate_header_lab] >>
    Cases_on `alookup bs (translate_label f l i)` >> fs [] >> rw []
    >- (
      `emap = emap'`
      by (
        drule good_emap_translate_unchanged >>
        disch_then irule >>
        first_x_assum (qspecl_then [`l'`, `bloc`] mp_tac) >>
        rw [] >>
        metis_tac []) >>
      first_x_assum drule >>
      disch_then (qspecl_then [`f`, `gmap`, `emap`, `regs_to_keep`, `edges`, `prog`] mp_tac) >>
      rw [])
    >- metis_tac [alookup_translate_instrs_lab])
QED

export_theory ();