Theory unfold_bind_options

(*
 * Copyright (c) 2022 Apple Inc. All rights reserved.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *)


theory unfold_bind_options
imports "AutoCorres2_Main.AutoCorres_Main"
begin



install_C_file "unfold_bind_options.c"

text ‹

This theory demonstrates the autocorres option ‹unfold_constructor_bind›, which allows
to tweak the behaviour of expanding certain 'simple' assignments which manifest in 'bind' variants.

Unfolding the bind can trigger simplifications later on in the code (e.g. guards). As 
a drawback the correspondence to the original C code gets obfuscated, as intermediate assignments
disappear.

Currently the following heuristics is implemented:
▪ If the assigned value is atomic the bind is unfolded
▪ If the assigned value is only used once in the following code it is unfolded.
▪ If none of the above rules triggered and the assigned value is a compound C type (aka struct) then
  ▪ In case it is a constructor expression the option ‹unfold_constructor_bind› is used.
    ▪ Never: the bind in not unfolded.
    ▪ Always: the bind is always unfolded.
    ▪ Selectors: the bind is unfolded if the constructor is only applied to field-selectors. This
      is the default option.
  ▪ In case it is an update-expression then the bind is unfolded if it is only applied to the
    corresponding field-selector.

This is immplemented via a simproc: @{ML L2Opt.l2_marked_gets_bind_simproc'}
›



autocorres [scope = add add3] "unfold_bind_options.c"

thm ts_def

text ‹Here we demonstrate the case were the assigned value is only used once. Even if
the option is ‹Never› the bind gets unfolded.›
autocorres [scope = single, unfold_constructor_bind = Never] "unfold_bind_options.c"

lemma "single' inner ≡ add' (fld1_C inner) 1" 
  by (simp add: ts_def)


text ‹Here the constructor is only used with field selectors, hence the bind gets unfolded
with option ‹Selectors›, which is also the default option.›
autocorres [scope = foo_selectors, unfold_constructor_bind=Selectors] "unfold_bind_options.c"

lemma "foo_selectors' inner ≡ add' (fld1_C inner) 0x2A"
  by (simp add: ts_def)


text ‹You can force unfolding by setting the option to ‹Always›. Note that the resulting
function is in the "pure"-monad. The "bind" became a "let" in the final outcome.›
autocorres [scope = foo_never, unfold_constructor_bind=Never] "unfold_bind_options.c"

lemma "foo_never' inner ≡
  let my_inner = inner_C (fld1_C inner) 0x2A
  in add' (fld1_C my_inner) (fld2_C my_inner)"
  by (simp add: ts_def)


text ‹We can surpress the unfolding by setting the option to ‹Never›.›
autocorres [scope = foo_never, unfold_constructor_bind = Never] "unfold_bind_options.c"

lemma "foo_never' inner ≡
  let my_inner = inner_C (fld1_C inner) 0x2A
  in add' (fld1_C my_inner) (fld2_C my_inner)"
  by (simp add: ts_def)
thm ts_def 


text ‹Here is an example where the constructor is not only applied to selectors, but passed
down to a function. The bind is hence not unfolded by default (option ‹Selectors›) ›
autocorres [scope = bar_selectors, unfold_constructor_bind = Selectors] "unfold_bind_options.c"



lemma "bar_selectors' inner ≡
  let my_inner = inner_C (fld1_C inner) 0x2A
  in add3' 1 my_inner + add3' 2 my_inner"
  by (simp add: ts_def)

text ‹You can force unfolding by setting the option to ‹Always›.›
autocorres [scope = bar_always, unfold_constructor_bind = Always] "unfold_bind_options.c"

lemma "bar_always' inner ≡
  add3' 1 (inner_C (fld1_C inner) 0x2A) +
  add3' 2 (inner_C (fld1_C inner) 0x2A)"
  by (simp add: ts_def)

end