Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deep structure updates in non-aliased imperative (was: computes to use condition as a more efficient implementation) #1465

Open
samuelchassot opened this issue Nov 1, 2023 · 4 comments
Assignees
Labels
aliasing Alias and effect analysis for imperative feature imperative

Comments

@samuelchassot
Copy link
Collaborator

Add computes that works as follows:

def f(x: BigInt): BigInt = {
    body(x)
} computes(v(x))

By default, computes behaves as ensuring(_ == v(x)) but with StaticChecks, it inlines v(x) and ignores body(x).

We can also use this to implement a tail pointer on list, with the update to the new value being in the computes, and the recursive traversal in body(...).

@samuelchassot samuelchassot changed the title Computes to use condition as a more efficient implementation Computes to use condition as a more efficient implementation Nov 1, 2023
@vkuncak vkuncak added imperative aliasing Alias and effect analysis for imperative labels Nov 1, 2023
@vkuncak
Copy link
Collaborator

vkuncak commented Nov 1, 2023

The question is, if we mean that v(x) is the implementation, why do we not write it the other way round,

def f(x: BigInt): BigInt = {
  v(x)
}.ensuring(_ == body(x))

Our motivation was that body would be defined in a way that passes aliasing restrictions, whereas v(x) would possibly break it. But I do not see how v(x) would be allowed to break it.

@vkuncak
Copy link
Collaborator

vkuncak commented Nov 1, 2023

The idea is basically to implement a form of field constraint analysis in Stainless

http://lara.epfl.ch/~kuncak/papers/WiesETAL06FieldConstraintAnalysis.pdf

which built on

N. Klarlund and M. I. Schwartzbach. Graph types. In Proc. 20th ACM POPL, Charleston, SC, 1993.
A. Møller and M. I. Schwartzbach. The Pointer Assertion Logic Engine. In Programming Language
Design and Implementation, 2001.

@vkuncak
Copy link
Collaborator

vkuncak commented Nov 1, 2023

What we really seem to need here is encapsulating private state. We can probably introduce this as an abstraction of stateful function pattern

def testOrder(t1: SFun[Unit,Int,Int], t2: SFun[Unit,Int,Int], init: Int): Unit = {

but restricted in both initialization and state change so that the state is not externally observable.

@vkuncak vkuncak changed the title Computes to use condition as a more efficient implementation Deep structure updates in non-aliased imperative (was: computes to use condition as a more efficient implementation) Nov 2, 2023
@vkuncak vkuncak added the feature label Nov 2, 2023
@vkuncak
Copy link
Collaborator

vkuncak commented Nov 2, 2023

To make things more concrete, the code below tries to implement a list with a tail pointer.

  import stainless.lang.*
  import stainless.lang.StaticChecks.*
  import stainless.annotation.*

  @mutable
  sealed abstract class Opt[@mutable T]
  case class NONE[@mutable T]() extends Opt[T]
  case class SOME[@mutable T](value: T) extends Opt[T]

  @ignore
  class pointer extends scala.annotation.Annotation

  final case class Node(var head: Int, var next: Cell[Opt[Node]])

  @extern
  def getLast(n: Node): Node =    
    n.next.v match
      case NONE() => n
      case SOME(next) => getLast(next)

  final case class TList(var first: Node,
                         @pointer var last: Node) {
    require(last == getLast(first))

    def isEmpty: Boolean =
      first.next.v == NONE()

    def popFirst: Int =
      require(!isEmpty)
      n.next.v match
        case SOME(n1) => 
          n1.head
          n.next = Cell(NONE())

    def addToEnd(x: Int): Unit =
      updateLast(last, mkEnd(x))
  }

  def updateLast(last: Node, newOne: Node): Unit =
    last.next.v = SOME(newOne)

  def mkEnd(head: Int): Node = Node(head, Cell(NONE[Node]()))

The idea would be that an invocation updateLast(last, mkEnd(x)) would get rewritten, for the purpose of verification but not code generation, to resolve_last_updateLast(first, mkEnd(x)) where

  def resolve_last_updateLast(n: Node, newOne: Node): Node =    
    n.next.v match
      case NONE() => n
      case SOME(next) =>  // computation of last as in getLast
        last.next.v = SOME(newOne) // the body of the original updateLast      

The @pointer fields need to be exempt from alias checks. All their uses need to be rewritten.
Perhaps a bit unusually, we can write to such pointer fields, but in the current getLast is not accepted as it returns a deep pointer.

This suggest that accessors should be written explicitly as accessor mutators, e.g.:

  def applyToLast(n: Node, action: Node => Unit): Unit = 
    decreases(n.len)
    n.next.v match
      case NONE() => action(n)
      case SOME(next) => applyToLast(next, action)

but then it is not clear how to relate the path traversed and the value traversed and how to write class invariants.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aliasing Alias and effect analysis for imperative feature imperative
Projects
None yet
Development

No branches or pull requests

2 participants