| Version 108 (modified by , 10 years ago) ( diff ) |
|---|
The CIVL-IR language. A program in this language is also known as a "CIVL model".
Properties of the language:
- the language is not intended to be written by humans; it is an intermediate form constructed by CIVL. However it should be readable to help debug things
- a CIVL-IR program represents a guarded-transition system explicitly
- as in CIVL-C, there are functions, scopes, and functions can be defined in any scope
- all blocks (including a function definition) consist of the following elements, in this order:
- a sequence of contract clauses (for function definitions only)
- a sequence of type definitions
- a sequence of variable declarations (with no initializers)
- a sequence of function definitions
- a body, which encodes a program graph.
- an array is declared without any length expression. When it is initialized it can specify length.
- curly braces are used only to indicate scopes, as in
{ // new scope ... } - parentheses are used to indicate function invocations, as in add(x,y)
- angular brackets are used to delimit tuples or sequences
- square brackets are used to delimit parameters in types
- unlike C, there is no "array-pointer pun". If an array
aneeds to be converted to a pointer, you must useaddr(asub(a, 0)). - there are no automatic conversions. All conversions must be by explicit casts or other functions. Operations such as numeric addition (
add) require that both operands have the exact same type. - in general, symbols can be used before they are defined, as long as they are in scope. For example, a function
fcan callg, even ifgis defined afterfin the same scope. There is no need for a "prototype". Similarly, a type definition can refer to a type defined later, or can refer to itself in its own definition.
Grammar
This is not the complete grammar, but the high-level overview.
program: typedef* vardecl* fundef+ ;
block: '{' typedef* vardecl* fundef* statement* '}' ;
statement: block | basicStmt ;
basicStmt: (ID ':')? (simpleStmt | chooseStmt) ;
simpleStmt: (guardedStmt | primitiveStmt) ('goto' ID)? ;
guardedStmt: 'when' expr 'do' primitiveStmt ;
chooseStmt: 'begin choose' simpleStmt+ 'end choose' ;
typedef: 'type' ID '=' typeName ';' ;
vardecl: 'var' varopts? ID ':' typeName ';' ;
varopts: '[' varopt+ ']' ;
varopt: 'input' | 'output' ;
fundef: 'fun' funopts? ID '(' paramlist ')' ':' typeName conclause* block ;
funopts: '[' funopt+ ']' ;
conclause : 'assigns' expr ';'
| 'requires' expr ';'
| 'ensures' expr ';'
...
;
Notes:
- If goto is missing, default is the next location.
- If guard missing, default is
true.
Example:
fun f(u:Integer, a:Array[Real]): Integer {
var x: Real;
var y: Real;
var z: Float[16,23];
L1 :
begin choose
when g1 do stmt1; goto L2;
when g2 do stmt2; goto L3;
end choose
{ // begin new scope
x: Real;
L2 :
stmt3; goto L4;
...
} // end new scope
...
}
Example: the C code
{
int x=3*y;
int a[x+1];
}
could be represented as
{
var x: Integer;
var a_t : Dytype[Array[Integer]];
var a: Array[Integer];
ASSIGN "x", mul(3,"y");
ASSIGN "a_t", dytype(Array[Integer, add("x",1)]);
ASSIGN "a", new("a_t");
}
Types
The types (and their type names) are:
Bool: boolean type, values aretrueandfalseProc: process typeScope: scope typeChar: character typeBundle: type representing some un-typed chunk of dataHeap: heap typeRange: ordered set of integersDomain: ordered set of tuples of integersDomain[n], n is an integer at least 1; subtype ofDomainin which all tuples have arity n.Enumtypes.- different from integers or like C?
- The numeric types
- The integral types
Integer: the mathematical integersInt[lo,hi,wrap]lo,hiare concrete (?) integers,wrapis boolean- finite interval of integers
[lo,hi]. Ifwrapis true then all operations "wrap", otherwise, any operation resulting in a value outside of the interval results in an exception being thrown. - Do we want to allow
loandhito be any values of typeInteger, which means they are dynamic types, like complete array types?
- The real types
Real: the mathematical real numbersFloat[e,f],e,fare concrete integers, each at least 1.- IEEE754 floating point numbers. This type includes NaNs, negative 0s, infinities, etc.
- Herbrand[T], for a non-Herbrand numeric type
T- the values of this type are (unsimplified) symbolic expressions. Each numeric operator is treated as an uninterpreted function. The leaf nodes in an expression are values of type
T. Two values are equal iff the two symbolic expressions are identical (using equality inTon the leaf nodes). A value of Herbrand type may therefore be thought of as representing an expression inTbut where all the numerical operations have been "delayed".
- the values of this type are (unsimplified) symbolic expressions. Each numeric operator is treated as an uninterpreted function. The leaf nodes in an expression are values of type
- The integral types
Tuple[<T0, T1, ...>]: a tuple type, the Cartesian product ofT0,T1, ...- What about bit-widths?
Union[<T0, T1, ...>]: union type, the disjoint union ofT0,T1, ...Array[T]: arrays of any length whose elements belong to T- Note: there is also a dynamic type
Array[T,n], signifying arrays of length n. That is a subtype of the dynamic type ofArray[T].
- Note: there is also a dynamic type
Function[<T0,T1,...>,T]: functions consumingT0,T1,... and returningT.Tcan bevoidto indicate nothing is returned.Mem: type representing a memory set. May be thought of as a set of pointers.Pointer: all pointers, a subtype ofMemPointer[T]: pointer-to-T, subtype ofPointerDytype: the set of all dynamic typesDytype[T]: dynamic types refiningT, for a static typeT.- Values of this type represent dynamic types that refine
T. For exampledytype(Array[Integer,24])has static typeDytype[Array[Integer]]
- Values of this type represent dynamic types that refine
Type facts:
Static types are the types assigned to variables in a program statically. A static type contains no values anywhere in the type tree. That is, there is no array length expression in the type. These are the types that are used in declarations. Each variable is declared to have some static type.
Value types (aka dynamic types) are the types associated to values. They include all the static types plus possible array lengths. A value type refines a static type if when you delete the values from the value type you get the static type.
A type name is a syntactic element that names a (static or value) type. Examples of type names include Array[Integer] and Array[Integer,24].
The expression new(t) takes a Dytype and returns the initial value for an object of that type. The initial value of Integer and other primitive (non-compound) types is "undefined". The initial value of Array[Integer] is an array of length 0 of Integer. The initial value of Pointer[Real] is the undefined pointer to Real. The initial value of Array[Real, 10] is the array of length 10 in which each element is undefined. In general, the initial value of an array of length n is the sequence of length n in which every element is the initial value of the element type of the array. The initial value of a tuple type is the tuple in which each component is assigned the initial value for its type.
Example: the C code
int n = 10;
struct S { int a[n]; };
struct S x1;
n=20;
struct S x2;
may be translated as
type S=Tuple[<Array[Integer]>];
var n: Integer;
var S_d: Dytype[S];
var x1: S;
var x2: S;
ASSIGN "n", 10;
ASSIGN "S_d", dytype(Tuple[<Array[Integer, "n"]]>);
ASSIGN "x1", new("S_d");
ASSIGN "n", 20;
ASSIGN "x2", new("S_d");
Example of self-referential data definition, a linked list:
type Node=Tuple[<Integer,Pointer[Node]>];
Expressions
In the following list of expressions, e, e0, e1, etc., are expressions. T is a type name. t is an expression of type Dytype.
Logical
true,false: literal values of typeBoolnot(e): logical notand(e1,e2),or(e1,e2): logical and/or operation.- These operators are short-circuiting, which matters because of exception side-effects.
implies(e1,e2): logical implication. Short-circuiting.eq(e1,e2),neq(e1,e2): equality/inequality testforall(<i1:T1,i2:T2,...>,e): universal quantification. For all i1 in type T1, i2 in type T2, ..., e2 holds.exists(<i1:T1,i2:T2,...>,e): existential quantification. There is some i1 in type T1, i2 in type T2, ..., such that e holds.
Numeric
- 123, -123, 3.1415, etc. : values of type
Integer,Int,Real,Float. NEED TO BE MORE SPECIFIC add(e1,e2): numeric addition.e1ande2have the same numeric type. Note that there are no "automatic conversions" as there are in C. If the original expressions have different types, explicit conversions must be inserted.
sub(e1,e2): subtractionmul(e1,e2): multiplicationdiv(e1,e2): division- If both are integral types, the result is integer division. Otherwise it is real division. Need to define what happens for negative integers.
mod(e1,e2): integer modulus. Arguments may beIntegeror anyInttype. They must have the same type, which is also the type of the result.neg(e): negative. Argument may have any numeric type; result has same type.lt(e1,e2),lte(e1,e2): less than/less than or equal to. Arguments must have same numeric type.round(e,t): returns value in numerical typetthat is "closest" to the given valuee- add argument for rounding mode?
- exception if out of range?
floor(e): given a real or floating number, returns the greatestIntegerless than or equal to it.ceil(e): given a real or floating number, returns the leastIntegergreater than or equal to it.abs(e): given any numeric expression e, returns the absolute value; result is same type ase.pow(e,n): power operator- given any numeric expression e and expression
nof any integral type, returns e to the n-th power. nmust evaluate to a nonnegative integer.
- given any numeric expression e and expression
herbrand(e): "Herbrandize" a numeric value.- given a numeric value of type
T, returns the value as typeHerbrand[T], a trivial symbolic expression consisting of a single node.
- given a numeric value of type
eval(e): evaluate a Herbrand expression- given a value of type
Herbrand[T], returns the value of typeTobtained by evaluating all the delayed operations - exceptions may be thrown if evaluating any of the delayed operations results in an exception
- given a value of type
Characters and Strings
- 'a', 'b', ... : Char values. UNICODE?
string("abc"): string literals- value of type
Array[Char, n+1], wherenis the length of the string (the last element is the character\0)
- value of type
Ranges and Domains
range(e1,e2,e3): value of typeRange.- If
e3is positive, the integerse1,e1+e3,e1+2*e3, ... that are less than or equal toe2. Ife3is negative, the integerse1,e1+e3,e1+2*e3, ... that are greater than or equal toe2. Exception ife3is 0.
- If
domain(<r1,...,rn>): value of typeDomain[n], the Cartesian product of the n ranges, with dictionary orderhasnext(dom, <i,j,…>): an expression of typeBool, testing if the domaindomcontains any element after<i,j,...>. Ifiorjor ... is undefined, then it evaluates tofalseifdomis empty, andtrueifdomis not empty.
Arrays (which are also Sequences)
array(T,<e0,...,en-1>): value of typeArray[T, n], a literal arrayarray(T,n,e): value of typeArray[T,n]in which each of the n elements iseasub(e1,e2): array subscript expression.- Note that
e1must have array type, not pointer type. (This is different from C.) Ife1has pointer type, usederef(padd(e1, e2))instead.
- Note that
seq_add(a,e): array obtained by adding element e to the end of the array. Original array a is not modified.seq_append(a1,a2): array obtained by concatenating two arrays. Original array not modified.seq_remove(a,i): array obtained by removing element at position i from a. Original array a not modified.seq_insert(a,i,x): array obtained by inserting an element x at position i in a.seq_length(a): returns length of array a (anInteger)bit_and(e1, e2),bit_or(e1, e2),bit_xor(e1, e2),bit_comp(e1): bit-wise operations- arguments are arrays of booleans of the same length
- need conversions between bit vectors and integers
Tuples
tuple(S,<e0,e1,...>): value of tuple typeS(tuple literal)tsel(e1,i): tuple selection of component i of e1. i must be a literal natural number.
Unions
union_inj(U,i,x):xis inTi, result is in the union typeUunion_sel(U,i,y):yis inU, result is inTi(or exception ifyis not in image of injection fromTi)union_test(U,i,y):yis inU; determines ifyis in the image under injection ofTi. Returns aBool.
Pointers and Memory
NULL: value of typevoid*deref(e): pointer dereferenceaddr(e): address-of operatorpadd(e1,e2): pointer addition.e1has pointer type ande2has an integer type or range type.- If
e2has integer type the result has pointer type. Otherwise, the result hasMemtype. (BETTER TO USE A DIFFERENT FUNCTION?)
psub(e1,e2): pointer subtraction- Both args must have same type
Pointer[T]for someT. - Result has type
Integer
- Both args must have same type
mem_reach(ptr), whereptris an expression with a pointer type.- This represents the set of all memory units reachable from
ptr, including the memory unit pointed to byptritself.
- This represents the set of all memory units reachable from
mem_union(mem1,mem2), wheremem1andmem2are expressions of typeMem.- This is the union of the two memory sets.
mem_isect(mem1,mem1): set intersectionmem_comp(mem1): set complement (everything not inmem1)mem_slice(a,dom)- where
ais an expression of array type anddomis an expression ofDomaintype. - The dimension of the array must match the dimension of the domain. This represents all memory units which are the cells in the array indexed by a tuple in
dom.
- where
Scopes and Processes
root,here: values of typeScopeself,proc_null: values of typeProcscopeof(e): scope of left-hand-side expressionescope_eq(e1,e2): equality of two dyscopesscope_lt(e1,e2): is first scope a proper subscope of second?scope_lte(e1,e2): is first scope a subscope or equal to second?
Types
new(t): new (default) value ofDytypet. Ifthas typeDytype[T], then this expression has static typeT.typeof(e): returns the value of typeDytyperepresenting the value type ofe.- If
ehas static typeTthen this expression has static typeDytype[T].
- If
subtype(t1,t2): ist1a subtype oft2? Both args have typeDytype. Returns aBoolsizeof_type(t): the size of the dynamic typet; return anIntegercast(e,t): castseto a value of dytypet.- If
thas typeDytype[T], this expression has static typeTand the value will have dynamic typet. - A cast can only take place when there is no change to the underlying value in the concrete semantics. It is only for changing the type.
- One can cast from any integral type to another (as the values in all cases are integers), but there might be an exception if the value is not in the range of the targeted type.
- One can cast any
Floatvalue to aRealvalue, but not the other way. (To convert from aRealto aFloat, use, e.g.,round.) - One cannot cast from one
Floattype to anotherFloattype (unless the float parameters are exactly the same). To do this, one could instead useround. - If
t1is a subtype oft2, one can always cast fromt1tot2(though this is rarely necessary, since an expression of typet1can be used wherever one of typet2is expected). One can also cast fromt2tot1, but a runtime exception will be thrown if the value being cast does not already belong tot1. For example, one might cast fromDomaintoDomain[n], or fromArray[Real]toArray[Real, n]. - One cannot cast between a Herbrand type and any other type. Instead, use functions
evalandherbrand.
- If
Other expressions
"x": x is an identifier, naming either a type, variable, or functionsizeof_expr(e): the size of the value of expressione; return anIntegerdefined(e): isedefined?Booltypeite(e1,e2,e3): if-then-else (conditional) expression, likee1?e2:e3in C.e1must haveBooltype.e2ande3must have the same static types, which is the static type of the result.
call(e0,<e1,...,en>): function call- a function invocation where
e0must evaluate to either an abstract or pure system function
- a function invocation where
choose_int(e): nondeterministic choice of integer
The Primitive Statements
ASSERT e, "msg", ...;: assertion with messageASSUME e;ASSIGN e1,e2;CALL f, <e1,...,en>;andCALL e, f, <e1,...,en>;- call to a function which is not abstract and is not a pure system function. The first form has no left-hand-side. The second form assigns the result returned by the call to
e.
- call to a function which is not abstract and is not a pure system function. The first form has no left-hand-side. The second form assigns the result returned by the call to
SPAWN f, <e1,...,en>;andSPAWN e, f, <e1,...,en>;WAIT e;WAITALL e, n;: wait on all procs in a listehas typePointer[Proc]andnhas an integral type, the number of procs in the list
ALLOCATE e, h, t, e0;ehas static typePointer[T], for some static typeThhas typeHeapthas typeDytype[T], for the same static typeTe0has an integral type.- Allocates
e0objects of typeton heaph, returning pointer to first element ine. - WOULD IT BE BETTER TO SPECIFY THE SCOPE?
- To translate the C
mallocyou first need to figure out the type of the elements being malloced. If the argument to malloc isn, then you first need to insert an assertioneq(mod(n, sizeof_type(t)), 0), and thenALLOCATE e, h, t, div(n, sizeof_type(t)).
FREE p;EVAL e;: evaluates an expressione- useful only because
emight contain exceptions (e.g., array index out of bound, division by zero)
- useful only because
NOOP;- Is there a need to add annotations for "true" or "false" branch, etc.? If so, we can just make these parameters to the Noop.
RETURN;andRETURN e;ATOMIC_ENTER;ATOMIC_EXIT;PARSPAWN p, d, f;: spawn a sequence of procsphas typePointer[Proc],dhasDomaintype andfhasFunctiontype.
NEXT dom, <i,j,…>;: move to next element in domain- updates
i,j,... to be the value of the next tuple indomafter<i,j,...> - if
i, orj, ... is undefined, theni,j, ... are updated to be the value of the first tuple indom - The CIVL-C code
$for(int i,j,...: dom) S;is translated into the following:var i: Integer; var j: Integer; ... ASSIGN "i", new(Integer); ASSIGN "j", new(Integer); ... begin choose when(hasnext(dom, <i,j,...>)) NEXT(dom, <i,j,...>); goto L1; when(not(hasnext(dom, <i,j,...>))) ; goto L2; end choose L1: S; goto L2; L2: ...
- updates
Function Definitions
The general form of a function definition:
fun[options] foo(x:Integer, ...) : Real
// contract clauses (optional):
requires expr;
ensures expr ;
mpi_requires expr ;
mpi_ensures expr ;
assigns expr ;
reads expr ;
depends actions ;
{
// all of the following are missing for a system function...
// types defns:
type T=tuple[<blah, blay>];
type U=blah;
// variable decls:
var x : T;
var y : U;
// body:
ASSIGN x, 0;
...
}
Example of declaration of a system function defined in library Concurrency:
fun[lib="edu.udel.cis.vsl.civl.lib.Concurrency"] f(x: Real, b: Bool): Float[32,33] {}
Function modifiers that may be placed in the brackets after fun:
pure: the function has no side effects, but may be nondeterministicabstract: function is a pure, mathematical function: deterministic function of inputsatomic: function definition is atomic, and it never blocks- ISN'T THIS TRUE OF EVERY SYSTEM FUNCTION? IN WHICH CASE, IS THIS ONLY FOR NON-SYSTEM FUNCTIONS?
lib="...": function is a system function defined in specified libraryguard="...":the name of the guard functionmain: this is the program entry point (global scope only)
System functions:
- A function declaration which contains an option
lib=...is a system function - A system function will have no body. It may have any number (including 0) of contract clauses.
- If the system function is called anywhere in the program, it must be defined by providing Java code in an Enabler and Executor. Failure to do so will result in an exception.
- A system function may modify only memory it can reach. This includes allocating new data on heaps it can reach. This modifiable memory can be further limit by the contract.
- A system function may have a guard, which is another function taking the same types of inputs, but returning
Bool.
Example of a declaration of a system function with guard.
fun g(x:Real):Bool { ... }
fun[lib="Blah",guard="g"] f(x:Real):Integer {}
Function Contracts
THIS NEEDS TO BE UPDATED
- event set expressions:
EventSetExpression : read(MemorySetExpression) | write(MemorySetExpression) | access(MemorySetExpression) | calls(FunctionCallExpression) | \nothing | \everything | ‘(’ EventSetExpression ‘)’ | EventSetExpression + EventSetExpression | EventSetExpression - EventSetExpression | EventSetExpression & EventSetExpression
- depends clause:
depends [condition] { event1, event2, ...}- Example:
depends { access(n) - (calls(inc(MemorySetExpression)) + calls(dec(MemorySetExpression))) } - absence of depends clause:
- Example:
- assigns-or-reads clause
- assigns clause:
assigns [condition] {memory-list} - reads clause:
reads [condition] {memory-list} reads {nothing}impliesassigns {nothing}reads {\nothing}is equivalent to:reads {\nothing} assigns {\nothing}assigns {X}whereX != \nothing, impliesreads {X}assigns {X}is equivalent to:assigns{X} reads{X}- absence:
- absence of
readsclause: there is no assumption about the read access of the function, i.e., the function could read anything - absence of
assignsclause: similar to the absence ofreadsclause
- absence of
reads/assigns {\nothing}doesn’t necessarily means that the function never reads or assigns any variable. The function could still reads/assigns its “local” variables, including function parameters and any variable declared inside the function body.
- assigns clause:
- For an independent function which has
depends {\nothing}, usually we also need to specifyreads{nothing}, for the purpose of reachability analysis.
e.g.,
/* Returns the size of the given bundle b. */
bundle_size(Bundle b; Int)
depends {\nothing}
reads {\nothing}
;
- Example of a function declaration with contracts:
atomic_f sendRecv(Int cmd, Pointer buf; Int) depends [eq(cmd, SEND)] {write(buf)} depends [eq(cmd, RECV)] {access(deref(buf))} assigns [eq(cmd, SEND)] {\nothing} assigns [eq(cmd, RECV)] {deref(buf)} reads {deref(buf)} { L0: when (eq(cmd, SEND)) send(deref(buf), ...); goto L1; when (eq(cmd, RECV)) deref(buf):=recv(...); goto L1; when (and(neq(cmd, SEND), neq(cmd, RECV))) ; goto L1; L1: }
Program
A program consists of a sequence of type definitions, followed by a sequence of variable declarations, followed by a sequence of function definitions. This is called the "global scope."
The variable declarations in the global scope may use the options
inputoutput
There must be exactly one global function definition with option main. This function must consume no inputs and return an Integer (check this).
Semantics
Semantics issues
- define every possible cast
- define every possible +, etc.
- define every kind of pointer value and casts between pointer types
- casts between pointer and integer types?
Values
Transitions
Libraries
Which libraries require system functions? pointer, ... Should these be called part of the language, or not?
Questions
- fix notation for contracts
- quotes around variables in declarations? Could be awkward but might simplify parsing.
- quotes around function names in definitions, prototypes? Could be awkward.
- is there ever a need for a function prototype? Could variable decl notation be used instead? NO, variable decls are different, those are state variables.
- should we leave our parameter names in abstract and system functions? They are not needed for anything. THEY ARE NEEDED FOR CONTRACTS, sometimes, and maybe for documentation. Consider making them optional.
