A Modality for Safe Resource Sharing and Code


















- Slides: 18
A Modality for Safe Resource Sharing and Code Reentrancy Rui Shi (Yahoo! Inc) Dengping Zhu (Bloomberg Inc) Hongwei Xi (Boston Univ. )
The Research Context ATS is a functional programming language with a highly expressive type system rooted in the Applied Type System framework: http: //www. ats-lang. org In ATS, advanced types such as dependent types and linear types are supported. In ATS, a programming style is advocated that combines programming with theorem-proving
A Function in ATS fun{a: t@ype} swap {l 1, l 2: addr} ( pf 1: !a@l 1, pf 2: !a@l 2 | p 1: ptr l 1, p 2: ptr l 2 ) : void = let val tmp = !p 1 in !p 1 : = !p 2; !p 2 : = tmp end // end of [swap]
Resource Specification Resource protection is crucial in programming We need to properly specify resources We need to strike a balance when specifying resources If specification is too weak, verification may underachieve If specification is too strong, verification may become too demanding
Views for Classifying Capabilities (1) Given a type T and a location L, we use T@L for a (primitive) view meaning that a value of the type T is stored at the location L. Given types T 1 and T 2 and a location L, we use (T 1@L) (T 2@(L+1)) for a (compound) view meaning that a value of the type T 1 is stored at L and another value of the type T 2 is stored at L+1.
Views for Classifying Capabilities (2) We can also declare recursively defined views (similar to datatypes in a functional language like ML). For instance the following declaration introduces a view constructor array_v: datatype array_v (a: t@ype, int, addr) = {l: addr} array_v_nil (a, 0, l) of () | {l: addr} {n: nat} array_v_cons (a, n+1, l) of (a @ l, array_v (a, n, l+1))
Viewtypes Given a view V and a type T, we can form a viewtype VT = V T The following type can be assigned to a function (ptrget_L) which reads from a fixed location L: (T@L, ptr(L)) (T@L T) The following type can be assigned to a function (ptrset_L) which writes to a fixed location L: (T 1@L, ptr(L), T 2) (T 2@L 1) Note that we use 1 for the unit type.
Resource Threading There is a serious problem with linear types: resources (of linear types) must thread through function calls. For instance, we have seen this in the types assigned to the functions ptrget_L and ptrset_L. Suppose larray(T) is a type for linear arrays in which each element is of type T. Then the array subscripting function sub for linear arrays needs to be given the following type: larray(T) int larray(T) T So a linear array is threaded through each call to sub.
Sharable Arrays We refer to arrays in functional languages like ML as (non-linear) sharable arrays. The question we want to answer is how a sharable array can be implemented based on a linear array. More generally, how sharable data structures can be implemented based on linear ones.
A Sharing Modality Given a viewtype VT, we use □VT for a (non-linear) type such that a value of the type □VT represents the handle of a box containing a value of the viewtype VT. The introduction for □ is straightforward: vbox: (VT) □VT However, the elimination for □ is tricky. Essentially, we need something like this: let □ x = □ v in … end
The “double-borrow” problem Assume that a resource stored in a box has been borrowed. If it is to be borrowed again before it is returned to the box, we have a “double-borrow” problem: let □ x 1 = □ v 1 in …… let □ x 2 = □ v 2 in … … end …… end
A Type-with-Effect System A pure function is one that does not borrow resources; it is given a type of the following form: (VT 1, …, VT 2) 0 VT A non-pure function is one that may borrow resources; it is given a type of the following form: (VT 1, …, VT 2) 1 VT So, we can distinguish a pure function from a nonpure function at the level of types.
Implementing references (1) We use ref(T) for (non-linear) references to values of the type T. In ATS, ref(T) is defined as typedef ref(T) = [l: addr] (vbox(T@l) | ptr l)
Implementing References (2) fun refget (r: ref(T)): <!ref> = let val (pfbox | p) = r; val vbox (pf) = pfbox in ptrget (pf | p) end // end of [refget]
Implement References (3) fun refset (r: ref(T), x: T): <!ref> void = let val (pfbox | p) = r; val vbox (pf) = pfbox in ptrset (pf | p, x) end // end of [refset]
Reentrancy (1) A function f is reentrant if it can be called in a nested manner. That is, another call to f can be made before a call to f is finished. A non-reentrant function is one that is not reentrant. For instance, functions like ctime, drand 48, and strtok are non-reentrant. Their reentrant counterparts are ctime_r, drand 48_r, and strtok_r, respectively.
Reentrancy (2) Pure functions are reentrant Non-pure functions are not reentrant However, if a function is guaranteed to do borrowing/returning atomically, then this function is still reentrant. We assign such a function a type of the following form: (VT 1, …, VT 2) 0. 5 VT
Conclusion We have presented a type-theoretic justification for implementing (non-linear) sharable resources based on linear resources We have fully implemented the sharing modality in ATS and also used it extensively. The solution we adopted to the “double-borrow” problem is simple but effective in practice. A more elaborate solution is certainly possible but it may not fit well in ATS.