This action might not be possible to undo. Are you sure you want to continue?

**Lee Gao November 11, 2012
**

Abstract The corner-stone of program code optimization is the ability to gather accurate and relevant information revolving around the control ﬂow of the program. To this end, several static analysis methods can be applied. We present an old but powerful technique in analyzing information ﬂow on graphs representing the control ﬂow of our program, known as dataﬂow analysis.

1

Introduction

Before we take oﬀ, we need to explain some of the concepts and terminology that will be used throughout this paper. First of all, we will represent programs and computations using an abstract assembly language extended with inﬁnite registers as well as inﬁnite memory. Suppose that our language consists of the following instructions at the procedure level (without consideration for how procedures themselves are structured, as we only consider the ﬂow of data within procedures rather than as a whole program)

stmt := move(dest, e2 ) | exp(e) | seq(s1 , s2 ) | jump(l) | cjump(e, l) | return dest must be either a register or a mem(e) evaluates expression e for its side eﬀects executes statement s1 and then s2 jump to address l jump to l if e is true returns from this procedure

where we consider programs and procedures as sequences of statements, which are themselves ∈ stmt. Here, we use e and s as symbolic variables representing expressions and statements respectively. Furthermore, we allow expressions to be of the form

expr := n | x | e1 ⊕ e2 | −e | mem e | lfunction (e1 , · · ·, en )

n

numerical constants register x, represented as a variable binary operations unary negation memory address calls the function at label lfunction

The notation for registers (x) and memory addresses (mem e) may seem a little mysterious. In short, if it’s used as the destination of a move instruction, it will be used as a ”storage 1

container”. (e.g. move(x, 1) is equivalent to x := 1; move(mem(A + 0), 0) is equivalent to A[0] := 0). Furthermore, we reserve a special register R which will contain the return values from functions. In this notation, we can represent programs as a large nested seq statement; for example, we can write a program that sums an array: Listing 1 Sum over the elements of a 1: function Sum(a, n) 2: move(i, 0); 3: lstart loop : 4: cjump(i >= n, lend loop ); 5: move(R, mem(a + i)); 6: move(i, i + 1); 7: jump lstart loop ; 8: lend loop : 9: return 10: end function Where the ; (semicolon) operator can be translated directly as s1 ; s2 seq(s1 , s2 ). Alternatively, we can also represent the structure of this code as the binary tree over seq (where a left-to-right depth-ﬁrst traversal gives the execution order) as seen in ﬁgure 1 Figure 1: ”Tree”-like representation of our control ﬂow seq move(i, 0) cjump(i ≥ n) move(R, mem(a + i)) move(i, i + 1) jump seq seq seq seq return

However, this way of representing the control ﬂow is rather cumbersome, and while the arrows indicating the branches seems to be rather useful, it feels a little stupid having to tack on a seq on every single level; if only there was a way to merge these two techniques... We can just arbitrarily add in the arrows within our code listing in the semicolon notation: 2

1: 2: 3: 4: 5: 6: 7: 8: 9: 10:

function Sum(a, n) move(i, 0); lstart loop : cjump(i >= n, lend loop ); move(R, mem(a + i)); move(i, i + 1); jump lstart loop ; lend loop : return end function

but this way of representing control ﬂow isn’t as satisfactory. For example, ﬁgure 1 clearly shows the loop-structure starting at the cjump which isn’t as apparent in this system. Finally, we arrive at the actual graph representing the control ﬂow with the instructions as the nodes of the graph, presented in ﬁgure 2: Figure 2: Control Flow graph with instructions as nodes move(i, 0) cjump(i ≥ n) move(R, mem(a + i)) move(i, i + 1) jump return

In brief, a control ﬂow graph G = (V , E), where the nodes V ∈ stmt and edges E ∈ stmt × stmt is a partial function from one instruction to the next, denoting whether one instruction can step to the next within one cycle. Next, we introduce the concept of basic blocks, which are sequences that will never branch until exiting the block. For example, the sequence move(x, 0); can become a basic block because move(y, x) there are no instructions deviating the control ﬂow within the basic block. On the other hand, the sequence there. Now, by introducing a NOP command skip, all basic blocks b ∈ seq ⊂ stmt; therefore, a control ﬂow graph can also be seen as a graph of basic blocks. For example, an equivalent graph of ﬁgure 2 may look like: move(x, 0); jump llabel ; move(y, x) cannot become a basic block because of the jump instruction in

