aspects of compilation for reversible programming languagesselinger/qpc2015/... · the view from...
TRANSCRIPT
Aspects of Compilation for ReversibleProgramming Languages
Holger Bock Axelsen
DIKU, Dept. of Computer Science, University of Copenhagen
www.diku.dk/~funkstar
QPCW @ IQC, U Waterloo, June 11, 2015
Overview
Setup
Quick language primers
JanusPISA
Complications for compilation
Assorted techniques
Assignment statementsControl flow operatorsProcedure (un)callsStructured heap data
Conclusion
2
Danger!
3
The view from Olympus
a += b
NEG $3
XORI $3 42
BRA −6
XORI $3 42
BRA 6
ADD $2 $3
SWBR $1
if a < bthen
update_a
elsea −= b * 2 + c
call
fiuncall
update_b
a > bupdate_b
procedure
Algorithms
High−levellanguages
Computer
Machine code
[CSR’07]
[CF’08]
ImplementationPhysical
Gate level
architecture
(Cl-)aim: reversibility everywhere.
4
Close-upn x1 x2
procedure fib
if n=0
then x1 += 1
x2 += 1
else n -= 1
call fib
x1 += x2
x1 <=> x2
fi x1=x2
procedure main
x1 += 5
x2 += 8
uncall fib
...
BRA fib bot
SUBI $1 1
EXCH $2 $1
fib: SWAPBR $2
NEG $2
EXCH $2 $1
ADDI $1 1
XORI $5 n
EXCH $6 $5
XOR $4 $6
EXCH $6 $5
XORI $5 n
ADDI $6 0
SLTX $3 $4 $6
if5: BEQ $3 $0 if6
...5
Source primer: Janus (dev@Caltech, early 80s)Janus: C-style reversible programming on arrays
Program
p ::= d∗ (procedure id s)+
d ::= x | x[c]Statements
s ::= x ⊕= e | x[e]⊕= e| call id | uncall id| if e then s else s fi e| from e do s loop s until e| skip | s s
Expressions, operators, constants
e ::= c | x | x[e] | e ⊗ e⊗ ::= ⊕ | * | && | <= | · · ·⊕ ::= + | - | ^c ::= · · · | -1 | 0 | 1 | · · ·
6
Janus sticky pointsn x1 x2
procedure fib
if n=0
then x1 += 1
x2 += 1
else n -= 1
call fib
x1 += x2
x1 <=> x2
fi x1=x2
procedure main
x1 += 5
x2 += 8
uncall fib
Key points:
Structured: translate byrecursive descent
Every assignmentreversibly updates arraycells
Assertions enforcereversibility at run-time
Uncalls provide directaccess to inversesemantics
7
Target primer: PISA (dev@MIT, late 90s)
RISC-style Von Neuman architecture (think MIPS)
32 GPRs of 32 bits each
Reversible data and control instructions
8
PISA examples: data ops
i Inv(i) Effect(i)
ADD regd regs SUB regd ← regd + regsSUB regd regs ADD regd ← regd − regsANDX regd regs regt ANDX regd ← regd ⊕ (regs ∧ regt)XOR regd regs XOR regd ← regd ⊕ regsRL regd regs RR regd ← regd <<rotate regsEXCH regd regp EXCH regd ↔ M(regp)
9
PISA examples: control ops
i Inv(i) Effect(i)
BRA n BRA br ← br + nBEQ regs regt n BEQ br ← br + (regs = regt ? n : 0)BGTZ regs n BGTZ br ← br + (regs > 0 ? n : 0)SWAPBR regd SWAPBR regd ↔ brRBRA n RBRA like BRA but changes direction
10
Paired branches for jumps
PC updateroutine
if br=0
then pc += 1
else pc += br
fi br=0
11
Complications for compilation
How does source & target reversibility make our task harder?
Correctness: The translation must be strictlysemantics-preserving, so no (final) garbage. Usualtransformations for reversible embeddings - tracing,compute-copy-uncompute - are not clean. (But still useful!)
Efficiency: The translation should preserve complexities.Bennett’s simulation (for injective functions) is neither timenor space-preserving
Granularity: Different atomic levels of reversibility: Janus is‘coarser’ than PISA, and relies on irreversible expressionevaluation. This must be simulated reversibly in PISA, usingancillary space, garbage, etc.
12
A common RC/QC goal: clean ancillae
Ancillary space
Timee2
start
Timee1 e4 e2 e−1
2 e−11e−1
4
e−17e1 e−1
2e−14e−1
1 e4 e7
. . . . . .
. . . . . . . . .(a)
(b)halt
start halt
(unbounded)
(bounded)
max(|e1|, |e2|, . . . , |ek |)
Ancillary space
13
Translation of x += exp
Most expression operators can be simulated, but...
Evaluating exp alone is irreversible: evaluating expressionsreversibly generates garbage.
Uncomputation (removing the garbage) is easy: Inversion ofthe PISA code for evaluating exp (w/ garbage.)
Translate by a clean compute-“copy”-uncompute.
(1) <code for re ← [[exp]]g > ; Generates garbage G(2) ADD rx re ; Update variable(3) <inverse code of 1> ; Removes garbage G
No garbage generated: the variables in exp are supposed to beconserved, and we didn’t consume an ancilla for the result!
14
Control flow operators
-��@@��@@e1
t
f
- s1?�
���e2
t
f
-
- s2 6
-��
��e1
t
f
- s1?
��@@��@@e2
t
f�s26
-
if e1 then s1 else s2 fi e2 from e1 do s1 loop s2 until e2
15
Compiling if-then-else-fi: decompose
-��
@@ ��
@@e1
t
f
- B1?�
���e2
t
f
-
- B26
=⇒ =⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
- B2-
?��
��e2
t
f
-
6
16
Compiling if-then-else-fi: branches and test<code for re1 ← [[e1]]c > ; Evaluate e1
test : BEQ re1 r0 testfalse ; Jump if [[e1]] = 0XORI re1 1 ; Clear re1<code for B1 branch>
...testfalse : BRA test ; Receive jump
<code for B2 branch>...
=⇒
=⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
- B2-
?��
��e2
t
f
-
6
17
Compiling if-then-else-fi: assertion<code for re1 ← [[e1]]c > ; Evaluate e1
test : BEQ re1 r0 testfalse ; Jump if [[e1]] = 0XORI re1 1 ; Clear re1<code for B1 branch>
...testfalse : BRA test ; Receive jump
<code for B2 branch>...
=⇒
=⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
(inversion)⇐⇒
- B2-
?��
��e2
t
f
-
6
18
Compiling if-then-else-fi: assertion...
<code for B1 branch>XORI re2 1 ; Set re2 = 1
asserttrue : BRA assert ; Jump...
<code for B2 branch>assert : BNE re2 r0 asserttrue ; Receive jump
<code for re2 → [[e2]]c > ; Unevaluate e2=⇒
=⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
(inversion)⇐⇒
- B2-
?��
��e2
t
f
-
6
18
Compiling if-then-else-fi: compose<code for re1 ← [[e1]]c > ; Evaluate e1
test : BEQ re1 r0 testfalse ; Jump if [[e1]] = 0XORI re1 1 ; Clear re1<code for B1 branch>XORI re2 1 ; Set re2 = 1
asserttrue : BRA assert ; Jumptestfalse : BRA test ; Receive jump
<code for B2 branch>assert : BNE re2 r0 asserttrue ; Receive jump
<code for re2 → [[e2]]c > ; Unevaluate e2=⇒
=⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
- B2-
?��
��e2
t
f
-
6
19
Compiling if-then-else-fi: error checkBNE re1 r0 error ; Error check<code for re1 ← [[e1]]c > ; Evaluate e1
test : BEQ re1 r0 testfalse ; Jump if [[e1]] = 0XORI re1 1 ; Clear re1<code for B1 branch>XORI re2 1 ; Set re2 = 1
asserttrue : BRA assert ; Jumptestfalse : BRA test ; Receive jump
<code for B2 branch>assert : BNE re2 r0 asserttrue ; Receive jump
<code for re2 → [[e2]]c > ; Unevaluate e2BNE re2 r0 error ; Error check
=⇒
=⇒ =⇒
-��
@@ ��
@@e1
t
f
-
-
- B1-
- B2-
?��
��e2
t
f
-
6
20
Procedure (un)calls
Recursion: Add call stack to subroutine convention [Frank99]
Code sharing for uncalls: Just one definition of f in the target
21
Other language paradigms work, too: RFUNmirror t , case t of
Cons(a, b) → let c = mirror a inlet d = mirror b in Cons(d , c)
Nil → Nil
Mirror function applied to Cons(Nil ,Cons(Nil ,Nil)):
Nil
Cons
ConsNil
Nil
Cons
Cons
Nil Nil
Nil
So the result is Cons(Cons(Nil ,Nil),Nil).
Key problem: How can constructor terms be represented in areversible machine? How can they be manipulated?
Requires a heap.(As in ‘heap memory management’, not the ‘heap data structure.’)22
Static heap representation — pointer trees
x 7→ Nily 7→
Nil
Cons
ConsNil
Nil
Cons
Cons
Nil
Nil
Nil
Nil
bottom of heap
heap pointer
free space
x :
y :
environment
free list pointer
The dynamics are a bit harder, but RFUN compiles to PISA.(In fact, a clever way using “hash-consing” is possible.)
23
Conclusion
Translation between reversible languages:
Extensionally clean - no garbage output at program level
Intensionally clean - no garbage across statements
Efficient - complexities of source program preserved
Generic - reversible updates and general CFOs can betranslated, richer data types possible
Surprises:
General register allocation is difficult
Even simple control flow is somewhat involved to translate
Hidden irreversibilities are costly, rely on generalreversibilizations
Check www.diku.dk/~funkstar for references, andtopps.diku.dk/pirc for interpreters. Thanks!
24