Theory Voblint_Compile.Simulation_Relation

theory Simulation_Relation
  imports Residual_Edges
begin

section ‹What it means for a source state and a graph state to agree›

text ‹
  ‹csim› relates a source configuration term‹(c, s, frs)› to a CFG configuration
  term‹(v, s, stk)› holding the very same store.  The two represent suspended callers
  differently: the source nests each caller's continuation inside the single command ‹c›,
  as term‹Seq (Seq inner (Restore i)) after› wrappings, while the CFG keeps them as
  continuation nodes on its frame stack.  ‹csim› pairs the two off, one layer per
  suspended caller.

  Both nestings run outermost-first, so the top command layer pairs with the last frame.
  After ‹main› calls ‹f› calls ‹g› the source command is
  ‹Seq (Seq (Seq (Seq C Restore) B) Restore) A› --- ‹A› is main's continuation and ‹C› the
  running residual of ‹g› --- while the CFG sits at ‹g›'s node with continuation frames
  ‹[B's node, A's node]›.

  Three constructors, one per phase an activation can be in.  ‹Base› relates a single
  activation with nothing beneath it.  ‹Nested› peels one suspended caller.  ‹Returning›
  covers the skew just after a callee finishes: the CFG has already reached
  term‹FunctionResult p›, while the source is still propagating const‹Restore› or
  const‹Unwind› towards its frame-pop rule.  Proving that execution preserves ‹csim› is
  the next theory's business; this one defines the relation and the vocabulary it is
  stated in.
›

subsection ‹Return-store update›

text ‹The store published by a return, common to the source const‹Return› step and the
  compiled term‹EA_Ret› edge.›
definition ret_store :: "exp option ⇒ store ⇒ store" where
  "ret_store e s = s(ret_var := (case e of None ⇒ s ret_var | Some a ⇒ ⟦a⟧e s))"

lemma ret_store_None [simp]: "ret_store None s = s"
  by (simp add: ret_store_def)

lemma ret_store_Some [simp]: "ret_store (Some e) s = s(ret_var := ⟦e⟧e s)"
  by (simp add: ret_store_def)

lemma edge_step_EA_Ret_ret_store_mem [simp]: "ret_store e s ∈ edge_step (EA_Ret e p) s"
  by (simp add: ret_store_def)

subsection ‹Bare Unwind is stuck›

text ‹A lone const‹Unwind› has no source step: only the const‹Seq› rules propagate it, and
  the frame pop it is heading for fires on term‹Seq (Unwind j) (Restore i)›.›
lemma pstep_Unwind_stuck: "¬ 𝒢, Π ⊢ (Unwind i, s, frs) →p x"
  by auto

text ‹
  Why a top-level ‹Return› must not reach the source: an empty frame stack has
  nowhere to unwind to, so a run that starts at ‹Unwind› or ‹Return› never completes.
›

lemma Unwind_not_pcompletes: "¬ pcompletes 𝒢 Π (Unwind ()) s t"
proof (rule notI)
  assume "𝒢, Π ⊢ (Unwind (), s, []) →p* (SKIP (), t, [])"
  then show False
    by (cases rule: star.cases) (auto simp: pstep_Unwind_stuck)
qed

lemma Return_empty_not_pcompletes: "¬ pcompletes 𝒢 Π (Return () e) s t"
proof (rule notI)
  assume "𝒢, Π ⊢ (Return () e, s, []) →p* (SKIP (), t, [])"
  then obtain y where step: "𝒢, Π ⊢ (Return () e, s, []) →p y"
      and rest: "𝒢, Π ⊢ y →p* (SKIP (), t, [])"
    by (cases rule: star.cases) auto
  from step have "y = (Unwind (), ret_store e s, [])"
    by (cases e) (auto simp: ret_store_def)
  with rest have "𝒢, Π ⊢ (Unwind (), ret_store e s, []) →p* (SKIP (), t, [])" by simp
  then show False
    by (cases rule: star.cases) (auto simp: pstep_Unwind_stuck)
qed

subsection ‹The returning (frame-pop) source shapes›