3

Figure 3: Control Flow Graph whose nodes are Basic Blocks move(i, 0) cjump(i ≥ n) basic block move(R, mem(a + i)); move(i, i + 1) jump return and furthermore, an additional equivalent graph to both ﬁgure 2 and ﬁgure 3 may look like ﬁgure 4: Figure 4: Another CFG whose nodes are Basic Blocks move(i, 0) cjump(i ≥ n) if true if false move(R, mem(a + i)); move(i, i + 1); jump return

It just so happens that ﬁgure 4 generates the maximal basic block control ﬂow (we can’t coalesce any blocks together to make a larger block and still retain the original program semantic); in general, we aim to work with CFGs of maximal basic blocks.

2

Motivating Example

As a motivating example, we ﬁrst consider a scenario in which we want to ﬁnd the set of assignments that will never be used and hence these assignment can be considered dead-code and can hence be removed. For example, in the program Listing 2 Dead Code Removal 1: move(a, 1); 2: move(b, a + 1); 3: print(3);

4

we can easily see that b’s value isn’t needed up to the print function call (we say that b doesn’t live up to print), so we can easily remove this assignment and the program would have printed the same thing. After this transformation, our program now becomes Listing 3 Dead Code Removal 1: move(a, 1); 2: print(3); Now, it’s fairly obvious that a’s value doesn’t live up to the print statement either, so we can remove the assignment for a as well, rendering our ﬁnal program to just become Listing 4 Dead Code Removal 1: print(3); As demonstrated above, the ability to ﬁnd live-ranges (the range during which the value of a particular assignment is useful) is quite useful. Now, we’re reasonable humans, so it’s quite easy for us to ﬁgure out the solution to these simple little puzzles. But when the system of variables is large, as is typical within even moderately useful programs, then similar to the existence of compilers, automation may be necessary.

2.1

Live Variable Analysis

More formally, we ask to ﬁnd which subset of all possible variables are live at every program point. Program Point A program point is every single point between two instructions. For example the program points of • before move(a, 0) • after move(a, 0) but before move(b, 0) • after move(b, 0) Live Variable A variable x is live at a program point p if the value of x is used/needed somehow along some path in the ﬂow graph starting at p. Dead Variable On the other hand, if x is no longer needed, or if x is not live, then x is dead. So our problem now boils down to determining what variables are live for each basic block of a program. This seems like quite an easy problem to solve; we can just program in what our intuition tells us: 1. First, gather the set of used registers/variables (from now on, they are synonymous) in our program. 2. From each time a variable xi is assigned to (it is on the left hand side of a move), up until it is re-assigned again, look for the last instruction that uses it. 5 move(a, 0); move(b, 0) are the points

3. We will set xi to be live at every program point after that assignment and immediately before the last time it was used or re-assigned. From this intuition, we observe a natural constraint for determining whether a variable is live at the program point before an instruction I: The variables {x0 , x1 , · · · , xn } that are live before instruction I must satisfy the restriction that • each of xi are either live after the program point immediately after I or will be used within I; furthermore, we cannot include variables assigned to during the execution of I within our live set. (e.g. beforelive [I] = (afterlive [I] − assignments(I)) + uses(I)) For example, we can try to apply the above constraints/equation to the uninitialized program: (For brevity’s sake, we’ll use the notation a := e move(a, e)) I1 : a := 0; I2 : b := 0; I3 : print(b + 3)

{}:beforelive [I1 ] {}:afterlive [I1 ] {} {} {} {}

