# Structural proof data indexed by an erased affine owner. Runtime owners carry # flat certificates; this tree is used only by proofs, never by numerical loops. import Base import ./storage_tree_model.bend as Tree import ./storage_shape.bend as Shape law Witness: for -Element: Data for depth: Nat for storage: Array Data type LeafWitness<-Element: Data,-storage: Array> is Data: Leaf{-value: Element,owner: {storage == ALeaf{value} : Array}} type NodeWitness<-Element: Data,-depth: Nat,-storage: Array> is Data: Node{-left: Array,-right: Array,owner: {storage == ANode{left,right} : Array}, left_balanced: Witness(Element,depth,left),right_balanced: Witness(Element,depth,right)} def Witness(Element,depth,storage): match depth: case 0n: LeafWitness case 1n+rest: NodeWitness def balanced(-Element: Data,depth: Nat,-storage: Array,witness: Witness(Element,depth,storage)) -> Shape.Balanced(Element,depth,Tree.reflect(Element,storage)): match depth witness: case 0n Leaf{value,owner}: %Equal.sym(Array,storage,ALeaf{value},owner) : Shape.Balanced(Element,0n,Tree.reflect(Element,_)) Unit{} case 1n+ +rest Node{left,right,owner,left_balanced,right_balanced}: %Equal.sym(Array,storage,ANode{left,right},owner) : Shape.Balanced(Element,1n+rest,Tree.reflect(Element,_)) Shape.Both{balanced(Element,rest,left,left_balanced),balanced(Element,rest,right,right_balanced)} def from_shape(-Element: Data,+depth: Nat,+tree: Tree.Tree,balanced: Shape.Balanced(Element,depth,tree)) -> Witness(Element,depth,Tree.pack(Element,tree)): match depth tree: case 0n Tree.Leaf{value}: Leaf{value,{==}} case 0n Tree.Node{left,right}: match balanced: case 1n+rest Tree.Leaf{value}: match balanced: case 1n+ +rest Tree.Node{+left,+right}: Shape.Both{left_balanced,right_balanced} = balanced Node{Tree.pack(Element,left),Tree.pack(Element,right),{==},from_shape(Element,rest,left,left_balanced),from_shape(Element,rest,right,right_balanced)} def fresh_model(-Element: Data,+depth: Nat,+value: Element) -> {Array.new(Element,depth,value) == Tree.pack(Element,Tree.fresh(Element,depth,value)) : Array}: match depth: case 0n: {==} case 1n+ +rest: %fresh_model(Element,rest,value) : {Array.new(Element,1n+rest,value) == ANode{_,Tree.pack(Element,Tree.fresh(Element,rest,value))} : Array} %fresh_model(Element,rest,value) : {Array.new(Element,1n+rest,value) == ANode{Array.new(Element,rest,value),_} : Array} {==} def fresh(-Element: Data,+depth: Nat,+value: Element) -> Witness(Element,depth,Array.new(Element,depth,value)): %Equal.sym(Array,Array.new(Element,depth,value),Tree.pack(Element,Tree.fresh(Element,depth,value)),fresh_model(Element,depth,value)) : Witness(Element,depth,_) from_shape(Element,depth,Tree.fresh(Element,depth,value),Shape.fresh(Element,depth,value)) def size(-Element: Data,+depth: Nat,-storage: Array,witness: Witness(Element,depth,storage)) -> {Array.size(Element,storage) == (storage,U32.shln(1,depth)) : Array & U32}: match depth witness: case 0n Leaf{value,owner}: %Equal.sym(Array,storage,ALeaf{value},owner) : {Array.size(Element,_) == (_,U32.shln(1,0n)) : Array & U32} {==} case 1n+ +rest Node{left,right,owner,left_balanced,right_balanced}: %Equal.sym(Array,storage,ANode{left,right},owner) : {Array.size(Element,_) == (_,U32.shln(1,1n+rest)) : Array & U32} %size(Element,rest,left,left_balanced) : {Array.size.node(Element,right,Array.size(Element,left)) == Array.size.node(Element,right,_) : Array & U32} {==} type Swapped<-Element: Data,-depth: Nat,-result: Array & Element> is Data: Swapped{-storage: Array,-previous: Element, equation: {result == (storage,previous) : Array & Element},balanced: Witness(Element,depth,storage)} def swap_left(-Element: Data,-depth: Nat,-right: Array,-result: Array & Element, swapped: Swapped,right_balanced: Witness(Element,depth,right)) -> Swapped: match swapped: case Swapped{storage,previous,equation,left_balanced}: Swapped{ANode{storage,right},previous, Equal.cong(Array & Element,Array & Element, pair => Array.swap.lo(Element,right,pair),result,(storage,previous),equation), Node{storage,right,{==},left_balanced,right_balanced}} def swap_right(-Element: Data,-depth: Nat,-left: Array,-result: Array & Element, left_balanced: Witness(Element,depth,left),swapped: Swapped) -> Swapped: match swapped: case Swapped{storage,previous,equation,right_balanced}: Swapped{ANode{left,storage},previous, Equal.cong(Array & Element,Array & Element, pair => Array.swap.hi(Element,left,pair),result,(storage,previous),equation), Node{left,storage,{==},left_balanced,right_balanced}} # Base passes the branch z (i < n/2) computed by its caller; any z is covered # here, and each child receives the comparison against its own half. def swap_choose(-Element: Data,-depth: Nat,-left: Array,-right: Array, +capacity: U32,+index: U32,-value: Element,choose_left: Bool, left_balanced: Witness(Element,depth,left),right_balanced: Witness(Element,depth,right), left_result: Swapped, right_result: Swapped) -> Swapped: match choose_left: case True{}: swap_left(Element,depth,right,Array.swap.go(Element,left,U32.shr(capacity),index,value,U32.is_lt(index,U32.shr(U32.shr(capacity)))), left_result,right_balanced) case False{}: swap_right(Element,depth,left,Array.swap.go(Element,right,U32.shr(capacity),U32.sub(index,U32.shr(capacity)),value, U32.is_lt(U32.sub(index,U32.shr(capacity)),U32.shr(U32.shr(capacity)))),left_balanced,right_result) def swap(-Element: Data,+depth: Nat,-storage: Array,+capacity: U32,+index: U32,-value: Element,choose_left: Bool, witness: Witness(Element,depth,storage)) -> Swapped: match depth witness: case 0n Leaf{previous,owner}: %Equal.sym(Array,storage,ALeaf{previous},owner) : Swapped Swapped{ALeaf{value},previous,{==},Leaf{value,{==}}} case 1n+ +rest Node{left,right,owner,+left_balanced,+right_balanced}: %Equal.sym(Array,storage,ANode{left,right},owner) : Swapped swap_choose(Element,rest,left,right,capacity,index,value,choose_left,left_balanced,right_balanced, swap(Element,rest,left,U32.shr(capacity),index,value,U32.is_lt(index,U32.shr(U32.shr(capacity))),left_balanced), swap(Element,rest,right,U32.shr(capacity),U32.sub(index,U32.shr(capacity)),value, U32.is_lt(U32.sub(index,U32.shr(capacity)),U32.shr(U32.shr(capacity))),right_balanced)) def finish(-Element: Data,-depth: Nat,-result: Array & Element,swapped: Swapped) -> Witness(Element,depth,Array.set.fin(Element,result)): match swapped: case Swapped{storage,previous,equation,witness}: %Equal.sym(Array & Element,result,(storage,previous),equation) : Witness(Element,depth,Array.set.fin(Element,_)) witness def write(-Element: Data,+depth: Nat,-storage: Array,+index: U32,-value: Element, +witness: Witness(Element,depth,storage)) -> Witness(Element,depth,Array.set(Element,storage,index,value)): %Equal.sym(Array & U32,Array.size(Element,storage),(storage,U32.shln(1,depth)),size(Element,depth,storage,witness)) : Witness(Element,depth,Array.set.fin(Element,Array.swap.at(Element,index,value,_))) +start = U32.and(index,U32.sub(U32.shln(1,depth),1)) +choose_left = U32.is_lt(start,U32.shr(U32.shln(1,depth))) finish(Element,depth,Array.swap.go(Element,storage,U32.shln(1,depth),start,value,choose_left), swap(Element,depth,storage,U32.shln(1,depth),start,value,choose_left,witness)) # Reading returns precisely the original affine owner. The observed element is # an erased index of this proof; no runtime sample is copied into evidence. type Read<-Element: Data,-storage: Array,-result: Array & Element> is Data: Read{-value: Element,equation: {result == (storage,value) : Array & Element}} def read_left(-Element: Data,-left: Array,-right: Array,-result: Array & Element, read: Read) -> Read: match read: case Read{value,equation}: Read{value,Equal.cong(Array & Element,Array & Element, pair => Array.swap.lo(Element,right,pair),result,(left,value),equation)} def read_right(-Element: Data,-left: Array,-right: Array,-result: Array & Element, read: Read) -> Read: match read: case Read{value,equation}: Read{value,Equal.cong(Array & Element,Array & Element, pair => Array.swap.hi(Element,left,pair),result,(right,value),equation)} def read_choose(-Element: Data,-left: Array,-right: Array,+capacity: U32,+index: U32,choose_left: Bool, left_read: Read, right_read: Read) -> Read: match choose_left: case True{}: read_left(Element,left,right,Array.get.go(Element,left,U32.shr(capacity),index,U32.is_lt(index,U32.shr(U32.shr(capacity)))),left_read) case False{}: read_right(Element,left,right,Array.get.go(Element,right,U32.shr(capacity),U32.sub(index,U32.shr(capacity)), U32.is_lt(U32.sub(index,U32.shr(capacity)),U32.shr(U32.shr(capacity)))),right_read) def read_tree(-Element: Data,+depth: Nat,-storage: Array,+capacity: U32,+index: U32,choose_left: Bool, witness: Witness(Element,depth,storage)) -> Read: match depth witness: case 0n Leaf{value,owner}: %Equal.sym(Array,storage,ALeaf{value},owner) : Read Read{value,{==}} case 1n+ +rest Node{left,right,owner,left_balanced,right_balanced}: %Equal.sym(Array,storage,ANode{left,right},owner) : Read read_choose(Element,left,right,capacity,index,choose_left, read_tree(Element,rest,left,U32.shr(capacity),index,U32.is_lt(index,U32.shr(U32.shr(capacity))),left_balanced), read_tree(Element,rest,right,U32.shr(capacity),U32.sub(index,U32.shr(capacity)), U32.is_lt(U32.sub(index,U32.shr(capacity)),U32.shr(U32.shr(capacity))),right_balanced)) def read(-Element: Data,+depth: Nat,-storage: Array,+index: U32,+witness: Witness(Element,depth,storage)) -> Read: %Equal.sym(Array & U32,Array.size(Element,storage),(storage,U32.shln(1,depth)),size(Element,depth,storage,witness)) : Read +start = U32.and(index,U32.sub(U32.shln(1,depth),1)) read_tree(Element,depth,storage,U32.shln(1,depth),start,U32.is_lt(start,U32.shr(U32.shln(1,depth))),witness) # Existential depth travels with proof data, not numerical storage. type ArrayWitness<-Element: Data,-storage: Array> is Data: ArrayWitness{depth: Nat,witness: Witness(Element,depth,storage)} def allocated(-Element: Data,+depth: Nat,value: Element) -> ArrayWitness: ArrayWitness{depth,fresh(Element,depth,value)} def stored(-Element: Data,-storage: Array,+index: U32,-value: Element,evidence: ArrayWitness) -> ArrayWitness: ArrayWitness{+depth,witness} = evidence ArrayWitness{depth,write(Element,depth,storage,index,value,witness)} def observed(-Element: Data,-storage: Array,index: U32,evidence: ArrayWitness) -> Read: ArrayWitness{depth,witness} = evidence read(Element,depth,storage,index,witness) # The native clone duplicates payload ownership, while its source observation # and balance depth are unchanged. This theorem is consumed only by rewrites. def clone(-Element: Data,+depth: Nat,-storage: Array,witness: Witness(Element,depth,storage)) -> {Array.clone(Element,storage) == (storage,storage) : Array & Array}: match depth witness: case 0n Leaf{value,owner}: %Equal.sym(Array,storage,ALeaf{value},owner) : {Array.clone(Element,_) == (_,_) : Array & Array} {==} case 1n+ +rest Node{left,right,owner,left_balanced,right_balanced}: %Equal.sym(Array,storage,ANode{left,right},owner) : {Array.clone(Element,_) == (_,_) : Array & Array} %Equal.sym(Array & Array,Array.clone(Element,left),(left,left),clone(Element,rest,left,left_balanced)) : {Array.clone.node(Element,_,Array.clone(Element,right)) == (ANode{left,right},ANode{left,right}) : Array & Array} %Equal.sym(Array & Array,Array.clone(Element,right),(right,right),clone(Element,rest,right,right_balanced)) : {Array.clone.node(Element,(left,left),_) == (ANode{left,right},ANode{left,right}) : Array & Array} {==}