September 24
01: read(N)
02: Z := 0
03: I := 1
04: while (I < N) do
05: read(X)
06: if (X < 0) then
07: Y := f1(X)
else
08: Y := f2(X)
end if
09: Z = f3(Z, Y)
10: I = I + 1
end while
11: write(Z)
Instruction 4 has a data dependency on 1
01: read(N)
02: Z := 0
03: I := 1
04: while (I < N) do
Instruction 11 has a data dependency on both 2 and 9
(any unbroken path from write to read counts)
02: Z := 0
...
04: while (I < N) do
...
09: Z = f3(Z, Y)
...
end while
11: write(Z)
Instruction 8 has a control flow dependency from 6
Instruction 9 has a control flow dependency from 4
04: while (I < N) do
06: if (X < 0) then
else
08: Y := f2(X)
09: Z = f3(Z, Y)
Target: Start with a line and a set of variables.
Goal: Find the subset of the program that generates the same values for the specified variables.
define f(x, y, z):
1: x = y + z
2: z = y * z
end
Worst case assumptions:
1: x = 2
2: y = 3
3: *z = 4
4: write(x)
Does x on the last line depend on line 3?
It depends on what z is
1: x[0] = 2
2: x[1] = 3
3: x[z] = 4
4: write(x[0])
Does x[0] on the last line depend on line 3?
It also depends on what z is
Which value gets written to can change at runtime.
Idea 1: Worst-case assumptions
Slightly tighter restrictions possible if referring to an array/struct instead of a pointer
Idea 2: Determine possible program states
struct { int curr; LL *next } LL;
1: read(N)
2: I := 0
3: X := new LL { -1, null }
4: Y := &X->curr
5: while(I < N)
6: X := new LL { I, X }
7: Y := X->next->curr
8: I := I + 1
end while
9: print_ll(X)
What does X depend on as of line 6?
Consider possible state trajectories
View each entry in the state trajectory as a set of possible states
{ | N = [read value] (line 1); |
I = 0 (line 2); | |
ℓ1 = { -1, null } (line 3); | |
Y = ptr to ℓ1.curr; (line 4); | |
X = ptr to ℓ2 (line 6); | |
ℓ2 = { 0, ptr to ℓ1 } (line 6) } |
{ | N = [read value] (line 1); |
I = 0 (line 2); | |
X = ptr to ℓ1 (line 3); | |
ℓ1 = { -1, null } (line 3); | |
Y = ptr to ℓ1.curr; (line 4); } |
{ | N = [read value] (line 1); |
I = 0 (line 2); | |
ℓ1 = { -1, null } (line 3); | |
Y = ptr to ℓ1.curr; (line 4); | |
X = ptr to ℓ2 OR ℓ1 (lines 3 or 6) | |
ℓ2 = { 0, ptr to ℓ1 } (line 6) } |
Problem: Infinite possible states!
Each time through the loop we get another ℓ assigned.
Observation: Only need to repeat states enough times to get dependencies
Idea: Collapse states into the minimum needed
struct { int curr; LL *next } LL;
1: read(N)
2: I := 0
3: X := new LL { -1, null }
4: while(I < N)
5: X := new LL { I, X }
6: I := I + 1
end while
7: print_ll(X)
{ | N = C (line 1); |
I = C (line 2); | |
ℓ1 = { C, null } (line 3); | |
X = ptr to ℓ1 (line 3) |
{ | N = C (line 1); |
I = C (line 2); | |
ℓ1 = { C, null } (line 3); | |
ℓ2 = { C, ptr to ℓ1 } (line 5); | |
X = ptr to ℓ2 (line 5) |
{ | N = C (line 1); |
I = C (line 2); | |
ℓ1 = { C, null } (line 3); | |
ℓ2 = { C, ptr to ℓ1 } (line 5); | |
ℓ3 = { C, ptr to ℓ2 } (line 5); | |
X = ptr to ℓ2 (line 5) |
ℓ2 and ℓ3 are "similar". Replace them with a new virtual v4
{ | N = C (line 1); |
I = C (line 2); | |
ℓ1 = { C, null } (line 3); | |
v4 = { C, ptr to ℓ1 OR v4 } (line 5); | |
X = ptr to v4 (line 5) |
2+ loops subsumes both 1 loop and 3+ loops
Final state representation: { 0 loops; 2+ loops }
What if we want to slice a program in the context of specific inputs?
01: read(N)
02: Z := 0
03: I := 1
04: while (I < N) do
05: read(X)
06: if (X < 0) then
07: Y := f1(X)
else
08: Y := f2(X)
end if
09: Z = f3(Z, Y)
10: I = I + 1
end while
11: write(Z)
... with N = 2, X = -4, 3 the trace of this program is
11 21 31 41 51 61 71 91 101 42 52 62 82 92 102 43 111
(superscripts distiguish repeated occurrences)
Instead of considering all paths, focus on the trace under the provided inputs
(Dependencies for Y as of 82 on N = 2, X = -4, 3)
Added benefit: Can handle pointer tracking gracefully: Know exactly what the pointer's value will be.