Notice that we don’t have any constraints/equations to reconcile the beforelive and afterlive of I1 and I2 . That is, once we have the information for I2 , how do we know what to put into I1 ? In this example, we can reason that since the program ﬂows linearly, we can simply set beforelive [I2 ] = afterlive [I1 ]. That is, the live variables after executing an instruction are the same as the live variables before executing the next instruction. Since the befores and afters are the same sets, we can just remove the before sets for now. I1 : a := 0; I2 : b := 0; I3 : print(b + 3)

{} {} {} {}

Next, we notice that the information ﬂows from afterlive to beforelive (that is, we need to know the values of after in order to gather the values of before). Therefore, we need to set an initial condition/base case for the last live-set, afterlive [I3 ]. For now, let’s just assume that this is our whole program, and that no variables are live/will be used after our program terminates, which gives I1 : a := 0; I2 : b := 0; I3 : print(b + 3)

{} {} {} ∅

Now that we have afterlive [I3 ], we can compute beforelive [I3 ]. Since I3 : print(b + 3) uses the variable b and does not assign to any variables, we say that assignments(I3 ) = ∅ uses(I3 ) = {b} Now, applying our equation beforelive [I] = (afterlive [I] − assignments(I)) + uses(I) 6

to I3 gives us beforelive [I3 ] = (∅ − ∅) + {b} Therefore, beforelive [I3 ] = afterlive [I2 ] = {b} I1 : a := 0; I2 : b := 0; I3 : print(b + 3) Similarly, for I2 , we have assignments(I2 ) = {b} uses(I2 ) = ∅ which gives an equation for beforelive [I2 ] = ({b} − {b}) + ∅ Therefore, beforelive [I2 ] = afterlive [I1 ] = ∅ I1 : a := 0; I2 : b := 0; I3 : print(b + 3)

{} ∅ {b} ∅ {} {} {b} ∅

**Applying this equation one last time gives the ﬁnal live-set I1 : a := 0; I2 : b := 0; I3 : print(b + 3)
**

∅ ∅ {b} ∅

From this information, we can look at the ﬁrst assignment a := 0 and deduce that, because a is not live/never used after assigning to this a, then there’s no point in even having this assignment, and hence we can just eliminate it. On the other hand, we see that we do need to use b after its assignment in I2 , therefore, we cannot eliminate I2 , which gives the ﬁnal transformed code (after eliminating dead code) to be b := 0; print(b + 3) Now, this is all ﬁne and dandy if we don’t have any branches or conditionals, but will our technique work within the following example? Figure 5: Find the live variables of a non-basic block program a := 1; b := 2; cjump(1 = 2) print(a); jump

print(b)

7

More precisely, we can no longer claim that the variables that are live after one block must be the same set of variables that are live before the next block, because we may now have multiple blocks that might ”come after” our original block. However, if we reason that in order to eliminate the assignment to a (a is dead), we must guarantee that no code path ever uses a, then the contrapositive of that assertion will be if we can guarantee that at least one code path uses a, then we cannot eliminate the assignment to a (a is live). Since ”at-least-one” neatly translates to set union (convince yourself of this), we can add in an additional constraint (for basic blocks at least) that afterlive [B] =

B ∈successors(B)

beforelive [B ]

**These two constraints beforelive [B] = (afterlive [B] − assignments(B)) + uses(B) afterlive [B] =
**

B ∈successors(B)

(1) (2)

beforelive [B ]

perfectly speciﬁes the behavior of live variables, and we can use them to solve the problem in ﬁgure 5: Figure 6: Solution to ﬁgure 5 a := 1; b := 2; cjump(1 = 2)

∅

{a}∪{b}

print(a); jump

{a}

print(b){b}

Which tells us that neither a nor b can be eliminated from the program.

2.2

What about loops?

Let’s consider the following program

8

Figure 7: Live Variable Analysis in loops

a := b; b := 2; cjump(a return

2)

Introducing the desired initial conditions (namely that after returning, no variables will be used), we get Figure 8: Live Variable Analysis in loops, step 1

before[I1 ]:{} {}

I1 : a := b; I2 : b := 2; I3 : cjump(a

2)

{} after[I3 ]:{} {} ∅

I4 : return

