/-!
# Tests for the `floatLetIn` compiler pass
-/

structure State where
  abc : Nat
  xyz : Array Nat
  bool : Bool
deriving Inhabited

def State.size (t : State) : Nat := t.xyz.size

@[extern]
opaque State.restore (abc : Nat) (size : Nat) (s : State) : State

def StateFn := State → State

set_option trace.Compiler.saveMono true

/-!
The `floatLetIn` pass moves values (here `st.size`) into one branch if they are only used in that
branch.
-/

/--
trace: [Compiler.saveMono] size: 7
    def test1 st : State :=
      cases st : State
      | State.mk abc xyz bool =>
        cases bool : State
        | Bool.false =>
          return st
        | Bool.true =>
          let b := State.size st;
          let _x.1 := State.restore abc b st;
          return _x.1
-/
#guard_msgs in
def test1 : StateFn := fun st =>
  let a := st.abc
  let b := st.size
  if st.bool then
    st.restore a b
  else
    st

/-!
This doesn't occur if it would destroy linearity.
-/

/--
trace: [Compiler.saveMono] size: 10
    def test2 fn st : State :=
      cases st : State
      | State.mk abc xyz bool =>
        let b := State.size st;
        let st := fn st;
        cases st : State
        | State.mk abc xyz bool =>
          cases bool : State
          | Bool.false =>
            return st
          | Bool.true =>
            let _x.1 := State.restore abc b st;
            return _x.1
-/
#guard_msgs in
def test2 (fn : StateFn) : StateFn := fun st =>
  let a := st.abc
  let b := st.size
  let st := fn st
  if st.bool then
    st.restore a b
  else
    st

/-!
Passing a value to a function as a borrowed parameter doesn't count as a linear pass and thus values
can be happily passed through it.
-/

/--
trace: [Compiler.saveMono] size: 0
    def State.remake @&s : State :=
      extern
-/
#guard_msgs in
@[extern]
opaque State.remake (s : @& State) : State

/--
trace: [Compiler.saveMono] size: 10
    def test3 st : State :=
      let st := State.remake st;
      cases st : State
      | State.mk abc xyz bool =>
        cases bool : State
        | Bool.false =>
          return st
        | Bool.true =>
          cases st : State
          | State.mk abc xyz bool =>
            let b := State.size st;
            let _x.1 := State.restore abc b st;
            return _x.1
-/
#guard_msgs in
def test3 : StateFn := fun st =>
  let a := st.abc
  let b := st.size
  let st := State.remake st
  if st.bool then
    st.restore a b
  else
    st

/-!
This also doesn't occur if there are indirect uses of a variable in both branches.
-/

/--
trace: [Compiler.saveMono] size: 13
    def test4 st : State :=
      cases st : State
      | State.mk abc xyz bool =>
        let b := State.size st;
        let _x.1 := 1;
        let c := Nat.add b _x.1;
        cases bool : State
        | Bool.false =>
          let _x.2 := Nat.add abc _x.1;
          let _x.3 := 2;
          let _x.4 := Nat.add c _x.3;
          let _x.5 := State.restore _x.2 _x.4 st;
          return _x.5
        | Bool.true =>
          let _x.6 := State.restore abc c st;
          return _x.6
-/
#guard_msgs in
def test4 : StateFn := fun st =>
  let a := st.abc
  let b := st.size
  let c := b + 1
  if st.bool then
    st.restore a c
  else
    st.restore (a + 1) (c + 2)