text ‹
  Once a callee finishes, the CFG is already at term‹FunctionResult p› while the source
  propagates toward the frame-pop rule.  ‹unwinding› describes an const‹Unwind› travelling
  up through the callee's residual, dropping the dead code it skips (never the activation's
  own const‹Restore›); ‹pop_ready› describes the whole activation body in that phase ---
  either the normal fall-through const‹Restore› or an const‹Unwind› spine
  ‹Seq u Restore› from an explicit const‹Return›.  Both pop the single caller frame with one
  ‹cstep› return; ‹unwinding› propagation is matched by ∗‹no› CFG step, because the CFG
  waits at term‹FunctionResult p›.
›
fun unwinding :: "'a acom ⇒ bool" where
  "unwinding (Unwind i) = True"
| "unwinding (Seq u c2) = (unwinding u ∧ ¬ is_Restore c2)"
| "unwinding _ = False"

fun pop_ready :: "'a acom ⇒ bool" where
  "pop_ready (Restore i) = True"
| "pop_ready (Seq u (Restore i)) = unwinding u"
| "pop_ready _ = False"

text ‹The annotation of the const‹Restore› a returning activation pops: the call it
  came from, which is also the annotation of the const‹SKIP› the pop leaves behind.›
fun pop_ann :: "'a acom ⇒ 'a option" where
  "pop_ann (Restore i) = Some i"
| "pop_ann (Seq u (Restore i)) = Some i"
| "pop_ann _ = None"

lemma unwinding_not_SKIP: "unwinding u ⟹ ¬ is_SKIP u"
  by (cases u) auto

lemma pop_ready_not_SKIP: "pop_ready w ⟹ ¬ is_SKIP w"
  by (cases w rule: pop_ready.cases) auto

lemma pop_ready_not_Unwind: "pop_ready w ⟹ w ≠ Unwind i"
  by (cases w rule: pop_ready.cases) auto

subsection ‹Compile-into-‹g› evidence for an active fragment›

text ‹
  ‹compiled_at Π g p body_p k n› is an activation's certificate.  It says two things at once,
  because every activation needs both: ‹body_p› is genuinely procedure ‹p›'s body, and that body,
  compiled at the exact offset ‹n› with continuation ‹k›, is embedded in the target graph
  ‹g› --- its intra edges lie in term‹intra g› and its call edges in term‹calls g›.

  The offset matters because compilation is shift-parametric: neither ‹n› nor ‹k› is fixed by
  the syntax, so graph membership ∗‹at the chosen offset› is the missing fact and this
  predicate supplies it, at the same ‹k› and ‹n› that const‹control_at› uses.  The procedure
  identity matters because const‹control_at›'s fragment parameter is otherwise free, and
  fall-through preservation is false for a fragment that is not a whole body.

  A procedure body's continuation is its epilogue node, which is why the fall-through return
  edge is stated from ‹k›.  const‹compile_proc› emits that edge for every body.

  The offset itself is anchored to the graph: the body enters at the target of ‹p›'s
  term‹EA_Body› edge, and ‹k› is the epilogue right after the body.  Without the anchor the
  body's edges could be matched at a shifted offset, and the relation would locate a residual
  at a node no execution of ‹p› uses.
