Florence Nightingale (1820-1910)
(1847) Viaje a Alemania
As discussed in Chapters 1 and 2, our goal is to enforce, at compile time, four safety guarantees for the extended language with nondeterminism: (1) freedom from data races; (2) strong isolation for statements markedatomic; (3) sequential equivalence for deterministic parallel constructs; and (4) determinism by default. We now discuss several extensions to the deterministic effect system described in Chapters 3 and 4 that allow these four properties to be enforced.
Data race freedom and strong isolation: We use the following strategy to ensure both data race freedom
and strong isolation:
1. A transactional runtime guarantees at least weak isolation ofatomicstatements.
2. The effect system ensures that for any pair of conflicting memory accesses, both of the accesses occur insideatomicstatements. This requirement ensures strong isolation, because no conflicts between unguarded memory accesses and atomic statements are allowed. It also ensures race freedom, because no conflicts between pairs of unguarded accesses are allowed.
Part 1 of our strategy is familiar from previous work on language mechanisms supported by transactional runtimes [62, 8, 65, 44]. Part 2 is new, and it leads to two significant advantages. First, our strategy guaran-
tees strong isolation even if the underlying implementation guarantees only weak isolation. This property is very important, because it allows our language to be built on top of a standard software transactional memory (STM) implementation. Typically, STM guarantees weak isolation, because the overhead of guar- anteeing strong isolation in software is prohibitive. Second, our strategy prohibits all data races. Even TM systems with strong isolation generally allow data races between pairs of accesses, both of which occur outside any transaction.
Effect system extensions. To ensure that pairs of conflicting accesses both occur insideatomicstatements (part 2 above), we extend the DPJ effect system described in Chapter 3 as follows:
1. Internally, the compiler distinguishes read and write effects as either atomic (meaning the effect oc- curred inside anatomicstatement) or non-atomic (meaning the effect occurred outside anyatomic statement).
2. To support sound reasoning about atomic effects across method invocations, we extend the syntax of DPJ’s method effect summaries to denote whether effects are atomic. An atomic effect is denoted by writing the keywordatomicbefore the RPL. For example, an effect writesR1, atomic R2
denotes a non-atomic write toR1and an atomic write toR2.
3. We extend DPJ’s rules for subeffects so a non-atomic effect covers an atomic effect, but not vice versa.
4. In checking aforeach nd orcobegin ndstatement, interference is allowed between the com- ponent parallel tasks only between pairs of effects that are either mutually noninterfering or both atomic.
Together, these rules ensure that any pair of conflicting accesses both occur inatomicstatements. The first three rules ensure that atomic effects are reported only where there is, in fact, an enclosing atomic statement. The fourth rule disallows interference without atomic effects, i.e., without enclosing atomic statements.
TSP example. The TSP example discussed in the previous section illustrates the effect system extensions. As shown in line 2 of Figure 5.1, two regions are used to hold the data: ReadOnlyfor fields that will not
be modified during the computation, andMutablefor those that will be. The type
PriorityQueue<Path<ReadOnly>, Mutable>
of the priority queue indicates that the queue contains objects of type Path<ReadOnly>, and that the internal data used to represent the queue itself is in regionMutable. Here are examples of how the four rules stated above are used to check the correctness of the TSP example:
1. In Figure 5.3, the read effect on region ReadOnly is not atomic, because it is generated in the test condition of theforloop, outside theatomicstatement. However, the write effect on region Mutableis atomic, because it is generated by the assignment to the variablebestTourat line 6, inside theatomicstatement.
2. The effect summary in line 2 of Figure 5.3 says that the write effect onMutableis atomic (but the read effect onReadOnlyis not).
3. It would be permissible (but conservative) to rewrite the effect summary in line 2 of Figure 5.3 as
reads ReadOnly writes Mutable,
becausewrites Mutablecoverswrites atomic Mutable, which is the actual effect gen- erated in line 6. However, it would be a compile-time error to writereads atomic ReadOnly, because the effect onReadOnlyis not atomic.
4. In the foreach nd at lines 15–23 of Figure 5.1, the effects on ReadOnly are all reads, and MutableandReadOnlyare distinct regions. Therefore, the only interfering effects across itera- tions of the loop are the atomic writes toMutablegenerated by the calls togenerateNextPrefix andsearchAllToursWithPrefix. The first call occurs inside anatomicsection, so it gener- ates atomic effects. The second call occurs outside an atomic section, but as noted above, the write effect in the signature ofsearchAllToursWithPrefixis marked atomic (line 2 of Figure 5.3. On the other hand, if theatomicstatement at line 18 of Figure 5.1 were removed, or the write effect in line 2 of Figure 5.3 were made non-atomic, then a compile-time error would occur in checking the foreach ndloop.
Programmer benefit. Together, race-freedom and strong isolation convey the following benefits to the pro- grammer in this language:
1. Sequential consistency. Because there are no data races, the Java memory model guarantees a sequen- tially consistent execution. That means the observed execution is consistent with program order (i.e., the order of execution defined by the program text).
2. Reduced interleavings. Because code occurring outside anatomicstatement is either noninterfering or not parallel by definition in this language, a program’s execution is determined by only two things: (1) the actual order of execution of concurrent, interferingatomicstatements, and (2) program order. Further, program order does not introduce any schedule dependence over and above (1). Therefore, the only source of non-equivalent interleavings is from different orderings of concurrent, interfering atomicstatements.
The first property is important because it is well known that sequentially consistent executions are much easier to reason about than non-sequentially consistent ones. The second property is important because many programmers (and testing tools) analyze program behavior by reasoning about the possible interleavings (or schedules) of parallel operations. Reducing the effective number of interleavings makes such reasoning easier.
Sequential equivalence for deterministic constructs: As discussed more formally in the next chapter,
a basic property of DPJ as described in Chapter 3 is thatcobeginand foreachbehave exactly like a program-ordered sequential composition of their component tasks. We wish to preserve this property for the extended language that includes cobegin ndandforeach nd. We view this property as essential for allowing local, compositional reasoning about the interactions between deterministic and nondeterministic operations.
To guarantee this property, we incorporate the following two rules in the type system:
1. Interference is allowed only between parallel branches of a foreach nd or cobegin nd. No interference is allowed between parallel branches of acobeginorforeach, even if the interfering accesses are both guarded byatomicsections.
2. Inside aforeach ndorcobegin nd, interference is allowed between acobeginstatement and other parallel code only if the entirecobeginstatement is enclosed in anatomicstatement. This
ensures that everycobeginexecutes as if it were an isolated, sequential statement, even inside a foreach ndorcobegin nd.
The motivation for the first rule is fairly obvious. For example, we wish to disallow code like this: x = 0
cobegin {
atomic x = 1; // S1 atomic x = 2; // S2
}
Even though the writes to variable xare enclosed in atomic statements (so there is no data race), the order of execution of the statements S1and S2is nondeterministic, as is the final result. The point of cobegin is to indicate deterministic composition, so we just disallow such interference. In the typing rules, the rule for checkingcobegin/foreachis then exactly the same as in Chapter 3, while the rule for cobegin nd/foreach nd is as stated above (interference is allowed, but only where guarded by atomicstatements). The next chapter gives these rules formally.
The motivation for the second rule is perhaps more surprising. To see the motivation, consider the following program: z = 0; cobegin_nd { cobegin { atomic x = z; // S1 atomic y = z; // S2 } atomic z = 1; // S3 }
In our view, this program has a serious problem: it destroys the property we want to carry over from the deterministic language, i.e., thatcobeginbehaves like a program-ordered sequential composition of its component tasks. According to the semantics of cobegin nd and cobegin, the statements could be executed in the order S2, S3, S1, producing the result x = 1, y = 0. This result is impossible for a sequentially consistent execution of the program obtained by erasing thecobegin: for that program,S1 must occur beforeS2; and so ifxequals 1, thenS3must have executed beforeS1andS2, andymust equal 1 as well. In effect, the presence of the interfering write toz in the othercobegin ndbranch exposes the fact that the order of execution ofS1andS2was different forcobeginthan it could be for ordinary sequential composition.
Because we want programmers to be able to reason about cobeginas program-ordered sequential composition, we disallow this program. Specifically, we require that the entirecobegin statement ex- ecute as an isolated operation (as if it were surrounded entirely by atomic) whenever it occurs inside cobegin nd. We express this requirement in the effect system by simply “ignoring” anyatomicstate- ments occurring inside thecobegin, for purposes of computing the effect of the entire cobegin. For example, in the fragment above, thecobeginbranches generate atomic effects onx,y, andzat the point of the assignments. However, when those component effects are accumulated into the effect of the entire cobegin, they are transformed into non-atomic effects, so the effect of the entire cobegin is a non-atomic read of zand a pair of non-atomic writes tox andy. In particular, because the read of zis non-atomic in the first branch of thecobegin nd, the conflicting write tozin the second branch is disallowed (even though it is atomic). Again, these rules are stated more formally in the next chapter.
Notice the following (slightly different) programs that a programmer may write in our language. First, if the entire cobeginis enclosed in anatomic statement, then the effects of the cobeginare made atomic again, and the composition is allowed:
z = 0;
cobegin_nd {
atomic cobegin { x = z; y = z; } atomic z = 1;
}
This code is permissible, because theatomicstatement in the first branch of thecobegin ndguarantees that thecobeginexecutes in isolation, despite the interference with the second branch.
Second, a programmer who truly wants both interference and interleaving can write a cobegin nd instead of acobegin:
z = 0;
cobegin_nd {
cobegin_nd { atomic x = z; atomic y = z; } atomic z = 1;
}
Because the inner parallelism is created bycobegin nd, and notcobegin, the effects onx,y, andzare reported as atomic effects to the outercobegin nd, and so the interference is allowed.
Determinism by default: The typing rules discussed above also guarantee the following property: evalu-
a fixed output heap state for a fixed input heap state, up to inessential details like the addresses of objects on the heap. From the discussion above, an isolated statement is (1) anyatomicstatement,cobegin, or foreach; or (2) any statement not occurring inforeach ndorcobegin nd(including the entire pro- gram); or (3) any statement that does not dynamically execute anatomicstatement (because if a statement executes no atomicstatement, then the type system ensures that it runs with no interference from any other parallel statement). We call this property determinism by default, because it says that nondeterminism occurs only where explicitly requested viaforeach ndorcobegin nd. We view this property as essen- tial to any language that allows the composition of deterministic and nondeterministic parallel constructs. This property is stated and proved formally in the next chapter.