gsoc: Improved code generation

Charles Zhang <[email protected]> Tue, 26 Jun 2018 17:51:33 -0400
Newsgroups gmane.lisp.clisp.devel
Message-ID <CAB=SSSnCkH6VuqOyo1ik8fNWmhM=zCnwGnr+amQMGnJvBy6syA@mail.gmail.com>
Hello clisp, Bruno, Sam,

I have spent some time optimizing bytecode generation, including:
- using all the various specialized CALL instructions more effectively
- inlining VALUES, MULTIPLE-VALUE-SETQ/BIND
- a very bad basic block scheduler
- reduce LOAD/STOREs (without peephole optimizing)

Together with data flow optimizations, the new compiler is usually
able to generate better code than the existing compiler.

For example, (from cl-ansi-test-suite)
> (cleavir-compile '(lambda ()
                   (let* ((x (list 'a 'b 'c 'd 'e))
                          (y (list 'f 'g 'h 'i 'j))
                          (p1 1) (p2 2) (len 3)
                          (z '(10 11 12)))
                     (rotatef (subseq x p1 (+ p1 len))
                              (subseq y p2 (+ p2 len))
                              z)
                     (values x y z))))

Disassembly of function NIL
(CONST 0) = A
(CONST 1) = B
(CONST 2) = C
(CONST 3) = D
(CONST 4) = E
(CONST 5) = F
(CONST 6) = G
(CONST 7) = H
(CONST 8) = I
(CONST 9) = J
(CONST 10) = 1
(CONST 11) = 4
(CONST 12) = 2
(CONST 13) = 5
(CONST 14) = (10 11 12)
(CONST 15) = :START1
(CONST 16) = :END1
(CONST 17) = :START1
(CONST 18) = :END1
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
42 byte-code instructions:
0     (CONST&PUSH 0)                      ; A
1     (CONST&PUSH 1)                      ; B
2     (CONST&PUSH 2)                      ; C
3     (CONST&PUSH 3)                      ; D
4     (CONST&PUSH 4)                      ; E
5     (LIST&PUSH 5)
7     (CONST&PUSH 5)                      ; F
8     (CONST&PUSH 6)                      ; G
9     (CONST&PUSH 7)                      ; H
10    (CONST&PUSH 8)                      ; I
11    (CONST&PUSH 9)                      ; J
12    (LIST&PUSH 5)
14    (LOAD&PUSH 1)
15    (CONST&PUSH 10)                     ; 1
16    (CONST&PUSH 11)                     ; 4
17    (CALLS2&PUSH 96)                    ; SUBSEQ
19    (LOAD&PUSH 1)
20    (CONST&PUSH 12)                     ; 2
21    (CONST&PUSH 13)                     ; 5
22    (CALLS2&PUSH 96)                    ; SUBSEQ
24    (CONST&PUSH 14)                     ; (10 11 12)
25    (LOAD&PUSH 4)
26    (LOAD&PUSH 2)
27    (PUSH-UNBOUND 4)
29    (CONST 10)                          ; 1
30    (STORE 3)
31    (CONST 11)                          ; 4
32    (STORE 2)
33    (CALLS2 104)                        ; REPLACE
35    (LOAD&PUSH 3)
36    (LOAD&PUSH 1)
37    (PUSH-UNBOUND 4)
39    (CONST 12)                          ; 2
40    (STORE 3)
41    (CONST 13)                          ; 5
42    (STORE 2)
43    (CALLS2 104)                        ; REPLACE
45    (LOAD&PUSH 4)
46    (LOAD&PUSH 4)
47    (LOAD&PUSH 4)
48    (STACK-TO-MV 3)
50    (SKIP&RET 6)

vs CLISP's
Disassembly of function NIL
(CONST 0) = A
(CONST 1) = B
(CONST 2) = C
(CONST 3) = D
(CONST 4) = E
(CONST 5) = F
(CONST 6) = G
(CONST 7) = H
(CONST 8) = I
(CONST 9) = J
(CONST 10) = (10 11 12)
(CONST 11) = 1
(CONST 12) = 3
(CONST 13) = 2
0 required arguments
0 optional arguments
No rest parameter
No keyword parameters
48 byte-code instructions:
0     (CONST&PUSH 0)                      ; A
1     (CONST&PUSH 1)                      ; B
2     (CONST&PUSH 2)                      ; C
3     (CONST&PUSH 3)                      ; D
4     (CONST&PUSH 4)                      ; E
5     (LIST&PUSH 5)
7     (CONST&PUSH 5)                      ; F
8     (CONST&PUSH 6)                      ; G
9     (CONST&PUSH 7)                      ; H
10    (CONST&PUSH 8)                      ; I
11    (CONST&PUSH 9)                      ; J
12    (LIST&PUSH 5)
14    (CONST&PUSH 10)                     ; (10 11 12)
15    (CONST&PUSH 11)                     ; 1
16    (CONST&PUSH 12)                     ; 3
17    (CALLSR&PUSH 2 55)                  ; +
20    (CONST&PUSH 13)                     ; 2
21    (CONST&PUSH 12)                     ; 3
22    (CALLSR&PUSH 2 55)                  ; +
25    (LOAD&PUSH 4)
26    (CONST&PUSH 11)                     ; 1
27    (LOAD&PUSH 3)
28    (CALLS2&PUSH 96)                    ; SUBSEQ
30    (LOAD&PUSH 4)
31    (CONST&PUSH 13)                     ; 2
32    (LOAD&PUSH 3)
33    (CALLS2&PUSH 96)                    ; SUBSEQ
35    (LOAD&PUSH 4)
36    (LOAD&PUSH 7)
37    (LOAD&PUSH 2)
38    (CONST&PUSH 11)                     ; 1
39    (LOAD&PUSH 7)
40    (PUSH-UNBOUND 2)
42    (CALLS2 104)                        ; REPLACE
44    (LOAD&PUSH 6)
45    (LOAD&PUSH 1)
46    (CONST&PUSH 13)                     ; 2
47    (LOAD&PUSH 6)
48    (PUSH-UNBOUND 2)
50    (CALLS2 104)                        ; REPLACE
52    (LOAD 2)
53    (STORE 5)
54    (SKIP 5)
56    (LOAD&PUSH 2)
57    (LOAD&PUSH 2)
58    (LOAD&PUSH 2)
59    (STACK-TO-MV 3)
61    (SKIP&RET 4)

An example of the suboptimal basic block scheduler:
CLEAVIR-CLISP> (cleavir-compile '(lambda (n) (dotimes (i n) (print i))))
#<COMPILED-FUNCTION NIL>
CLEAVIR-CLISP> (disassemble *)

Disassembly of function NIL
(CONST 0) = 0
(CONST 1) = NIL
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
15 byte-code instructions:
0     (CONST&PUSH 0)                      ; 0
1     L1
1     (LOAD&PUSH 0)
2     (LOAD&PUSH 3)
3     (CALLSR&PUSH 1 52)                  ; >=
6     (LOAD&JMPIF 0 L20)
9     (LOAD&PUSH 1)
10    (PUSH-UNBOUND 1)
12    (CALLS1 142)                        ; PRINT
14    (LOAD&INC&STORE 1)
16    (SKIP 1)
18    (JMP L1)
20    L20
20    (CONST 1)                           ; NIL
21    (SKIP&RET 4)

Recursive functions don't use JSR in the new compiler, but rather
close over themselves.
CLEAVIR-CLISP> (cleavir-compile '(lambda ()
                   (labels ((fact (n)
                              (if (zerop n)
                                  1
                                  (* n (fact (1- n))))))
                     #'fact)))
#<COMPILED-FUNCTION NIL>
CLEAVIR-CLISP> (funcall * )
#<COMPILED-FUNCTION NIL>
CLEAVIR-CLISP> (disassemble *)

Disassembly of function NIL
(CONST 0) = #(NIL #<COMPILED-FUNCTION NIL>)
(CONST 1) = 1
1 required argument
0 optional arguments
No rest parameter
No keyword parameters
13 byte-code instructions:
0     (LOAD&PUSH 1)
1     (CALLS2&PUSH 172)                   ; ZEROP
3     (LOAD&JMPIF 0 L20)
6     (LOADV&PUSH 0 1)
9     (LOAD&DEC&PUSH 3)
11    (FUNCALL&PUSH 1)
13    (LOAD&PUSH 3)
14    (LOAD&PUSH 1)
15    (CALLSR 2 57)                       ; *
18    (SKIP&RET 4)
20    L20
20    (CONST 1)                           ; 1
21    (SKIP&RET 3)

Charles

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
clisp-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/clisp-devel