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

Unique specifier for types leading to using different fields in the backend #1219

Draft
wants to merge 14 commits into
base: dev
Choose a base branch
from

Conversation

sakehl
Copy link
Contributor

@sakehl sakehl commented Jul 1, 2024

  • The wiki is updated in accordance with the changes in this PR. For example: syntax changes, semantics changes, VerCors flags changes, etc.

PR Description

Having many arrays in scope at the same time leads to poor performance in Silicon, to the point where it is not possible to verify files with many arrays present. A workaround is to use different fields for different arrays if you are sure the arrays will never overlap.

With this pull request, I want to add the ability to do this, by marking the type of an array as unique, indicating that it does not overlap with other arrays. In the backend, this will generate different types of fields for each array.

In the frontend it looks like this

/*@
  context_everywhere x0 != NULL ** \pointer_length(x0) == n ** (\forall* int i; 0<=i && i<n; Perm(&x0[i], write));
  context_everywhere x1 != NULL ** \pointer_length(x1) == n ** (\forall* int i; 0<=i && i<n; Perm(&x1[i], 1\2));
  context_everywhere x2 != NULL ** \pointer_length(x2) == n ** (\forall* int i; 0<=i && i<n; Perm(&x2[i], 1\2));
  ensures (\forall int i; 0<=i && i<n; x0[i] == x1[i] + x2[i] );
@*/
int main(int n, /*@ unique<1> @*/ int* x0, /*@ unique<2> @*/ int* x1, /*@ unique<2> @*/ int* x2){
  /*@
    loop_invariant 0 <= j && j <= n;
    loop_invariant (\forall int i; 0<=i && i<j; x0[i] == x1[i] + x2[i] );
  @*/
  for(int j=0; j<n; j++){
    x0[j] = x1[j] + x2[j];
  }
}

In the generated viper file we would like to have the following fields and permissions:

field int1: Int
field int2: Int

(forall i: Int ::
      { ptrDeref(ptrAdd(optGet2(x0), i)).int1 }
      0 <= i && i < n ==>
      acc(ptrDeref(optGet2((some(ptrAdd(optGet2(x0), i)): Option[Pointer]))).int1, write))

(forall i: Int ::
      { ptrDeref(ptrAdd(optGet2(x1), i)).int2 }
      0 <= i && i < n ==>
      acc(ptrDeref(optGet2((some(ptrAdd(optGet2(x1), i)): Option[Pointer]))).int2, write))

I want to start by verifying the above simple example.
Internal I probably want to add a new Array Type, which is unique. To make sure that we do not mix-up arrays of different uniques.

Extensions

Further options I want to consider are the following.

  • Called functions with unique arrays as arguments, will need their own versions. E.g.

    /*@ requires xs != NULL ** \pointer_length(xs)>1 ** Perm(&xs[0], write);
           ensures xs != NULL ** \pointer_length(xs)>1 ** Perm(&xs[0], write) ** xs[0] == \old(xs[0])+1@*/
    void f(int* xs){
      return xs[0] = xs[0]+1;
    }
    
    /*@ ... @*/
    int g(int* xs0, /*@ unique<0> @*/ int* xs1, /*@ unique<1> @*/ int* xs2){
      f(xs0);
      f(xs1);
      f(xs2);
      //@ assert (xs0[0] == \old(xs0[0]) + 1 && (xs1[0] == \old(xs1[0]) + 1 && (xs2[0] == \old(xs2[0]) + 1
    }

    should verify. But then we need 3 versions of f. The normal version, and two versions that work for unique<0> and unique<1>. I intend that the two other versions are abstract functions with the same signature and annotations, such that we do not do extra verification.

  • I first consider this only for C, since that is my use case. We could extend this to PVL as well? And the other languages if we want.

  • We could consider adding an automatic program pass, which we can call via an extra option in VerCors, which automatically marks all declared arrays unique, except when this is not possible and we know they overlap. For instance with recursive functions or we see that the code makes the arrays overlap somehow. This is then not the default behavior, but allows the user to use this without annotation effort.

  • I want to start with arrays. But this is also useful for other references where we use fields. So for examples attributes from classes or structs.

    • When we do want to support struct we do have the following pattern:

      struct d { int* xs; }
      
      main(struct d a, struct d b){ ... }

      Where I might want to indicate that a.xs is unique and b.xs is unique. I'm unsure how I want to do this yet. Maybe like:
      /*@ unique_field<0>(xs) @*/ struct d a. Maybe any comments on this?

      • The above touches on something I've considered for some time already, namely that in C files, types are something used for templating structs or functions. E.g. you just give something the type 'void *' to to mean that a thing should be used integer, float, char, or whatever array. So I was considering if we want to give things a 'phantom type'. So with a phantom type you could write something like
        struct d { int* xs; }
        /*@ struct d_1 { unique<1> int* xs; } @*/
        /*@ struct d_2 { unique<2> int* xs; } @*/
        
        main(/*@ as struct d_1 @*/ struct d a, /*@ as struct d_2 @*/ struct d b){ ... }
        Although I'm not sure about this yet.

@sakehl sakehl self-assigned this Jul 1, 2024
@sakehl sakehl marked this pull request as draft July 1, 2024 12:22
@pieter-bos
Copy link
Member

Nice! You should know that @superaxander (and sometimes me) are working on some proposed silicon changes that address similar concerns, although we've focused on singleton heap chunks (i.e. plain permission without quantification), but I don't see a strong reason why e.g. the proposed chunk ordering heuristics would not improve things. Maybe we should have a look at some slow array examples again with our recently gained knowledge.

@superaxander
Copy link
Member

Yes this would be quite useful. Ideally there'd be a way we could give Viper a better view of which fields may alias and which may not instead of simply relying on the name of the field. For example I want to be able to say the field int_1 and int_2 never alias but both of them may alias with int_3 which would drastically reduce the amount of heap chunks viper has to consider when dealing with a pointer to a field int_1 or int_2. (additionally during state consolidation Viper will take every combination of two heap chunks with the same field name to attempt to prove that they do or don't alias, this generates an exponential amount of proof goals)

Your example of wanting to specify that the two fields are unique is basically the exact thing I've been working on for the past two weeks with my by-value classes. Because I'm encoding structs as ADTs with pointers inside of them we have for a struct:

struct A {
   int a;
   int b;
}

That we know that &a and &b must always be different. (and this is also true between different structs and their fields) However, a normal int pointer may alias with these field pointers therefore in the general case we cannot give them a separate field name in viper. Being able to annotate/automatically detect the easy cases where no aliasing may occur might help a lot though.

As for specifying a ghost type of e.g. void pointers I have also been thinking about that since this kind of pattern occurs everywhere in LLVM. (all pointers are untyped in LLVM) My idea so far was to extend the notion of permission to being "permission to access as if it were a certain type". For example:

//@ requires Perm(a, write, int)
void foo(void *a)  {
    int b = *((int *) a)
}

You could then call this function with many different kinds of values which can all be treated as int pointers:

struct A a;
a.a = 5;
// A pointer to a value "struct A" can be treated as an int pointer
foo(&a);
// This is already an int pointer
foo(&a.b);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants