0% found this document useful (0 votes)
144 views28 pages

Value Numbering

Value numbering is an optimization technique that assigns unique numbers to expressions to identify redundant computations. It works by analyzing intermediate representation code in passes and replacing expressions with the same value number to eliminate redundancy. While local value numbering only considers single basic blocks, superlocal value numbering analyzes extended basic blocks, which allows identifying redundancy across multiple connected blocks to improve optimization opportunities.

Uploaded by

Must Bastani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
144 views28 pages

Value Numbering

Value numbering is an optimization technique that assigns unique numbers to expressions to identify redundant computations. It works by analyzing intermediate representation code in passes and replacing expressions with the same value number to eliminate redundancy. While local value numbering only considers single basic blocks, superlocal value numbering analyzes extended basic blocks, which allows identifying redundancy across multiple connected blocks to improve optimization opportunities.

Uploaded by

Must Bastani
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 28

VALUE NUMBERING (VN) AS A

REPRESENTATIVE OPTIMIZATION

CSE 521
Penn State University

Spring 2022
Traditional Three-pass Compiler

Source Front IR Middle IR Back Machine


Code End End End code

Errors

Code Improvement (or Optimization)


• Analyzes IR and rewrites (or transforms) IR
• Primary goal is to reduce running time of the compiled code
→ May also improve space, power consumption, …
• Must preserve “meaning” of the code
→ Measured by values of named variables
→ A course (or two) unto itself
The Optimizer (or Middle End)

IR Opt IR Opt IR Opt IR


... Opt IR
1 2 3 n

Errors

Modern optimizers are structured as a series of passes

Typical Transformations
• Discover & propagate some constant value
• Move a computation to a less frequently executed place
• Specialize some computation based on context
• Discover a redundant computation & remove it
• Remove useless or unreachable code
• Encode an idiom in some particularly efficient form
The Role of the Optimizer
• The compiler can implement a procedure in many ways
• The optimizer tries to find an implementation that is “better”
→ Speed, code size, data space, …

To accomplish this, it
• Analyzes the code to derive knowledge about run-time behavior
→ Data-flow analysis, pointer disambiguation, …
→ General term is “static analysis”
• Uses that knowledge in an attempt to improve the code
→ Literally hundreds of transformations have been proposed
→ Large amount of overlap between them

Nothing ‘optimal’ about optimization


• Proofs of optimality assume restrictive & unrealistic conditions
Redundancy Elimination as an Example
An expression x+y is redundant if and only if, along every
path from the procedure’s entry, it has been evaluated, and its
constituent subexpressions (x & y) have not been re-defined.

If the compiler can prove that an expression is redundant


• It can preserve the results of earlier evaluations
• It can replace the current evaluation with a reference
Two pieces to the problem
• Proving that x+y is redundant
• Rewriting the code to eliminate the redundant evaluation
One technique for accomplishing both is called Value Numbering

Note: We have already seen Available Expression Analysis and


Global Common Subexpression Elimination. We will do a
comparison later.
Value Numbering (An old idea)
The key notion (Balke 1968 or Ershov 1954)
• Assign an identifying number, V(n), to each expression
→ V(x+y) = V(j) iff x+y and j have the same value ∀ path
→ Use hashing over the value numbers to make it efficient
• Use these numbers to improve the code
Improving the code
• Replace redundant expressions
• Simplify algebraic identities
• Discover constant-valued expressions, fold & propagate them
• This technique was invented for low-level, linear IRs
• Equivalent methods exist for trees (build a DAG )
Local Value Numbering
The Algorithm
For each operation o = <operator, o1, o2> in the block
1 Get value numbers for operands from hash lookup
2 Hash <operator,VN(o1),VN(o2)> to get a value number for o
3 If o already had a value number, replace o with a reference
4 If o1 & o2 are constant, evaluate it & replace with a loadI

If hashing behaves well, the algorithm runs in linear time


→ If not, use multi-set discrimination

Handling algebraic identities


• Case statement on operator type
• Handle special cases within each operator
Local Value Numbering
An example

Original Code With VNs Rewritten


