Aspects of Compilation for Reversible Programming Languages Holger - - PowerPoint PPT Presentation
Aspects of Compilation for Reversible Programming Languages Holger - - PowerPoint PPT Presentation
Aspects of Compilation for Reversible Programming 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
Overview
Setup Quick language primers
Janus PISA
Complications for compilation Assorted techniques
Assignment statements Control flow operators Procedure (un)calls Structured 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 < b then update_a else a −= b * 2 + c call fi uncall update_b a > b update_b procedure
Algorithms High−level languages Computer Machine code
[CSR’07] [CF’08]
Implementation Physical Gate level architecture
(Cl-)aim: reversibility everywhere.
4
Close-up
n 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 points
n 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 by recursive descent Every assignment reversibly updates array cells Assertions enforce reversibility at run-time Uncalls provide direct access to inverse semantics
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 + regs SUB regd regs ADD regd ← regd − regs ANDX regd regs regt ANDX regd ← regd ⊕ (regs ∧ regt) XOR regd regs XOR regd ← regd ⊕ regs RL regd regs RR regd ← regd < <rotate regs EXCH regd regp EXCH regd ↔ M(regp)
9
PISA examples: control ops
i Inv(i) Effect(i) BRA n BRA br ← br + n BEQ regs regt n BEQ br ← br + (regs = regt ? n : 0) BGTZ regs n BGTZ br ← br + (regs > 0 ? n : 0) SWAPBR regd SWAPBR regd ↔ br RBRA n RBRA like BRA but changes direction
10
Paired branches for jumps
PC update routine 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 strictly semantics-preserving, so no (final) garbage. Usual transformations 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 time nor space-preserving Granularity: Different atomic levels of reversibility: Janus is ‘coarser’ than PISA, and relies on irreversible expression
- evaluation. This must be simulated reversibly in PISA, using
ancillary space, garbage, etc.
12
A common RC/QC goal: clean ancillae
Ancillary space Time e2 start Time e1 e4 e2 e−1
2
e−1
1
e−1
4
e−1
7
e1 e−1
2
e−1
4
e−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 expressions reversibly generates garbage. Uncomputation (removing the garbage) is easy: Inversion of the 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 be conserved, and we didn’t consume an ancilla for the result!
14
Control flow operators
✲
- ❅
❅
- ❅
❅
e1
t f
✲ s1 ❄ ✗ ✖ ✔ ✕
e2
t f
✲ ✲ s2 ✻ ✲ ✗ ✖ ✔ ✕
e1
t f
✲ s1 ❄
- ❅
❅
- ❅
❅
e2
t f
✛
s2
✻ ✲
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
✲ ✲ B2 ✻
= ⇒ = ⇒ = ⇒
✲
- ❅
❅
- ❅
❅
e1
t f
✲ ✲ ✲ B1 ✲ ✲ B2 ✲ ❄ ✗ ✖ ✔ ✕
e2
t f
✲ ✻
16
Compiling if-then-else-fi: branches and test
<code for re1 ← [ [e1] ]c > ; Evaluate e1 test : BEQ re1 r0 testfalse ; Jump if [ [e1] ] = 0 XORI 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
✲ ✻
17
Compiling if-then-else-fi: assertion
<code for re1 ← [ [e1] ]c > ; Evaluate e1 test : BEQ re1 r0 testfalse ; Jump if [ [e1] ] = 0 XORI 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
✲ ✻
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
✲ ✻
18
Compiling if-then-else-fi: compose
<code for re1 ← [ [e1] ]c > ; Evaluate e1 test : BEQ re1 r0 testfalse ; Jump if [ [e1] ] = 0 XORI re1 1 ; Clear re1 <code for B1 branch> XORI re2 1 ; Set re2 = 1 asserttrue : BRA assert ; Jump testfalse : 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
✲ ✻
19
Compiling if-then-else-fi: error check
BNE re1 r0 error ; Error check <code for re1 ← [ [e1] ]c > ; Evaluate e1 test : BEQ re1 r0 testfalse ; Jump if [ [e1] ] = 0 XORI re1 1 ; Clear re1 <code for B1 branch> XORI re2 1 ; Set re2 = 1 asserttrue : BRA assert ; Jump testfalse : BRA test ; Receive jump <code for B2 branch> assert : BNE re2 r0 asserttrue ; Receive jump <code for re2 → [ [e2] ]c > ; Unevaluate e2 BNE re2 r0 error ; Error check
= ⇒ = ⇒ = ⇒
✲
- ❅
❅
- ❅
❅
e1
t f
✲ ✲ ✲ B1 ✲ ✲ B2 ✲ ❄ ✗ ✖ ✔ ✕
e2
t f
✲ ✻
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: RFUN
mirror t case t of Cons(a, b) → let c = mirror a in let d = mirror b in Cons(d, c) Nil → Nil
Mirror function applied to Cons(Nil, Cons(Nil, Nil)):
Nil Cons Cons Nil Nil Cons Cons Nil Nil Nil
So the result is Cons(Cons(Nil, Nil), Nil). Key problem: How can constructor terms be represented in a reversible 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 → Nil y →
Nil Cons Cons Nil 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 be translated, 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 general reversibilizations Check www.diku.dk/~funkstar for references, and topps.diku.dk/pirc for interpreters. Thanks!
24