Applying our live variable equation to I4 , namely uses(I4 ) = ∅ assignments(I4 ) = ∅ before[I4 ] = (after[I4 ] − assignments(I4 )) + uses(I4 ) = (∅ − ∅) + ∅ gives the following CFG after one step Figure 9: Live Variable Analysis in loops, step 2

before[I1 ]:{} {}

I1 : a := b; I2 : b := 2; I3 : cjump(a

2)

{} after[I3 ]:{} ∅ ∅

I4 : return

At this point, we no longer have any of the after information, so we can no longer apply 9

our primary dataﬂow constraint (which we will call the instruction constraint). Instead, we now need to ﬁnd after[I3 ] using the information we have for before[I4 ]. Applying our second dataﬂow constraint (which we will call the control ﬂow constraint): afterlive [I] =

I ∈successors(I)

beforelive [I ]

we see that we need to ﬁrst ﬁnd all successors I3 of I3 . Because I3 cjump(a 2, I4 , I1 ), which branches to I1 if a 2 and I4 otherwise, we see that the successors of I3 must be the set {I4 , I1 }, therefore, our control ﬂow constraint applied to I3 boils down to afterlive [I3 ] = beforelive [I4 ] ∪ beforelive [I1 ] We have already calculated the value of beforelive [I4 ], so we can just plug that in. However, we don’t have the value of beforelive [I1 ] yet, so we need to explicitly solve for that before we can continue. Let’s try to do just that. Figure 10: Basic block of at step 2 I1 : a := b; I2 : b := 2; I3 : cjump(a

before[I1 ]:{} {}

2)

{} after[I3 ]:{}

Recall that without branching within a speciﬁc sequence of instructions (as is the case within a basic block), we can use the simpliﬁed control ﬂow constraint, namely afterlive [Ik ] = beforelive [Ik+1 ] Finally, let’s abbreviate equation (1) as the function Transfer Transfer[S, B] = (S − assignments(B)) + uses(B) Let’s attempt to ﬁnd the value of before[I1 ] now beforelive [I1 ] = (afterlive [I1 ] − assignments(I1 )) + uses(I1 ) at this point, it’s fairly obvious that we need after[I1 ] as well after[I1 ] = before[I2 ] before[I2 ] = Transfer[after[I2 ], I2 ] = Transfer[before[I3 ], I2 ] = Transfer[Transfer[after[I3 ], I3 ], I2 ] uh oh At this point, we run into a slight problem. The entire point of calculating before[I1 ] was so that we can compute after[I3 ]. But in order to calculate before[I1 ], we need the value of after[I3 ] anyways... 10

Obviously, this occurs due to the fact that our program contains a loop in it. We can try to resolve this as we would a system of equations in R. For example, if we set x1 = before[I1 ] and after[I3 ] = ∅ ∪ before[I1 ] = x1 , then our equation becomes x1 = Transfer[Transfer[Transfer[x1 , I3 ], I2 ], I1 ] Now, if we think of x1 , Ik as real values and our transfer function as Transfer : R × R → R, then we can easily solve this system by substitution and the algebraic manipulation of the variables. For example, if the Transfer function was Transfer[x, Ik ] = 2x + k then the above system just becomes x = 2 (2 (2x + 3) + 2) + 1 = 8x + 17 17 x=− 7 (We can interpret this as a graph that must satisfy the following system of equations) Figure 11: Real valued CFG and transfer function x1 : 2x2 + 1; x2 : 2x3 + 2; x3 : 2x1 + 3

Unfortunately, there are no nice analogues of this ”substitution” technique for our sets-ofregister valued transfer functions so our traditional gradeschool algebra will not be enough to save us here. However, all is not lost. Suppose that we go back to an analogous problem Figure 12: Another real valued CFG and transfer function x1 : 2x2 ; x2 : 2x3 ; cos x1 x3 : 4

Which represents the system x = cos(x); then, if we just run this program over and over again with the initial value of 0 (that is, we plug in the value of cos(0) as x in the second iteration), we will see that its value eventually converges, or cosk (0) = cosk+1 (0) Therefore, set x = cosk (0) gives the solution, since x = cos(x) 11