a←b+c a 3 ← b 1 + c2 a←b+c
b←a-d b5 ← a 3 – d 4 b ← a –d
c ← b +c c6 ← b5 + c2 c ← b +c
d←a-d d5 ← a 3 – d 4 d←b

Any redundancy in this code?


Local Value Numbering
An example

Original Code With VNs Rewritten


a←x+y a 3 ← x 1 + y2 a3 ← x1 + y2
∗ b←x+y ∗ b 3 ← x 1 + y2 ∗ b3 ← a 3
a ← 17 a4 ← 17 a4 ← 17
∗ c←x+y ∗ c3 ← x1 + y2 ∗ c3 ← a3 (oops!)

Options:
• Use c3 ← b3
• Save a3 in t3
• Rename around it
Local Value Numbering
Example (continued)

Original Code With VNs Rewritten


a0 ← x0 + y0 a03 ← x01 + y02 a03 ← x01 + y02
∗ b 0 ← x 0 + y0 ∗ b03 ← x01 + y02 ∗ b03 ← a03
a1 ← 17 a14 ← 17 a14 ← 17
∗ c0 ← x0 + y0 ∗ c03 ← x01 + y02 ∗ c03 ← a03

Renaming: Notation: Result:


• Give each value a • While complex, • a03 is available
unique name the meaning is • Rewriting just
• Makes it clear clear works
Simple Extensions to Value Numbering
Constant folding
• Add a bit that records when a value is constant
• Evaluate constant values at compile-time
• Replace with load immediate or immediate operand

Identities: (Click)
Algebraic identities
x←y, x+0, x-0, x∗1, x÷1, x-x,
• Must check (many) special cases x∗0, x÷x, x∨0, x ∧ 0xFF…FF,
max(x,MAXINT), min(x,MININT),
• Replace result with input VN max(x,x), min(y,y), and so on ...

• Build a decision tree on operation

With values, not names


Optimization Scopes
• Local Methods: They confine their attention to basic blocks
• Superlocal Methods: They operate over extended basic blocks (EBBs)
→ Inside an EBB, the compiler can use facts discovered in earlier blocks to
improve the code in later blocks
→ Larger contexts often lead to larger set of optimization opportunities
• Regional Methods: They operate over scopes large than a single EBB but
smaller than a full procedure
→ The compiler can choose regions in many different ways
♦ Loop nests
♦ Theoretical properties such as domination relationship
• Global Methods (Intraprocedural Methods): Examine an entire
procedure/function
→ Motivation is that decision that are locally (or superlocally) optimal may have
bad consequences when considering larger contexts
• Whole-Program Methods (Interprocedural Methods): Consider an entire
program as their scope
→ We classify any transformation that involves more than one procedure as an
interprocedural optimization
Value Numbering

A m ← a + b
n ← a + b Missed opportunities
(need stronger methods)
B p ← c + d C q ← a + b
r ← c + d r ← c + d

D e ← b + 18 E e ← a + 17
s ← a + b t ← c + d
u ← e + f u ← e + f

F v ← a + b
w ← c + d
x ← e + f Local Value Numbering

G y ← a + b
• 1 block at a time
z ← c + d • Strong local results
• No cross-block effects
An Aside on Terminology

Control-flow graph (CFG)


A m ← a + b
n ← a + b • Nodes for basic blocks
• Edges for branches
B p ← c + d C q ← a + b • Basis for much of program
r ← c + d r ← c + d analysis & transformation

D e ← b + 18 E e ← a + 17
s ← a + b t ← c + d
u ← e + f u ← e + f

F v ← a + b
w ← c + d For this CFG, G = (N,E)
x ← e + f
• N = {A,B,C,D,E,F,G}
G y ← a + b • E = {(A,B),(A,C),(B,G),(C,D),
z ← c + d (C,E),(D,F),(E,F),(F,E)}
• |N| = 7, |E| = 8
Superlocal Value Numbering

A m ← a + b
n ← a + b EBB: A set of blocks B1, B2, …, Bn
where Bi is the sole predecessor of
Bi+1, and B1 does not have a unique
B p ← c + d C q ← a + b predecessor
r ← c + d r ← c + d

