| | 1 | = PIL: Parallel Intermediate Language |
| | 2 | |
| | 3 | == Basic Properties |
| | 4 | * PIL programs are target of translators from C, Fortran, MPI/OpenMP/etc. They can also be written by humans. |
| | 5 | * PIL should be a high-level language like CIVL-C. |
| | 6 | * PIL programs can be easily translated to PFG. |
| | 7 | * PIL must also have a totally well-defined semantics and syntax. |
| | 8 | * PIL programs can have nested funtion definitions. |
| | 9 | * PIL programs can use preprocessor directives like in C |
| | 10 | * There are no automatic conversions. All must be done by explicit casts. |
| | 11 | * `$input`/`$output` variables in global scope (like CIVL-C) |
| | 12 | * Identifiers are like in C, e.g., `x`, `f10`, ... |
| | 13 | * Keywords, functions, etc. not in C start with `$` (like CIVL-C) |
| | 14 | * Libraries: similar to C, `#include <stdlib.h>`, but these name PIL libraries and there are additional PIL libraries |
| | 15 | * PIL programs can be divided into multiple files (translation units) |
| | 16 | * One TU can refer to a variable, function, type, in another TU. |
| | 17 | * the variable just needs to be declared somewhere in the TU |
| | 18 | * the function just needs a prototype somewhere in the TU |
| | 19 | * the use of "static" in a variable decl or function def makes it private to that TU (so two can have same name) |
| | 20 | * no need for "extern" |
| | 21 | * at most one of the TUs declaring the variable can have an initializer |
| | 22 | * at most one of the TUs can have a definition for a function |
| | 23 | * Program order: |
| | 24 | * a program is a sequence of variable, function, and type definitions. |
| | 25 | * the order is totally irrelevant. |
| | 26 | * a variable can be used anywhere it is in scope |
| | 27 | * a function can be called anywhere it is in scope |
| | 28 | |
| | 29 | == Types |
| | 30 | - type names look like `int[]`, `int*[](double)`, etc. |
| | 31 | - basic types: same as C: `_Bool`, `char`, `short`, `int`, `long`, `long long`, `unsigned`, `float`, `double`, ... |
| | 32 | - what are the limits of these types? to be decided |
| | 33 | - `$int`, `$real` (mathematical types) |
| | 34 | - `struct`: as in C |
| | 35 | - `union`: as in C |
| | 36 | - `T[]`: sequence of `T`. Note: no "T[n]". sequences can be assigned, returned, passed as arguments, etc. |
| | 37 | - `$set<T>` : finite set of `T`, ops to add, contains, remove, size, ... |
| | 38 | - `$map<T1,T2>` : finite map from `T1` to `T2`, ops to put, get, containsKey, ... |
| | 39 | - `$fun<T1,T2>` : total functions from `T1` to `T2` |
| | 40 | - `$tuple<T,...>` : tuples of specified type (similar to struct) |
| | 41 | - `$rel<T,...>` : relations of specified type (set of tuples) |
| | 42 | - `T*`: pointer to `T` |
| | 43 | - what can be pointed to: var, array element, struct element, union member, ... |
| | 44 | - Type definitions: `typedef typename ID;` |
| | 45 | |
| | 46 | == Tuples |
| | 47 | |
| | 48 | Non-mutating expressions: |
| | 49 | - `t1 == t2` |
| | 50 | - `t.i` |
| | 51 | - `($tuple<T1,...>){ x1, ... }` |
| | 52 | - `$pure $tuple<T1,...> $tuple_write($tuple<T1,...> t, $int i, Ti x);` |
| | 53 | |
| | 54 | Mutating expressions: |
| | 55 | - `t.i = x;` |
| | 56 | |
| | 57 | == Sets |
| | 58 | |
| | 59 | Non-mutating expressions: |
| | 60 | - `s1 == s2` |
| | 61 | - `($set<T>)$empty` // empty set of type T |
| | 62 | - `$pure _Bool $set_in(T x, $set<T> s);` // is x an element of s? |
| | 63 | |
| | 64 | $pure $set<T> $set_with($set<T> s, T x); // s U {x} |
| | 65 | $pure $set<T> $set_without($set<T> s, T x); // s - {x} |
| | 66 | $pure $set<T> $set_union($set<T> s1, $set<T> s2); // s1 U s2 |
| | 67 | $pure $set<T> $set_difference($set<T> s1, $set<T> s2); // s1-s2 |
| | 68 | $pure $set_intersection($set<T> s1, $set<T> s2); // s1 \cap s2 |
| | 69 | $pure T[] $set_elements($set<T> s); |
| | 70 | $pure _Bool $set_isSubsetOf($set<T> s1, $set<T> s2); |
| | 71 | $pure $set<U> $set_map($set<T> s, $fun<T,U> f); |
| | 72 | |
| | 73 | |
| | 74 | Mutation functions: |
| | 75 | _Bool $set_add($set<T> * this, T x); |
| | 76 | _Bool $set_remove($set<T> * this, T x); |
| | 77 | void $set_addAll($set<T> * this, $set<T> that); |
| | 78 | void $set_removeAll($set<T> * this, $set<T> that); |
| | 79 | void $set_keepOnly($set<T> * this, $set<T> that); |
| | 80 | |
| | 81 | Sequences (arrays) |
| | 82 | ========= |
| | 83 | Expressions: |
| | 84 | a1 == a2 |
| | 85 | a[i] |
| | 86 | (T[]){ x1, ... } |
| | 87 | |
| | 88 | Built-in logic functions: |
| | 89 | $pure T[] $seq_fun($int len, $fun<$int,T> f); |
| | 90 | $pure T[] $seq_uniform($int n, T val); |
| | 91 | $pure $int $length(T[] a); // length of a |
| | 92 | $pure T[] $seq_write(T[] a, int i, T x); // a[i:=x] |
| | 93 | $pure T[] $seq_subseq(T[] a, int i, int n); // a[i..i+n-1] |
| | 94 | $pure T[] $seq_without(T[] a, int i); // a with position i removed |
| | 95 | $pure T[] $seq_with(T[] a, int i, T x); |
| | 96 | $pure T[] $seq_concat(T[] a1, T[] a2); |
| | 97 | $pure U[] $seq_map(T[] a, $fun<T,U> f); |
| | 98 | $pure T[] $seq_filter(T[] a, $fun<T,_Bool> f); |
| | 99 | $pure U $seq_foldl(T[] a, $fun<$tuple<T,U>,U> f, U init); |
| | 100 | $pure U $seq_foldr(T[] a, $fun<$tuple<T,U>,U> f, U init); |
| | 101 | |
| | 102 | Mutating operations: |
| | 103 | a[i]=x; |
| | 104 | |
| | 105 | Mutating Procedures: |
| | 106 | T $seq_remove(T[] * this, int i); |
| | 107 | void $seq_insert(T[] * this, int i, T x); |
| | 108 | void $seq_append(T[] * this, T[] that); |
| | 109 | |
| | 110 | Maps |
| | 111 | ==== |
| | 112 | Non-mutation: |
| | 113 | m1 == m2 |
| | 114 | ($map<K,V>)$empty |
| | 115 | $pure V $map_get($map<K,V> K key); |
| | 116 | $pure _Bool $map_containsKey($map<K,V> map, K key); |
| | 117 | $pure _Bool $map_containsValue($map<K,V> map, V val); |
| | 118 | $pure $map<K,V> $map_with($map<K,V> map, K key, V val); |
| | 119 | $pure $map<K,V> $map_without($map<K,V> map, K key); |
| | 120 | $pure $set<K> $map_keys($map<K,V> map); |
| | 121 | $pure $set<$tuple<K,V>> $map_entries($map<K,V> map); |
| | 122 | |
| | 123 | Mutation: |
| | 124 | V $map_put($map<K,V> * this, K key, V val); |
| | 125 | V $map_remove($map<K,V> * this, K key); |
| | 126 | void $map_removeAll($map<K,V> * this, $set<K> keys); |
| | 127 | void $map_addAll($map<K,V> * this, $map<K,V> that); |
| | 128 | |
| | 129 | Functions |
| | 130 | ========= |
| | 131 | There are two kinds of functions in PIL: |
| | 132 | |
| | 133 | 1. (Imperative) procedures, like in C. These can be nested. |
| | 134 | Obviously they can have side-effects, be nondeterministic, and |
| | 135 | the behavior can depend on non-local state. A procedure call |
| | 136 | f(x1,...,xn) is an expression that can be used anywhere an |
| | 137 | expression with side-effects is allowed. Imperative procedures |
| | 138 | are not values and cannot be assigned to anything. However, as |
| | 139 | in C, a pointer to a procedure is a first-class value that can be |
| | 140 | assigned, used as an argument, returned, etc. There is a |
| | 141 | procedure type, exactly like C's function type, e.g., |
| | 142 | |
| | 143 | typedef int(int) i2i; |
| | 144 | |
| | 145 | defines i2i to be the type "procedure consuming int and returning |
| | 146 | int". Most commonly, pointers to procedure types will be used, |
| | 147 | e.g., |
| | 148 | |
| | 149 | typedef int(int)* i2ip; |
| | 150 | |
| | 151 | defines the type "pointer to procedure from int to int". As in |
| | 152 | C, a pointer to a procedure can be used in a procedure call. |
| | 153 | |
| | 154 | A procedure definition can be templated: |
| | 155 | |
| | 156 | <T1,T2,...> <procedure def> |
| | 157 | |
| | 158 | This defines one procedure for each assignment of types to |
| | 159 | the Ti. |
| | 160 | |
| | 161 | 2. Logic functions. Typically specified by a $lambda expression. |
| | 162 | These are first-class objects in PIL: they can be assigned to |
| | 163 | variables, passed as an argument in a function call (including a |
| | 164 | procedure call), and can be returned by a function. Each has a |
| | 165 | type of the form $fun<T1,...,Tn;U> (functions from T1 x...x Tn to |
| | 166 | U). They are side-effect-free. An application of a logic |
| | 167 | function f(x1,...,xn) is an expression that can be used anywhere |
| | 168 | an expression is allowed. A logic function is not necessarily |
| | 169 | pure, i.e., the value of an application may depend on any part of |
| | 170 | the state, not just the arguments. |
| | 171 | |
| | 172 | Lambda expressions: have the form |
| | 173 | |
| | 174 | $lambda (T1 x1, ..., Tn xn) {U1 v1=init1; ... Um vm=initm; expr} |
| | 175 | |
| | 176 | where the Ti and Uj are types, the xi and vj are variables, |
| | 177 | and expr is an expression in which the only variables that can |
| | 178 | occur free are the x1,..., xn, v1,..., vm. The initializer |
| | 179 | expressions initj are expressions using any variables in scope. |
| | 180 | The expression has type $fun<T1,...,Tn; V>, where V is the type |
| | 181 | of expr. |
| | 182 | |
| | 183 | Semantics: the lambda expression is evaluated to yield a *closure*: |
| | 184 | an ordered pair consisting of a dyscope and an expression. The |
| | 185 | dyscope has variables v1, ..., vm, initialized by evaluating init1, |
| | 186 | ..., initm. The dyscope has no parent scope, it is associated only |
| | 187 | with the closure. The expression is expr. The dyscope is destroyed |
| | 188 | whenever it becomes unreachable, i.e., whenever no part of the state |
| | 189 | is assigned the closure. |
| | 190 | |
| | 191 | A logic function application f(e1,...,en) is evaluated by first |
| | 192 | evaluating the arguments ei, then assigning the ei to the xi and |
| | 193 | evaluating expr in the closure dyscope. Note that since expr |
| | 194 | has no side effects, the values of the xi are constant. |
| | 195 | [Note: a possible change is to allow side-effects in logic |
| | 196 | functions.] |
| | 197 | |
| | 198 | Heaps |
| | 199 | ===== |
| | 200 | |
| | 201 | There is a single heap for dynamic allocation. The following |
| | 202 | built-in procedures are provided: |
| | 203 | |
| | 204 | T* $new<T>(): creates an object A of type T in heap and returns &A. |
| | 205 | |
| | 206 | T* $alloc<T>(int n): creates an array A of length n of T in heap and |
| | 207 | returns &A[0]. |
| | 208 | |
| | 209 | [Question: how to model C's malloc? Must be able to execute it |
| | 210 | without knowing T.] |
| | 211 | |
| | 212 | void $free<T>(T* p): frees the object referred to by p, the |
| | 213 | pointer returned by an earlier call to $new or $alloc. |
| | 214 | |
| | 215 | |
| | 216 | Questions |
| | 217 | how to deal with "undefined" values? |
| | 218 | |
| | 219 | can there be nondeterministic expressions, i.e., expressions that |
| | 220 | evaluate to a set of values instead of one value? |
| | 221 | |
| | 222 | how to implement C's malloc? |
| | 223 | |
| | 224 | Union of all types occurring in program: |
| | 225 | typedef union { |
| | 226 | T1 t1; |
| | 227 | T2 t2; |
| | 228 | ... |
| | 229 | } BigUnion; |
| | 230 | |
| | 231 | |
| | 232 | A call to malloc returns a memory block: |
| | 233 | - size (in bytes) (int) |
| | 234 | - num_elements (int) |
| | 235 | - sequence of tuples: |
| | 236 | - offset (int) |
| | 237 | - size (int) |
| | 238 | - value (type BigUnion) |