Figure 13: cos(cos(cos(· · · 0))) eventually converges

This suggests that if we just apply Transfer to itself a bunch of times with a good initial guess, it will eventually reach the desired solution. The reasoning behind this is quite natural. Suppose that our Transfer function will always transform its input into one that is ”better” or more optimal than before, then the output will serve as a better initial guess than the previous iteration until we eventually converge towards the optimal point. Indeed, if we apply this technique to ﬁgure 10, we will see that this will produce the correct live sets within just a few iterations (termination occurs when our live-sets no longer gets updated). Let’s try that now. We’ll start with an initial guess that no variables are live, namely beforelive [I1 ] = ∅, then Figure 14: Initial setup of ﬁxed point solution

I1 : a := b; I2 : b := 2; I3 : cjump(a

before[I1 ]:∅

2)

after[I3 ]:{}

Since we deﬁned our block-level Transfer function to be before[I1 ](k+1) = Transfer[Transfer[Transfer[before[I1 ](k) , I3 ], I2 ], I1 ] where before[I1 ](k) is the k th approximation of before[I1 ] using our ﬁxed-point iteration; we

12

can expand this out by considering all of the assignments(Ik ), uses(Ik ) as well assignments(I1 ) = {a} uses(I1 ) = {b} assignments(I2 ) = {b} uses(I2 ) = ∅ assignments(I3 ) = ∅ uses(I3 ) = {a} before[I1 ](k+1) = Transfer[Transfer[Transfer[before[I1 ](k) , I3 ], I2 ], I1 ] = Transfer[Transfer[ before[I1 ](k) + {a} , I2 ], I1 ] = Transfer[ before[I1 ](k) + {a} − {b} , I1 ] = before[I1 ](k) − {b} + {a} − {a} + {b} = before[I1 ](k) + {b} So feeding in before[I1 ](0) = ∅, we get before[I1 ](1) = {b} Figure 15: Step two of our ﬁxed point solution

I1 : a := b; I2 : b := 2; I3 : cjump(a

before[I1 ]:{b}

2)

after[I3 ]:{b}

Now, before[I1 ](1) is a better guess than ∅, so we can now use it as our initial guess. However, if we attempt to take one more step however, we will ﬁnd that before[I1 ](2) = {b} + {b} = before[I1 ](1) So before[I1 ](1) is our ﬁxed point and before[I1 ] = {b} solves our constraints. From this, we can easily gather the rest of the live-sets: Figure 16: Final solution

I1 : a := b; I2 : b := 2; I3 : cjump(a

before[I1 ]:{b} {a}

2)

{a,b} after[I3 ]:{b} ∅ ∅

I4 : return

From which we can conclude that neither I1 nor I2 are dead code. 13

3

Mathematical Preliminaries

You may have been slightly put oﬀ by the clever arrangement of words in section 2.2 justifying that applying the Transfer function to any input will result in an output that is always at least as ”close” to the optimal solution as the input. However, this is obviously not true, as ﬁgure 11 demonstrates. If we have our transfer function being Transfer[x] = 8x + 17 then the sequence Transferk [0]

∞ k=0

**is the same as 17
**

k 0

∞ 8

k 0

which obviously diverges. We will now establish, with more mathematical rigor, the conditions under which the ﬁxedpoint of a transfer function may exist by starting oﬀ with the basics.

3.1

(Partial) Ordering

The concept of ”closeness” surely depends on some way of ranking and ordering the variablesets such that some sets of variables are ”closer” to the true set of live variables than others. We will embody this concept using an ordering relationship. Recall from elementary school that we can order/rank natural numbers as: 1 ≤ 2 ≤ 3 ≤ 4 ≤ 5 ≤ ··· We extend this concept of ordering to arbitrary sets P . Let’s introduce some terminology (the bread and butter of mathematicians, without which, anyone could become one) Partial Order A partial order (P , ) (or a partially ordered set) consists of • A set P on which we wish to impose ordering upon • An ”ordering” (partial) relation that exhibits the properties that is: 1. Reﬂexive : x x 2. Anti-symmetric : x y ∧ y x =⇒ x = y 3. Transitive : x y ∧ y z =⇒ x z notice the similarity between the generic and ≤ in the naturals. In fact, the naturals is itself a partially ordered set of (N, ≤). In fact, it is often easier to think of in terms of ≤ on some quantitative feature of our set in question. Now, we call (P , ) a partial order because not all elements of P may be comparable. We call a partial order a total order if its ordering relation obeys all of the three properties above as well as an additional fourth property 4. Totality : either x y or y x for all x, y ∈ P

