• Keine Ergebnisse gefunden

Contextualization Statement

Im Dokument A Reference Description of the C (Seite 36-42)

6 Conditionalization and Contextualization Statements

6.2 Contextualization Statement

where(where-expression) then-body

where(where-expression) then-body else else-body

The where statement is involved with setting the context, a process known as contextualization. The context is a parallel boolean mask (i.e., each element of it is true or false) that controls the execution of parallel operations position by position. A different context is associated with each shape object, and the context associated with the current shape is always applied to operators. If an element of the current context is true, parallel operations on elements in the corresponding position take place - these are active positions; if an element of the context is false, parallel operations on elements in the corresponding position do not take place - these are inactive positions. Scalar operations are not affected by context.

The contextualization statement must be invoked with a parallel where-expression, which must be of the current shape - it is a compile-time error to attempt to use where (a new reserved word) with a scalar expression. The where statement causes contextualization of the positions of that shape for the duration of the then-body and else-body. If APprior is the set of active positions in the current shape immediately prior to execution of the where statement, then parallel code in the then-body is executed only in those positions in the intersection of APprior and those positions in 'which the where-expression is true (or non-zero). That is, if Cprior is the context for the current

shape immediately prior to execution of the where statement, then for the duration of the then-body, the context is narrowed to be the logical-AND of Cprior and (where-expression != 0). If there is an else-body, then parallel code in it is executed only in those positions in the intersection of APprior and those positions in which the where-expression is false (or zero). That is, if there is an else-body, then for the duration of the else-body, the context is narrowed to be the logical-AND of Cprior and (where-expression -= 0). All scalar code in both the then-body and the else-body is always evaluated. After the where statement has completed, the set of active positions is restored to its state prior to executing the where statement. For example, with the following declarations,

shape [10]Sb;

int:Sb bil, bi2, bi3, bi4;

int sil;

and if the data has the following values,

Shape Sb: Position 0 Position 1 Position 2

bil: 2 4 3 ...

bi2: 0 0 0 ...

bi3: 1 5 32 ...

bi4: 0 0 0

Scalars:

sil: 12

and then the following program is executed with shape Sb current and all positions initially active, where(bil >= 3) {

bi2 = bi3;

sil++;

bi4 = bi3;

the data will have the following values after execution,

Shape Sb: Position 0 Position 1 Position 2 ...

bil: 2 4 3 ...

bi2: 0 5 32 ...

bi3: 1 5 32 ...

bi4: 1 5 32 ...

Scalars:

sil: 13

Note that the assignment of bi3 to bi2 has occurred only in the active positions, that the scalar variable sil is incremented once, but that the assignment of bi3 to bi4 has occurred in all positions because the context after the where statement body has completed reverts back to its state prior to the where statement.

The parallel where statement contextualized the positions of the shape, making position 0 inactive. This then affects the body of the contextualized statement in a dynamically bound way.

That is, the contextualization remains in effect for the duration of the statement and for all procedures that it may call. (Note: there is a mechanism, called everywhere, to turn on all positions of a shape in a nested context. [See §6.4]) It is mandatory that the parallel expression in the where statement follow all of the usual rules for an expression. For example, the following where statement is not acceptable because it tries to use a parallel variable in other than the current shape:

where((bil >= 3) && (cil <= 1)) S;

31 [Note: The previous example would be illegal even if a selection statement were not in C* because it attempts to combine values in two different shapes with &&.]

The then-body and else-body that follow the where-expression may contain statements with parallel expressions only of the current shape. It is possible for them to have expressions in a different shape by nesting a shape selection statement within the thenbody or elsebody -expressions of another shape are not affected by the contextualization of the shape of the where-expression. Scalar code within the statements is also not conditionalized. Even if no positions of the shape are made active by the contextualization, the then-body or else-body is still executed.

For example, with the following declarations,

shape [10]Sb, [50] [30]Sc;

int:Sb bil, bi2, bi3;

int:Sc cil, ci2;

int sil;

and the following data:

Shape Sb: Position 0 Position 1 Position 2 ...

bil: 2 4 3 0...

bi2: 0 0 0 0...

bi3: 1 5 32 54...

Shape Sc: Position 0,0 Position 0,1 Position 0,2 ...

cil: 0 0 0

ci2: 34 42 7

Scalars:

sil: O

then the following program is executed with shape Sb current and all positions (of both Sb and Sc) initially active:

where(bi > 4) bi2 = bi3;

sil = 4;

with(Sc)

cil. = ci2;

The data will be changed as follows after execution, even though no positions of shape Sb were left active by the where-expression (bil > 4):

32 TR-253 Shape Sb: Position 0 Position 1 Position 2

bil: 2 4 3 0...

bi2: 0 0 0 0...

bi3: 1 5 32 54...

Shape Sc: Position 0,0 Position 0,1 Position 0,2

cil: 34 42 7

ci2: 34 42 7

Scalars:

sil: 4

Note that the assignment of 4 to sil has occurred even though no positions of Sb were active. An optimizer for C* programs might check to see if the then-body or else-body contained any scalar code. If it determined that there was no scalar code (including no calls to functions that might contain parallel code), it might not execute the particular body at all if no positions were active.

This is an example of "as if' behavior - that is, an optimizer is free to change the actual behavior of code it produces so long as the effect of that code is as if it did exactly what the specification requires it to do. This is the basic license given to all code optimizers.

To keep scalar code and code in other shapes from executing if no positions of the current shape are left active, use a reduction operator to evaluate the condition to a scalar, and then use an if statement to conditionalize code, as follows:

if(l= (parallel_condition != 0))

where (paral 1 el_condition) then_statement;

if (= !parallel_condition)

where( !parallel_condition) else_statement;

except if the parallel_condition has side effects. The parallel_condition should be evaluated only once to avoid multiple side effects. For example:

int:current parallel_temp;

if(l= ((parallel_temp = parallel_condition) != 0)) where (parallel_temp)

then_ s tatement;

if(l= !parallel_temp)

where ( !parallel_temp) else_statement;

}