›
definition compiled_at ::
  "'a ptable ⇒ cfg ⇒ pname ⇒ 'a acom ⇒ cfg_node ⇒ nat ⇒ bool" where
  "compiled_at Π g p body_p k n ⟷
     (∃decl n' en E K. Π p = Some decl ∧ body_p = body decl
        ∧ compile Π p body_p k n = (n', en, E, K)
        ∧ E ⊆ intra g ∧ K ⊆ calls g
        ∧ (k, EA_Ret None p, FunctionResult p) ∈ intra g
        ∧ (FunctionEntry p, EA_Body p, Statement n) ∈ intra g
        ∧ k = Statement (n + csize body_p))"

lemma compiled_atI [intro]:
  "Π p = Some decl ⟹ body_p = body decl ⟹
   compile Π p body_p k n = (n', en, E, K) ⟹ E ⊆ intra g ⟹ K ⊆ calls g ⟹
   (k, EA_Ret None p, FunctionResult p) ∈ intra g ⟹
   (FunctionEntry p, EA_Body p, Statement n) ∈ intra g ⟹ k = Statement (n + csize body_p) ⟹
   compiled_at Π g p body_p k n"
  unfolding compiled_at_def by blast

lemma compiled_atE [elim]:
  assumes "compiled_at Π g p body_p k n"
  obtains decl n' en E K where
    "Π p = Some decl" "body_p = body decl"
    "compile Π p body_p k n = (n', en, E, K)" "E ⊆ intra g" "K ⊆ calls g"
    "(k, EA_Ret None p, FunctionResult p) ∈ intra g"
  using assms unfolding compiled_at_def by blast

text ‹The procedure-identity half on its own, for the many proofs that only need the
  declaration behind an activation.›
lemma compiled_at_decl:
  assumes "compiled_at Π g p body_p k n"
  obtains decl where "Π p = Some decl" "body_p = body decl"
  using assms unfolding compiled_at_def by blast

lemma compiled_at_exit:
  "compiled_at Π g p body_p k n ⟹ (k, EA_Ret None p, FunctionResult p) ∈ intra g"
  unfolding compiled_at_def by auto

subsection ‹The simulation relation›

text ‹What it means for a source configuration and a graph configuration to be at the same
  place.  Three shapes, one per stack situation: a single activation, a call in progress with
  the caller recorded on both stacks, and the moment between a return and the frame pop.  All
  three carry the same store, so the relation constrains position and stack only.

  The index ‹p› in ‹Π, g, p ⊢ cfg ≈ dg› is the procedure of the outermost activation of
  ‹cfg›, the one with no caller below it in the relation.  ‹Nested› and ‹Returning› record the
  call that suspended that activation, term‹Call i dst q args›: the activation inside
  term‹Seq inner (Restore i)› runs ‹q›, and a returning activation is at term‹FunctionResult q›
  of that same ‹q›.  Recursion is harmless because the index belongs to an activation, not to a
  body.›
inductive csim :: "'a ptable ⇒ cfg ⇒ pname ⇒ 'a acom × store × frame list
                    ⇒ cfg_node × store × cframe list ⇒ bool"
    ("(_,_,_ ⊢/ _ ≈/ _)" [51, 51, 51, 51, 51] 50) for Π g where
  Base:
    "control_at Π p body_p k n c v ⟹ compiled_at Π g p body_p k n ⟹
     Π, g, p ⊢ (c, s, []) ≈ (v, s, [])"
| Nested:
    "Π, g, q ⊢ (inner, s, frs) ≈ (v, s, stk) ⟹
     control_at Π pc body_pc kc nc (seq_after (SKIP i) afters) cont ⟹
     control_at Π pc body_pc kc nc (seq_after (Call i dst q args) afters) site ⟹
     compiled_at Π g pc body_pc kc nc ⟹
     Π, g, pc ⊢ (seq_after (Seq inner (Restore i)) afters, s, frs @ [Frame caller dst])
       ≈ (v, s, stk @ [(cont, dst, caller)])"
| Returning:
    "pop_ready w ⟹ pop_ann w = Some i ⟹
     control_at Π pc body_pc kc nc (seq_after (SKIP i) afters) cont ⟹
     control_at Π pc body_pc kc nc (seq_after (Call i dst q args) afters) site ⟹
     compiled_at Π g pc body_pc kc nc ⟹
     Π, g, pc ⊢ (seq_after w afters, callee, [Frame caller dst])
       ≈ (FunctionResult q, callee, [(cont, dst, caller)])"
text ‹
  On its own const‹csim› is a ∗‹structural› correspondence, not a reachability one.  For an
  unannotated program it is not functional either: a residual carries no record of which
  occurrence it came from, so two textually equal statements share every location.  A caller
  that needs the semantic node pairs it with membership in ‹node_collect› at the same node.
  Annotated programs whose annotations are the compiler's own locations are the case where the
  node is determined.
›

text ‹Inversion at an empty source frame stack.  Only ‹Base› can produce one --- ‹Nested› and
  ‹Returning› both append a caller frame --- so the CFG stack is empty too, the stores agree,
  and the whole activation certificate comes back.  ‹csim›'s introduction rules stay
  undeclared: choosing between ‹Base›, ‹Nested› and ‹Returning› is the step each preservation
  proof exists to make, and it belongs in the proof text rather than in a search.›
inductive_cases csim_empty_stackE [elim]: "Π, g, p ⊢ (c, s, []) ≈ (v, t, stk)"

text ‹‹Nested› and ‹Returning› both locate the ∗‹resumed caller› --- the ordinary residual
  term‹seq_after (SKIP i) afters› that the return will land in --- at the continuation node
  ‹cont›.  ‹Returning› carries exactly the one frame the return pops; outer callers are
  consumed by the ‹Nested› layers wrapping it, and after the pop the caller resumes as a
  ‹Base› activation with that wrapping untouched.›

lemma csim_store_eq:
  "Π, g, ρ ⊢ (c, s, frs) ≈ (v, t, stk) ⟹ s = t"
  by (induction "(c, s, frs)" "(v, t, stk)" arbitrary: c frs v stk rule: csim.induct) auto

subsection ‹Returning commands and the frame-pop phase›

text ‹‹is_returning c› holds when the leftmost-innermost of ‹c› is const‹Restore› or
  const‹Unwind›: the shape of every returning-phase command, ordinary or ‹Nested›-wrapped.›
fun is_returning :: "'a acom ⇒ bool" where
  "is_returning (Restore i) = True"
| "is_returning (Unwind i) = True"
| "is_returning (Seq c1 c2) = is_returning c1"
| "is_returning _ = False"

lemma is_returning_seq_after [simp]:
  "is_returning (seq_after c afters) = is_returning c"
  by (induction afters arbitrary: c) auto

subsection ‹The call-redex source shape›

text ‹‹head_call c› holds when the leftmost-innermost of ‹c› is a const‹Call› to a procedure
  const‹special_table› does not classify: the shape of an active residual whose next
  const‹pstep› pushes a frame.  A call const‹special_table› classifies resolves in place
  instead (const‹intra_step›'s ‹ISpecial›), so it is deliberately excluded here, mirroring
  const‹intra_step›'s own exclusion of const‹Call›.›
fun head_call :: "'a acom ⇒ bool" where
  "head_call (Call i dst q actuals) = (special_table q = None)"
| "head_call (Seq c1 c2) = head_call c1"
| "head_call _ = False"

lemma head_call_seq_after [simp]:
  "head_call (seq_after c afters) = head_call c"
  by (induction afters arbitrary: c) auto

lemma head_call_not_SKIP: "head_call c ⟹ ¬ is_SKIP c"
  by (cases c) auto

lemma head_call_not_Unwind: "head_call c ⟹ c ≠ Unwind i"
  by (cases c) auto

lemma head_call_seq_after_form:
  "head_call c ⟹ ∃i dst q actuals afters.
     c = seq_after (Call i dst q actuals) afters ∧ special_table q = None"
proof (induction c)
  case (Seq c1 c2)
  from Seq.prems have "head_call c1" by simp
  from Seq.IH(1)[OF this] obtain i dst q actuals afters where
    "c1 = seq_after (Call i dst q actuals) afters" "special_table q = None" by blast
  then have "Seq c1 c2 = seq_after (Call i dst q actuals) (afters @ [c2])"
             "special_table q = None"
    by (simp_all add: seq_after_snoc)
  then show ?case by blast
qed auto

lemma unwinding_not_head_call: "unwinding u ⟹ ¬ head_call u"
  by (induction u rule: unwinding.induct) auto

lemma pop_ready_not_head_call: "pop_ready w ⟹ ¬ head_call w"
  by (cases w rule: pop_ready.cases) (auto simp: unwinding_not_head_call)

subsection ‹The return-initiation source shape›

text ‹‹head_return c› holds when the leftmost-innermost of ‹c› is a source const‹Return›:
  the shape whose next const‹pstep› initiates a return.›
fun head_return :: "'a acom ⇒ bool" where
  "head_return (Return i e) = True"
| "head_return (Seq c1 c2) = head_return c1"
| "head_return _ = False"

lemma head_return_seq_after [simp]:
  "head_return (seq_after c afters) = head_return c"
  by (induction afters arbitrary: c) auto

lemma head_return_not_SKIP: "head_return c ⟹ ¬ is_SKIP c"
  by (cases c) auto

lemma head_return_seq_after_form:
  "head_return c ⟹ ∃i e afters. c = seq_after (Return i e) afters"
proof (induction c)
  case (Seq c1 c2)
  from Seq.prems have "head_return c1" by simp
  from Seq.IH(1)[OF this] obtain i e afters where "c1 = seq_after (Return i e) afters" by blast
  then have "Seq c1 c2 = seq_after (Return i e) (afters @ [c2])" by (simp add: seq_after_snoc)
  then show ?case by blast
qed auto

lemma unwinding_not_head_return: "unwinding u ⟹ ¬ head_return u"
  by (induction u rule: unwinding.induct) auto

lemma pop_ready_not_head_return: "pop_ready w ⟹ ¬ head_return w"
  by (cases w rule: pop_ready.cases) (auto simp: unwinding_not_head_return)

subsection ‹Source well-formedness: the base-activation return guard›

text ‹‹ret_guarded allow_return c› is the deep source-only well-formedness check: an explicit
  const‹Return› (or an in-flight const‹Unwind›) is admissible only where ‹allow_return›
  holds --- inside a called activation.  Permission is ∗‹granted by the activation tail›: the
  right child const‹Restore› of a const‹Seq› marks a callee body, so its left child is
  checked with ‹allow_return = True›, whereas ordinary continuations inherit the ambient
  permission.  A bare const‹Restore› (the transient tail after the callee body reduces to
  const‹SKIP›) is always admissible.  Crucially this looks ∗‹behind› const‹Restore›
  boundaries: a const‹Return› hidden in a base-activation continuation is rejected, which a
  head-only check misses --- exactly the configuration a frame pop would later surface at the
  base.›
fun ret_guarded :: "bool ⇒ 'a acom ⇒ bool" where
  "ret_guarded allow_return (SKIP i) = True"
| "ret_guarded allow_return (Assign i x a) = True"
| "ret_guarded allow_return (VIMP_Proc.gcom.Check i l b) = True"
| "ret_guarded allow_return (Seq c1 c2) =
     (if is_Restore c2 then ret_guarded True c1
      else ret_guarded allow_return c1 ∧ ret_guarded allow_return c2)"
| "ret_guarded allow_return (If i b c1 c2) =
     (ret_guarded allow_return c1 ∧ ret_guarded allow_return c2)"
| "ret_guarded allow_return (While i b c) = ret_guarded allow_return c"
| "ret_guarded allow_return (Call i dst p actuals) = True"
| "ret_guarded allow_return (Return i e) = allow_return"
| "ret_guarded allow_return (Restore i) = True"
| "ret_guarded allow_return (Unwind i) = allow_return"

lemma ret_guarded_True_source: "source_com c ⟹ ret_guarded True c"
  by (induction c) (auto split: if_splits)

lemma ret_guarded_False_source_not_head_return:
  "source_com c ⟹ ret_guarded False c ⟹ ¬ head_return c"
  by (induction c) (auto split: if_splits)

lemma ret_guarded_pstep:
  assumes bodies: "⋀p decl. Π p = Some decl ⟹ source_com (body decl)"
      and step: "𝒢, Π ⊢ (c, s, frs) →p (c', s', frs')"
      and "ret_guarded allow_return c"
  shows "ret_guarded allow_return c'"
  using step assms(3)
proof (induction "(c, s, frs)" "(c', s', frs')"
       arbitrary: c s frs c' s' frs' allow_return rule: pstep.induct)
  case Call
  then show ?case using bodies ret_guarded_True_source by auto
next
  case (Seq2 c1 s1 f1 c1' s1' f1' c2)
  then show ?case by (auto split: if_splits)
qed (auto split: if_splits)

text ‹The runtime invariant ‹csim_step› asks for: the active command may not return until a
  call has opened an activation for it.  Only the command matters --- neither the store nor
  the frame stack --- and unlike a head-only guard this survives a const‹pstep›, because it
  already forbids the base-activation returns that a frame pop would expose.›
definition return_safe :: "'a acom ⇒ bool" where
  "return_safe c ⟷ ret_guarded False c"

lemma return_safe_if_no_return:
  assumes "source_com c" and "no_return c"
  shows "return_safe c"
  using assms
  unfolding return_safe_def
  by (induction c) (auto split: if_splits)

lemma return_safe_not_head_return:
  "return_safe c ⟹ source_com c ⟹ ¬ head_return c"
  by (auto simp: return_safe_def ret_guarded_False_source_not_head_return)

lemma return_safe_pstep:
  assumes bodies: "⋀p decl. Π p = Some decl ⟹ source_com (body decl)"
      and step: "𝒢, Π ⊢ (c, s, frs) →p (c', s', frs')"
      and wf: "return_safe c"
  shows "return_safe c'"
  using ret_guarded_pstep[OF bodies step] wf by (simp add: return_safe_def)

lemma unwinding_seq_after_Unwind:
  "(∀a ∈ set afters. ¬ is_Restore a) ⟹ unwinding (seq_after (Unwind i) afters)"
  by (induction afters rule: rev_induct) (auto simp: seq_after_snoc)

subsection ‹Source-step classification›

text ‹Sorting a source step into the three cases the simulation treats separately: an
  ordinary intra step, a call, or a return in progress.  The classification is what lets each
  preservation proof assume it is in exactly one case rather than re-deriving the exclusions
  from const‹pstep› every time.›
lemma intra_step_not_returning:
  "intra_step Π (c, s, frs) (c', s', frs') ⟹ ¬ is_returning c"
  by (induction "(c, s, frs)" "(c', s', frs')" arbitrary: c s frs c' s' frs'
      rule: intra_step.induct) auto

text ‹A declared procedure's name is never one const‹special_table› also classifies: the
  same disjointness ‹procs_embedded› bundles in the next theory, supplied directly since this
  lemma sits upstream of that definition.  Without it the ‹pstep.Call› case cannot rule out
  ‹c› also matching ‹pstep.Special›, which const‹head_call› excludes.›
lemma pstep_intra_classify:
  assumes disj: "⋀p decl. Π p = Some decl ⟹ special_table p = None"
  shows "𝒢, Π ⊢ (c, s, frs) →p (c', s', frs') ⟹ ¬ head_call c ⟹ ¬ head_return c ⟹
   ¬ is_returning c ⟹ intra_step Π (c, s, frs) (c', s', frs')"
proof (induction "(c, s, frs)" "(c', s', frs')"
       arbitrary: c s frs c' s' frs' rule: pstep.induct)
  case (Seq2 c1 s1 f1 c1' s1' f1' c2)
  from Seq2.prems have "¬ head_call c1" "¬ head_return c1" "¬ is_returning c1" by auto
  from Seq2.hyps(2)[OF this] have ih: "intra_step Π (c1, s1, f1) (c1', s1', f1')" .
  from intra_step_frame_eq[OF ih] ih show ?case by auto
next
  case (Call i p decl actuals dst vals callee s1 frs0)
  then show ?case using disj by simp
qed auto

lemma control_at_not_returning:
  "control_at Π p c0 k n r v ⟹ ¬ is_returning r"
  by (induction rule: control_at.induct) auto

lemma control_at_not_unwind:
  "control_at Π p c0 k n r v ⟹ r ≠ Unwind i"
  using control_at_not_returning by fastforce

text ‹‹is_returning› classifies the active head.  The ‹unwinding› and ‹pop_ready› predicates
  refine that class with the nested residual shape needed by frame-pop proofs; they
  deliberately overlap instead of forming a phase partition.›
lemma unwinding_is_returning: "unwinding u ⟹ is_returning u"
  by (induction u rule: unwinding.induct) auto

lemma pop_ready_is_returning: "pop_ready w ⟹ is_returning w"
  by (cases w rule: pop_ready.cases) (auto simp: unwinding_is_returning)

lemma is_returning_next_check: "is_returning c ⟹ next_check c = None"
  by (induction c) auto

text ‹A source configuration about to run a check is, under const‹csim›, at a node
  whose const‹EA_Check› edge carries that check.  Suspended callers only wrap the
  running residual, and the frame-pop phase runs no check.›
lemma csim_next_check_edge:
  assumes "Π, g, ρ ⊢ (c, s, frs) ≈ (v, t, stk)" and "next_check c = Some (l, e)"
  shows "∃w. (v, EA_Check l e, w) ∈ intra g"
  using assms
proof (induction "(c, s, frs)" "(v, t, stk)" arbitrary: c s frs v t stk rule: csim.induct)
  case (Base p body_p k n c v s)
  from Base.hyps(2) obtain n' en E K
    where "compile Π p body_p k n = (n', en, E, K)" and "E ⊆ intra g"
    by blast
  with control_at_next_check_edge [OF Base.hyps(1) Base.prems] show ?case by blast
next
  case (Returning w i pc body_pc kc nc afters cont callee caller dst p)
  then show ?case by (simp add: is_returning_next_check pop_ready_is_returning)
qed simp

subsection ‹Stepping the frame-pop phase›

text ‹How the phase between a return and its frame pop behaves: it neither changes the store
  nor touches the frames, and it stays in the phase until the pop.  That is what lets the
  simulation hold the graph side still while the source side unwinds.›
lemma pstep_unwinding:
  assumes "unwinding u" and "𝒢, Π ⊢ (u, s, frs) →p (u', s', frs')"
  shows "s' = s ∧ frs' = frs ∧ unwinding u'"
  using assms
proof (induction u arbitrary: u' s' frs')
  case (Seq u1 c2)
  from Seq.prems(1) have u1w: "unwinding u1" and c2R: "¬ is_Restore c2" by auto
  from Seq.prems(2) consider
      (dead) j where "u' = Unwind j" "s' = s" "frs' = frs"
    | (step) c1' where "u' = Seq c1' c2" "𝒢, Π ⊢ (u1, s, frs) →p (c1', s', frs')"
    using c2R unwinding_not_SKIP[OF u1w] by auto
  then show ?case
  proof cases
    case dead then show ?thesis by simp
  next
    case (step c1')
    from Seq.IH(1)[OF u1w step(2)] have "s' = s" "frs' = frs" "unwinding c1'" by simp_all
    then show ?thesis using step(1) c2R by simp
  qed
next
  case SKIP    from SKIP.prems(1)    show ?case by simp
next
  case Assign  from Assign.prems(1)  show ?case by simp
next
  case Check   from Check.prems(1)   show ?case by simp
next
  case If      from If.prems(1)      show ?case by simp
next
  case While   from While.prems(1)   show ?case by simp
next
  case Call    from Call.prems(1)    show ?case by simp
next
  case Return  from Return.prems(1)  show ?case by simp
next
  case Restore from Restore.prems(1) show ?case by simp
next
  case (Unwind i)
  from Unwind.prems(2) show ?case using pstep_Unwind_stuck by blast
qed

lemma pstep_pop_ready_head:
  assumes "pop_ready w" and "pop_ann w = Some i"
      and "𝒢, Π ⊢ (w, s, Frame fr dst # frs) →p x"
  shows "x = (SKIP i, combine_assign dst (s ret_var) (combine_env 𝒢 fr s), frs)
       ∨ (∃w'. x = (w', s, Frame fr dst # frs) ∧ pop_ready w' ∧ pop_ann w' = Some i)"
  using assms
proof (cases w rule: pop_ready.cases)
  case 1
  with assms(2,3) show ?thesis by auto
next
  case (2 u)
  hence uw: "unwinding u" using assms(1) by simp
  from assms(3) show ?thesis
    unfolding 2 using assms(2) unfolding 2
    by (elim SeqSE) (auto dest: pstep_unwinding[OF uw] simp: unwinding_not_SKIP[OF uw])
qed (use assms(1) in auto)

subsection ‹Stepping through a ‹seq_after› spine›

text ‹A step of a const‹seq_after› spine is a step of its head, with the pending
  continuations carried along untouched.  const‹SKIP› and const‹Unwind› heads are excluded
  because those relocate into the spine instead of stepping in place.›
lemma pstep_seq_after_headE [elim]:
  assumes step: "𝒢, Π ⊢ (seq_after w afters, s, frs) →p src'"
      and wsk: "¬ is_SKIP w" and wunw: "⋀i. w ≠ Unwind i"
  obtains h' s' fz where
    "src' = (seq_after h' afters, s', fz)"
    "𝒢, Π ⊢ (w, s, frs) →p (h', s', fz)"
proof -
  have "∃h' s' fz. src' = (seq_after h' afters, s', fz)
          ∧ 𝒢, Π ⊢ (w, s, frs) →p (h', s', fz)"
    using step
  proof (induction afters arbitrary: src' rule: rev_induct)
    case Nil
    then obtain h' s' fz where "src' = (h', s', fz)"
      "𝒢, Π ⊢ (w, s, frs) →p (h', s', fz)"
      by (cases src') auto
    then show ?case by auto
  next
    case (snoc a xs)
    from snoc.prems have "𝒢, Π ⊢ (Seq (seq_after w xs) a, s, frs) →p src'"
      by (simp add: seq_after_snoc)
    then obtain B' s' fz where
      A: "src' = (Seq B' a, s', fz)"
        and B: "𝒢, Π ⊢ (seq_after w xs, s, frs) →p (B', s', fz)"
      using wsk wunw by auto
    from snoc.IH[OF B] obtain h' where
      "B' = seq_after h' xs" "𝒢, Π ⊢ (w, s, frs) →p (h', s', fz)" by auto
    then show ?case using A by (auto simp: seq_after_snoc)
  qed
  then show ?thesis using that by blast
qed

end