14

Let’s begin with a quick and dirty example. Suppose I wanted to get my friend three birthday presents. I have 6 rolls of wrapping paper, colored as {red, blue, yellow, purple, orange, green}, and I know that she prefers purple over red and blue, orange over red and yellow, and green over blue and yellow; however, she has speciﬁed no preference say, between purple and orange or red versus blue. Which three colors should I pick? We can create an order of her preference in colors ({red, blue, purple, orange, yellow, green} , ): red, blue red, yellow blue, yellow purple orange green

and Reﬂexivity, Transitivity 3.1.1 Hasse diagrams

We can represent this graphically so items that are on the ”same” level of ordering can be grouped together at the same height to visually indicate the ranking of the elements. This is known as a Hasse Diagram. More precisely, a Hasse Diagram is a graphical representation of a partial order where • if two elements x and y ∈ P are not related by our ordering relation (i.e. we do not deﬁne whether they should be related or not, we say that these two elements are incomparable), then they will be drawn on the same level. For example, since we do not known if the friend prefers purple over orange, these two colors are incomparable, and we would draw our chart like Figure 17: Relative positioning of purple and orange within our Hasse Diagram purple orange

• x is below y when x y ∧ x red and blue below purple

y. For example, because red, blue

purple, we would put

Figure 18: red and blue are below purple in our Hasse Diagram purple red orange blue

• x is connected to y by a line whenever x ywedgex y and there is no z ∈ P such that x z y. That is, x must be an immediate ”successor” of y within our ordering hierarchy. For example, because red purple immediately, we can connect these two Figure 19: Connecting purple and red within our Hasse Diagram purple red orange blue

15

Similarly, because blue

purple and red

orange, we can connect these as well

Figure 20: More connections within our Hasse Diagram purple red orange blue

Now, suppose that we add in a new color brown, and we overhear that our friend dislikes brown as opposed to blue, then we get a new rule: brown blue

This means that we need to add it to the third level of our Hasse diagram, since brown must be below blue. Figure 21: Adding brown purple red orange blue brown Now, we know by transitivity that brown purple and brown blue, so how should we connect brown? Well, because brown isn’t an ”immediate” successor of purple (e.g. it needs to go through blue ﬁrst), we know that we cannot connect these two. Therefore, we can only connect brown to blue Figure 22: Connecting brown purple red orange blue brown In full, our hasse diagram will look like Figure 23: Full Hasse Diagram of our partial order of colors including brown purple red orange blue brown green yellow

16

From ﬁgure 23, it’s easy to see that brown is the least preferred color; red, blue, and yellow are next; and ﬁnally, purple, orange, and green are the most preferred colors. Therefore, we should obviously select purple, orange, and green as our wrapping paper color. As another example, the Hasse Diagram of ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9} , ≤) looks like Figure 24: The partial ordering of naturals 9 8 7 6 5 4 3 2 1 0

3.1.2

Upper/Lower Bounds of Partial Orders

Let’s now deﬁne the concept of lower and upper bounds of a partial order. If (P , ) is a partial order and S ⊆ P , then • An element l ∈ P is a lower bound of S if l S, or l x∀x ∈ S u ∀x ∈ S

• An element u ∈ P is an upper bound of S if S

u, or x

For example, 0 is a lower bound of {1, 2, 3} ∈ Z and 4 is an upper bound. In general, there may be multiple lower and upper bounds of the same subset S. As another example, consider the same partial ordering seen before in ﬁgure 25: Figure 25: Sample Partial Order purple red orange blue brown green yellow