I

33 If the parallelcondition is known to be 0- or 1-valued, the not-equal comparison to 0 is not needed

(the following example illustrates such a case). Alternatively, if the not-equal comparison to 0 is needed, the parallel_temp could be declared of bool type [see § 14]. Our specific example from

above can be reformed to execute its then-body only if some positions are active, as follows:

int:Sb ptemp;

if(I= ptemp = (bil > 4))

where(ptemp) {

bi2 = bi3;

sil = 4;

with (Sc)

cil = ci2;

It is often desirable to allow iteration over parallel variables so that the iteration count varies from position to position. This is referred to as per-position iteration. The following idiom is useful in these cases:

while(I= (parallel_condition != 0)) where (paral 1 el_condi ti on)

statement;

It is expected that the execution of the statement will eventually decrease the positions in which the parallel_condition is true. Thus, the statement is repeatedly executed with a gradually diminishing

set of active positions. When no more positions remain active, the while loop will terminate. As above, the parallel_condition should be evaluated only once to avoid multiple side effects, as follows:

int:current parallel_temp;

while( = ((parallel_temp = parallel_condition) != 0)) where (parallel_ temp)

statement;

Also as above, if the parallel_condition is known to be 0- or -valued, the not-equal comparison to 0 is not needed. Note that this technique may be used with the other iteration statements in C -do-while and for) as well. Here is an example of this technique in a program fragment that computes 2count in each position:

shape [10]Sb;

int:Sb count, prod;

with(Sb) {

/* Initialize each element of "count" here */

prod = 1;

while(j= (count>O)) where (count>O) count--;

prod *= 2;

It is possible to cause more than one shape to be contextualized by nesting where statements, as follows:

where(bil >= 3) with (Sc)

where(cil <= 1)

S;

Any code in S (or called from S) that is of shape Sb is contextualized by the (bil >= 3) expression, while any code in S that is of shape Sc is contextualized by the (cil <= 1) expression.

The then-body always appears to be executed before the else-body. The compiler is free to perform dependency analysis to prove that there are no possible dependencies between the two bodies and run them in any order or concurrently.

The where statement provides an efficient mechanism to guarantee that the compiler may generate code that simply contextualizes a shape without needing to perform a global reduction (global-logior) to determine if there are any active positions left. Of course, the compiler is free to actually generate a global-logior and branch around the code if the compiler determines that the body contains only parallel code in the current shape and no function calls. It would do this if there were a sufficiently large, number of lines of such code.

6.2.1 Execution with No Active Positions

The where statement allows execution of statements when there are no active positions of the current shape. This implies that the following statement may be executed:

where(bil > 4)

sil = (+= bil);

TR-253

TR-253

This statement assigns to scalar variable sil the sum of the elements in active positions of bil.

0 What happens if the where statement leaves no active positions of shape Sb? The assignment to sil should occur in any case because it is a scalar. C* deals with this situation by defining a set of values that are returned by the unary reduction operators when there are no active positions. These values are the identities for the operator (when one exists). Here is the table of reduction values when there are no active positions:

Unary Reduction Operator Value

When one of the reduction operators is used in a binary context, the LHS is left unchanged if there are no active positions of the expression's shape. (This is the natural consequence even if one considers that the operators return the values specified in the table above, which then operate with the LHS - except for the cast operator.) The where statement behavior implies that operators (i.e., code generated for all operators) in all parts of a program where the contextualization is not known at compile time (such as externally visible functions) must be able to deal with the cases where there are no active positions of shapes.

The where statement can only further constrain the set of active positions of a shape - it can never enlarge that set. It is precisely for the purpose of enlarging the active set of positions that the everywhere statement [see §6.4] exists.

Im Dokument A Reference Description of the C (Seite 36-42)