D e ← b + 18 E e ← a + 17
s ← a + b t ← c + d
u ← e + f u ← e + f

F v ← a + b
The Concept
w ← c + d
x ← e + f • Apply local method to EBBs
G
• Do {A,B}, {A,C,D}, & {A,C,E}
y ← a + b
z ← c + d • Obtain reuse from ancestors
• Avoid re-analyzing A & C
• Does not help with F or G

*
Superlocal Value Numbering
Efficiency
• Use A’s hash table to initialize hash tables for B & C
• To avoid duplication, use a scoped hash table
→ A, AB, A, AC, ACD, AC, ACE, F, G
• Need a VN → name mapping to handle kills
→ Must restore map with scope
→ Adds complication, not cost A m ← a + b
n ← a + b

B C
To simplify matters p ←
r ←
c + d
c + d
q ← a + b
r ← c + d

• Unique name for each definition D e ← b + 18 E e ← a + 17


s ← a + b t ← c + d
u ← e + f u ← e + f

F v ← a + b
w ← c + d
x ← e + f

G y ← a + b
z ← c + d
SSA Name Space (locally)
Example

Original Code With VNs Rewritten


a0 ← x0 + y0 a03 ← x01 + y02 a03 ← x01 + y02
∗ b 0 ← x 0 + y0 ∗ b03 ← x01 + y02 ∗ b03 ← a03
a1 ← 17 a14 ← 17 a14 ← 17
∗ c0 ← x0 + y0 ∗ c03 ← x01 + y02 ∗ c03 ← a03

Renaming: Notation: Result:


• Give each value a • While complex, • a03 is available
unique name the meaning is • rewriting works
• Makes it clear clear
SSA Name Space (in general)
Two principles
• Each name is defined by exactly one operation
• Each operand refers to exactly one definition

To reconcile these principles with real code


• Insert φ-functions at merge points to reconcile name space
• Add subscripts to variable names for uniqueness

x0 ← ... x1 ← ...
x ← ... x ← ...

becomes
x2 ←φ(x0,x1)
... ← x + ...
← x2 + ...
Superlocal Value Numbering
• To improve the results of value numbering, the compiler can
extend its scope from a single basic block to an EBB
• EBB-based version often capitalize on the tree structure of
the EBB
• To make value numbering over EBBs efficient, the compiler
must reuse the results of blocks that occur on multiple
paths through the EBB
Superlocal Value Numbering

A m0 ← a + b With all the bells & whistles


n0 ← a + b
• Find more redundancy
B p0 ← c + d C q0 ← a + b
• Pay little additional cost
r0 ← c + d r1 ← c + d • Still does nothing for F & G

D e0 ← b + 18 E e1 ← a + 17
s0 ← a + b t0 ← c + d
u0 ← e + f u1 ← e + f

F e3 ← φ(e0,e1)
u2 ← φ(u0,u1)
v0 ← a + b
This is in
w0 ← c + d
SSA Form
x0 ← e + f

G r2 ← φ(r0,r1)
y0 ← a + b
z0 ← c + d
Dominator-Based Value Numbering (a Regional
Optimization)

• The superlocal value numbering algorithm misses some opportunities


because it must discard the entire value table when it reaches a
block that has multiple processors in the CFG
• The superlocal method does not extend directly to regions that
include join points
• In the example above, there is a table that we can use for node F
→ Both paths that reach F have a common prefix, {A,C}
→ Every path from A to F passes through A and C
→ Thus, the compiler knows that every operation in A and every operation
in C must execute before the first operation in F
→ Consequently, we can use the table produced by processing C as the
‘initial state’ for processing F
→ What abouts assignments in D and E?
• The value numbering algorithm needs a way to find the most recent
common ancestor along all paths that reach a block
• Dominator-based value numbering goes beyond Superlocal value
numbering
→ It can potentially achieve better results by eliminating more
redundancies
Dominators
Definitions
x dominates y if and only if every path from the entry of the
control-flow graph to the node for y includes x
• By definition, x dominates x
• We associate a Dom set with each node
• |Dom(x )| ≥ 1