17

Now, we can clearly see that red is a lower bound for the subset {purple, orange} Figure 26: purple red orange blue brown However, there are no lower bounds for the subset {purple, orange, green} Figure 27: No common lower bounds for purple, orange, and green purple red orange blue brown Similarly, both blue and brown are lower bounds of the subset {blue} Figure 28: Both blue and brown are lower bounds of {blue} purple red orange blue brown green yellow green yellow green yellow

3.1.3

Least Upper Bound and Greatest Lower Bound

We will now deﬁne the least upper bound (LUB) and the greatest lower bound (GLB). Suppose that (P , ) is a partial order and S ⊆ P , then • x ∈ P is the GLB of S if x is a lower bound of S and y x ∀ y ∈ LB(S), in other words, it’s ”larger” than all other lower bounds of S, and is hence termed the greatest lower bound • Similarly, we deﬁne the LUB x ∈ P of S if x is the ”smallest” upper bound of S. Unlike normal LB and U B, GLB and LU B are unique. As an example, the GLB of {purple, green, blue} is just blue

18

Figure 29: Blue is the GLB of {purple, blue, green} purple red orange blue brown while the GLB of {purple, green, blue, brown} is brown Figure 30: Blue is the GLB of {purple, blue, green} purple red orange blue brown green yellow green yellow

On the other hand, if we change our partial ordering to Figure 31: There is no GLB of {purple, orange} purple brown red orange blue green yellow

While both red, brown are lower bounds of {purple, orange}, neither are comparable. Therefore, there is no GLB for {purple, orange}

3.2

Lattices

A partial order (L, ) is a lattice if: • any ﬁnite non-empty subset S ⊆ L has both a LUB and a GLB in L. For example, the partial order (N, ≤) is a lattice because • Every ﬁnite subset of N has a LUB • Every (ﬁnite or inﬁnite) subset of N has a GLB On the other hand, a complete lattice is a lattice with the additional constraint that all inﬁnite subsets of L must also have both a LUB and a GLB. By this deﬁnition, (N, ≤) is not a complete lattice because no inﬁnite subsets of N has a LUB. 19

Figure 32: The lattice of naturals is not complete . . . 3 2 1 0 However, we can extend partial orders with two elements, • ⊥ = GLB(L) • = LU B(L) ∀ x ∈ N, then this , ⊥ such that

In the case of the naturals ⊥ = 0; but what about ? Instead, suppose we extend the naturals with a top elements such that x ≤ augmented set is complete (i.e. it contains a LUB and GLB) Figure 33: The lattice of N ∪ is complete

. . . 3 2 1 0 From now on, we will almost exclusively work with complete lattices, so let’s take a look at a complete lattice of a set of 3 elements on the ordering operator ⊆. Figure 34: The complete lattice of ({x, y, z} , ⊆) : {a, b, c} {a, b} {a} {a, c} {b} ⊥:∅ {b, c} {c}

20

Sign up to vote on this title

UsefulNot useful- Korn Shell (Ksh) Programming
- MIT6_045JS11_assn03
- Com Put Ability and Complexity Spring 2007
- lab 1 & 3
- Ether Um
- aata-20150812
- Mathematics Class12
- 52996443-Maths-NCERT
- Recursive Types
- Hash Tables
- Programming Languages and Lambda Calculi
- R Programing
- Foundations of Functional Programming
- Samsung Qp Data Struct Solution
- How to Transform and Filter Images Using IFS
- A Tutorial on the Universality and Expressiveness of Fold
- Tuple
- 31851696
- root finding methods.pdf
- Convex Analysis
- prez0
- Denotational Semantics
- Notes 7
- Ncert Math 12
- Lab05wsolns
- A 03310103
- Puntos de Funcion Guia Ultima.pdf
- 12math
- ppl1
- Chapter 1 Relation and Function
- Dataflow Odyssey

Are you sure?

This action might not be possible to undo. Are you sure you want to continue?

We've moved you to where you read on your other device.

Get the full title to continue

Get the full title to continue reading from where you left off, or restart the preview.

scribd