Immediate dominators
• For any node x, there must be a y in Dom(x ) closest to x
• We call this y the immediate dominator of x
• As a matter of notation, we write this as IDom(x )
Dominators
Dominators have many uses in analysis & transformation
• Finding loops A m0 ← a + b
n0 ← a + b

• Building SSA form


B p0 ← c + d C q0 ← a + b
• Making code motion decisions r0 ← c + d r1 ← c + d

D e0 ← b + 18 E e1 ← a + 17
s0 ← a + b t0 ← c + d
u0 ← e + f u1 ← e + f
Dominator sets Dominator tree
F
Bl ock D om I Dom A e3 ← φ(e0,e1)
u2 ← φ(u0,u1)
A A – v0 ← a + b
w0 ← c + d
B A, B A B C G x0 ← e + f

C A, C A
G
D A, C D, C D E F
r2 ← φ(r0,r1)
y0 ← a + b
E A, C E, C z0 ← c + d

F A, C , F C
G A,G A

We can visualize the IDOM relationship by building the


dominator tree for the CFG
Going from Superlocal Algorithm to Dominance-
Based Algorithm (DVNT)
The superlocal algorithm has not optimized F or G
• Why? Multiple predecessors

• Must decide what facts hold in F and in G


→ For G, combine B & F? A m0 ← a + b
n0 ← a + b
→ Merging state is expensive
B C
→ Fall back on what’s known
p0 ← c + d q0 ← a + b
r0 ← c + d r1 ← c + d

D E
• Can use table from IDom(x ) to start x
e0 ← b + 18 e1 ← a + 17
s0 ← a + b t0 ← c + d
u0 ← e + f u1 ← e + f

→ Use C for F and A for G F e3 ← φ(e0,e1)


→ Imposes a Dom-based application order u2 ← φ(u0,u1)
v0 ← a + b
w0 ← c + d
x0 ← e + f
Leads to Dominator VN Technique (DVNT)
G r2 ← φ(r0,r1)
y0 ← a + b
z0 ← c + d
Dominator Value Numbering
The DVNT Algorithm
• Use superlocal algorithm on extended basic blocks
→ Retain use of scoped hash tables & SSA name space
• Start each node with table from its IDom
→ DVNT generalizes the superlocal algorithm
• That is, DVNT traverses the dominator tree in preorder
form

• No values flow along back edges (i.e., around loops)


• Larger scope leads to (potentially) better results
Dominator Value Numbering

A DVNT advantages
m ← a + b
n ← a + b • Find more redundancy
• Little additional cost
B p ← c + d C q ← a + b
r ← c + d r ← c + d • Retains online character

D e ← b + 18 E e ← a + 17
s ← a + b t ← c + d
u ← e + f u ← e + f

F e3 ← φ(e1,e2)
u2 ← φ(u0,u1)
v ← a + b DVNT shortcomings
w ← c + d
x ← e + f • Misses some opportunities
• No loop-carried CSEs
G r2 ← φ(r0,r1)
y ← a + b
z ← c + d
The Story So Far
• Local algorithm (Balke, 1967)
• Superlocal extension of Balke (many)
• Dominator VN technique (Simpson, 1996)
⇒ All these propagate along forward edges
⇒ None are global methods

Global Methods
• Classic CSE (Cocke 1970)
• Partitioning algorithms (Alpern et al. 1988, Click 1995)
• Partial Redundancy Elimination (Morel & Renvoise 1979)
• SCC/VDCM (Simpson 1996)

We have already covered a global common subexpression


elimination algorithm based on Available Expressions
1 Build a model of control-flow
2 Pose questions as sets of simultaneous equations
3 Solve the equations
4 Use solution to transform the code
VN vs GCSE
• VN is value based!
→ Analyzes the use of values in a program, identifies expressions
that have the same value, and rewrites the code to remove
redundant computations
→ Its focus in on values, rather than names
→ Its scope can be extended – from BBs to EBBs to Regions
• GCSE is name based!
→ Can handle cases when different paths carry ‘different values’
for the same expression
→ Can handle cycles in CFG
• There is no clear winner!

You might also like