Function: byte-optimize-lapcode
byte-optimize-lapcode is an autoloaded and byte-compiled function
defined in byte-opt.el.gz.
Signature
(byte-optimize-lapcode LAP &optional FOR-EFFECT)
Documentation
Simple peephole optimizer. LAP is both modified and returned.
If FOR-EFFECT is non-nil, the return value is assumed to be of no importance.
Source Code
;; Defined in /usr/src/emacs/lisp/emacs-lisp/byte-opt.el.gz
;; This crock is because of the way DEFVAR_BOOL variables work.
;; Consider the code
;;
;; (defun foo (flag)
;; (let ((old-pop-ups pop-up-windows)
;; (pop-up-windows flag))
;; (cond ((not (eq pop-up-windows old-pop-ups))
;; (setq old-pop-ups pop-up-windows)
;; ...))))
;;
;; Uncompiled, old-pop-ups will always be set to nil or t, even if FLAG is
;; something else. But if we optimize
;;
;; varref flag
;; varbind pop-up-windows
;; varref pop-up-windows
;; not
;; to
;; varref flag
;; dup
;; varbind pop-up-windows
;; not
;;
;; we break the program, because it will appear that pop-up-windows and
;; old-pop-ups are not EQ when really they are. So we have to know what
;; the BOOL variables are, and not perform this optimization on them.
;; The variable `byte-boolean-vars' is now primitive and updated
;; automatically by DEFVAR_BOOL.
(defun byte-optimize-lapcode (lap &optional _for-effect)
"Simple peephole optimizer. LAP is both modified and returned.
If FOR-EFFECT is non-nil, the return value is assumed to be of no importance."
(let ((side-effect-free (if byte-compile-delete-errors
byte-compile-side-effect-free-ops
byte-compile-side-effect-and-error-free-ops))
;; Ops taking and produce a single value on the stack.
(unary-ops '( byte-not byte-length byte-list1 byte-nreverse
byte-car byte-cdr byte-car-safe byte-cdr-safe
byte-symbolp byte-consp byte-stringp
byte-listp byte-integerp byte-numberp
byte-add1 byte-sub1 byte-negate
;; There are more of these but the list is
;; getting long and the gain is typically small.
))
;; Ops producing a single result without looking at the stack.
(producer-ops '( byte-constant byte-varref
byte-point byte-point-max byte-point-min
byte-following-char byte-preceding-char
byte-current-column
byte-eolp byte-eobp byte-bolp byte-bobp
byte-current-buffer byte-widen))
(add-depth 0)
(keep-going 'first-time)
;; Create a cons cell as head of the list so that removing the first
;; element does not need special-casing: `setcdr' always works.
(lap-head (cons nil lap)))
(while keep-going
(byte-compile-log-lap " ---- %s pass"
(if (eq keep-going 'first-time) "first" "next"))
(setq keep-going nil)
(let ((prev lap-head))
(while (cdr prev)
(let* ((rest (cdr prev))
(lap0 (car rest))
(lap1 (nth 1 rest))
(lap2 (nth 2 rest)))
;; You may notice that sequences like "dup varset discard" are
;; optimized but sequences like "dup varset TAG1: discard" are not.
;; You may be tempted to change this; resist that temptation.
;; Each clause in this `cond' statement must keep `prev' the
;; predecessor of the remainder of the list for inspection.
(cond
;;
;; PUSH(K) discard(N) --> <deleted> discard(N-K), N>K
;; PUSH(K) discard(N) --> <deleted>, N=K
;; where PUSH(K) is a side-effect-free op such as
;; const, varref, dup
;;
((and (memq (car lap1) '(byte-discard byte-discardN))
(memq (car lap0) side-effect-free))
(setq keep-going t)
(let* ((pushes (aref byte-stack+-info (symbol-value (car lap0))))
(pops (if (eq (car lap1) 'byte-discardN) (cdr lap1) 1))
(net-pops (- pops pushes)))
(cond ((= net-pops 0)
(byte-compile-log-lap " %s %s\t-->\t<deleted>"
lap0 lap1)
(setcdr prev (cddr rest)))
((> net-pops 0)
(byte-compile-log-lap
" %s %s\t-->\t<deleted> discard(%d)"
lap0 lap1 net-pops)
(setcar rest (if (eql net-pops 1)
(cons 'byte-discard nil)
(cons 'byte-discardN net-pops)))
(setcdr rest (cddr rest)))
(t (error "Optimizer error: too much on the stack")))))
;;
;; goto(X) X: --> X:
;; goto-if-[not-]nil(X) X: --> discard X:
;;
((and (memq (car lap0) byte-goto-ops)
(eq (cdr lap0) lap1))
(cond ((eq (car lap0) 'byte-goto)
(byte-compile-log-lap " %s %s\t-->\t<deleted> %s"
lap0 lap1 lap1)
(setcdr prev (cdr rest)))
((memq (car lap0) byte-goto-always-pop-ops)
(byte-compile-log-lap " %s %s\t-->\tdiscard %s"
lap0 lap1 lap1)
(setcar lap0 'byte-discard)
(setcdr lap0 0))
;; goto-*-else-pop(X) cannot occur here because it would
;; be a depth conflict.
(t (error "Depth conflict at tag %d" (nth 2 lap0))))
(setq keep-going t))
;;
;; varset-X varref-X --> dup varset-X
;; varbind-X varref-X --> dup varbind-X
;; const/dup varset-X varref-X --> const/dup varset-X const/dup
;; const/dup varbind-X varref-X --> const/dup varbind-X const/dup
;; The latter two can enable other optimizations.
;;
;; For lexical variables, we could do the same
;; stack-set-X+1 stack-ref-X --> dup stack-set-X+2
;; but this is a very minor gain, since dup is stack-ref-0,
;; i.e. it's only better if X>5, and even then it comes
;; at the cost of an extra stack slot. Let's not bother.
((and (eq 'byte-varref (car lap2))
(eq (cdr lap1) (cdr lap2))
(memq (car lap1) '(byte-varset byte-varbind))
(let ((tmp (memq (car (cdr lap2)) byte-boolean-vars)))
(and
(not (and tmp (not (eq (car lap0) 'byte-constant))))
(progn
(setq keep-going t)
(if (memq (car lap0) '(byte-constant byte-dup))
(let ((tmp (if (or (not tmp)
(macroexp--const-symbol-p
(car (cdr lap0))))
(cdr lap0)
(byte-compile-get-constant t))))
(byte-compile-log-lap " %s %s %s\t-->\t%s %s %s"
lap0 lap1 lap2 lap0 lap1
(cons (car lap0) tmp))
(setcar lap2 (car lap0))
(setcdr lap2 tmp))
(byte-compile-log-lap " %s %s\t-->\tdup %s"
lap1 lap2 lap1)
(setcar lap2 (car lap1))
(setcar lap1 'byte-dup)
(setcdr lap1 0)
;; The stack depth gets locally increased, so we will
;; increase maxdepth in case depth = maxdepth here.
;; This can cause the third argument to byte-code to
;; be larger than necessary.
(setq add-depth 1))
t)))))
;;
;; dup varset discard(N) --> varset discard(N-1)
;; dup varbind discard(N) --> varbind discard(N-1)
;; dup stack-set(M) discard(N) --> stack-set(M-1) discard(N-1), M>1
;; (the varbind variant can emerge from other optimizations)
;;
((and (eq 'byte-dup (car lap0))
(memq (car lap2) '(byte-discard byte-discardN))
(or (memq (car lap1) '(byte-varset byte-varbind))
(and (eq (car lap1) 'byte-stack-set)
(> (cdr lap1) 1))))
(setcdr prev (cdr rest)) ; remove dup
(let ((new1 (if (eq (car lap1) 'byte-stack-set)
(cons 'byte-stack-set (1- (cdr lap1)))
lap1))
(n (if (eq (car lap2) 'byte-discard) 1 (cdr lap2))))
(setcar (cdr rest) new1)
(cl-assert (> n 0))
(cond
((> n 1)
(let ((new2 (if (> n 2)
(cons 'byte-discardN (1- n))
(cons 'byte-discard nil))))
(byte-compile-log-lap " %s %s %s\t-->\t%s %s"
lap0 lap1 lap2 new1 new2)
(setcar (cddr rest) new2)))
(t
(byte-compile-log-lap " %s %s %s\t-->\t%s"
lap0 lap1 lap2 new1)
;; discard(0) = nop, remove
(setcdr (cdr rest) (cdddr rest)))))
(setq keep-going t))
;;
;; not goto-X-if-nil --> goto-X-if-non-nil
;; not goto-X-if-non-nil --> goto-X-if-nil
;;
;; it is wrong to do the same thing for the -else-pop variants.
;;
((and (eq 'byte-not (car lap0))
(memq (car lap1) '(byte-goto-if-nil byte-goto-if-not-nil)))
(let ((not-goto (if (eq (car lap1) 'byte-goto-if-nil)
'byte-goto-if-not-nil
'byte-goto-if-nil)))
(byte-compile-log-lap " not %s\t-->\t%s"
lap1 (cons not-goto (cdr lap1)))
(setcar lap1 not-goto)
(setcdr prev (cdr rest)) ; delete not
(setq keep-going t)))
;;
;; goto-X-if-nil goto-Y X: --> goto-Y-if-non-nil X:
;; goto-X-if-non-nil goto-Y X: --> goto-Y-if-nil X:
;;
;; it is wrong to do the same thing for the -else-pop variants.
;;
((and (memq (car lap0)
'(byte-goto-if-nil byte-goto-if-not-nil)) ; gotoX
(eq 'byte-goto (car lap1)) ; gotoY
(eq (cdr lap0) lap2)) ; TAG X
(let ((inverse (if (eq 'byte-goto-if-nil (car lap0))
'byte-goto-if-not-nil 'byte-goto-if-nil)))
(byte-compile-log-lap " %s %s %s\t-->\t%s %s"
lap0 lap1 lap2
(cons inverse (cdr lap1)) lap2)
(setcdr prev (cdr rest))
(setcar lap1 inverse)
(setq keep-going t)))
;;
;; const goto-if-* --> whatever
;;
((and (eq 'byte-constant (car lap0))
(memq (car lap1) byte-conditional-ops)
;; Must be an actual constant, not a closure variable.
(consp (cdr lap0)))
(cond ((if (memq (car lap1) '(byte-goto-if-nil
byte-goto-if-nil-else-pop))
(car (cdr lap0))
(not (car (cdr lap0))))
;; Branch not taken.
(byte-compile-log-lap " %s %s\t-->\t<deleted>"
lap0 lap1)
(setcdr prev (cddr rest))) ; delete both
((memq (car lap1) byte-goto-always-pop-ops)
;; Always-pop branch taken.
(byte-compile-log-lap " %s %s\t-->\t%s"
lap0 lap1
(cons 'byte-goto (cdr lap1)))
(setcdr prev (cdr rest)) ; delete const
(setcar lap1 'byte-goto))
(t ; -else-pop branch taken: keep const
(byte-compile-log-lap " %s %s\t-->\t%s %s"
lap0 lap1
lap0 (cons 'byte-goto (cdr lap1)))
(setcar lap1 'byte-goto)))
(setq keep-going t))
;;
;; varref-X varref-X --> varref-X dup
;; varref-X [dup ...] varref-X --> varref-X [dup ...] dup
;; stackref-X [dup ...] stackref-X+N --> stackref-X [dup ...] dup
;; We don't optimize the const-X variations on this here,
;; because that would inhibit some goto optimizations; we
;; optimize the const-X case after all other optimizations.
;;
((and (memq (car lap0) '(byte-varref byte-stack-ref))
(let ((tmp (cdr rest))
(tmp2 0))
(while (eq (car (car tmp)) 'byte-dup)
(setq tmp2 (1+ tmp2))
(setq tmp (cdr tmp)))
(and (eq (if (eq 'byte-stack-ref (car lap0))
(+ tmp2 1 (cdr lap0))
(cdr lap0))
(cdr (car tmp)))
(eq (car lap0) (car (car tmp)))
(progn
(when (memq byte-optimize-log '(t byte))
(let ((str "")
(tmp2 (cdr rest)))
(while (not (eq tmp tmp2))
(setq tmp2 (cdr tmp2))
(setq str (concat str " dup")))
(byte-compile-log-lap " %s%s %s\t-->\t%s%s dup"
lap0 str lap0 lap0 str)))
(setq keep-going t)
(setcar (car tmp) 'byte-dup)
(setcdr (car tmp) 0)
t)))))
;;
;; TAG1: TAG2: --> <deleted> TAG2:
;; (and other references to TAG1 are replaced with TAG2)
;;
((and (eq (car lap0) 'TAG)
(eq (car lap1) 'TAG))
(byte-compile-log-lap " adjacent tags %d and %d merged"
(nth 1 lap1) (nth 1 lap0))
(let ((tmp3 (cdr lap-head)))
(while (let ((tmp2 (rassq lap0 tmp3)))
(and tmp2
(progn
(setcdr tmp2 lap1)
(setq tmp3 (cdr (memq tmp2 tmp3)))
t))))
(setcdr prev (cdr rest))
(setq keep-going t)
;; replace references to tag in jump tables, if any
(dolist (table byte-compile-jump-tables)
(maphash #'(lambda (value tag)
(when (equal tag lap0)
(puthash value lap1 table)))
table))))
;;
;; unused-TAG: --> <deleted>
;;
((and (eq 'TAG (car lap0))
(not (rassq lap0 (cdr lap-head)))
;; make sure this tag isn't used in a jump-table
(cl-loop for table in byte-compile-jump-tables
when (member lap0 (hash-table-values table))
return nil finally return t))
(byte-compile-log-lap " unused tag %d removed" (nth 1 lap0))
(setcdr prev (cdr rest))
(setq keep-going t))
;;
;; goto ... --> goto <delete until TAG or end>
;; return ... --> return <delete until TAG or end>
;;
((and (memq (car lap0) '(byte-goto byte-return))
(not (memq (car lap1) '(TAG nil))))
(let ((i 0)
(tmp rest)
(opt-p (memq byte-optimize-log '(t byte)))
str deleted)
(while (and (setq tmp (cdr tmp))
(not (eq 'TAG (car (car tmp)))))
(if opt-p (setq deleted (cons (car tmp) deleted)
str (concat str " %s")
i (1+ i))))
(if opt-p
(let ((tagstr
(if (eq 'TAG (car (car tmp)))
(format "%d:" (car (cdr (car tmp))))
(or (car tmp) ""))))
(if (< i 6)
(apply 'byte-compile-log-lap-1
(concat " %s" str
" %s\t-->\t%s <deleted> %s")
lap0
(nconc (nreverse deleted)
(list tagstr lap0 tagstr)))
(byte-compile-log-lap
" %s <%d unreachable op%s> %s\t-->\t%s <deleted> %s"
lap0 i (if (= i 1) "" "s")
tagstr lap0 tagstr))))
(setcdr rest tmp)
(setq keep-going t)))
;;
;; <safe-op> unbind --> unbind <safe-op>
;; (this may enable other optimizations.)
;;
((and (eq 'byte-unbind (car lap1))
(memq (car lap0) byte-after-unbind-ops))
(byte-compile-log-lap " %s %s\t-->\t%s %s" lap0 lap1 lap1 lap0)
(setcar rest lap1)
(setcar (cdr rest) lap0)
(setq keep-going t))
;;
;; varbind-X unbind-N --> discard unbind-(N-1)
;; save-excursion unbind-N --> unbind-(N-1)
;; save-restriction unbind-N --> unbind-(N-1)
;; save-current-buffer unbind-N --> unbind-(N-1)
;;
((and (eq 'byte-unbind (car lap1))
(memq (car lap0) '(byte-varbind byte-save-excursion
byte-save-restriction
byte-save-current-buffer))
(< 0 (cdr lap1)))
(setcdr lap1 (1- (cdr lap1)))
(when (zerop (cdr lap1))
(setcdr rest (cddr rest)))
(if (eq (car lap0) 'byte-varbind)
(setcar rest (cons 'byte-discard 0))
(setcdr prev (cddr prev)))
(byte-compile-log-lap " %s %s\t-->\t%s %s"
lap0 (cons (car lap1) (1+ (cdr lap1)))
(if (eq (car lap0) 'byte-varbind)
(car rest)
(car (cdr rest)))
(if (and (/= 0 (cdr lap1))
(eq (car lap0) 'byte-varbind))
(car (cdr rest))
""))
(setq keep-going t))
;;
;; goto*-X ... X: goto-Y --> goto*-Y
;; goto-X ... X: return --> return
;;
((and (memq (car lap0) byte-goto-ops)
(let ((tmp (nth 1 (memq (cdr lap0) (cdr lap-head)))))
(and
(memq (car tmp) '(byte-goto byte-return))
(or (eq (car lap0) 'byte-goto)
(eq (car tmp) 'byte-goto))
(not (eq (cdr tmp) (cdr lap0)))
(progn
(byte-compile-log-lap " %s [%s]\t-->\t%s"
(car lap0) tmp
(if (eq (car tmp) 'byte-return)
tmp
(cons (car lap0) (cdr tmp))))
(when (eq (car tmp) 'byte-return)
(setcar lap0 'byte-return))
(setcdr lap0 (cdr tmp))
(setq keep-going t)
t)))))
;;
;; OP goto(X) Y: OP X: -> Y: OP X:
;;
((and (eq (car lap1) 'byte-goto)
(eq (car lap2) 'TAG)
(let ((lap3 (nth 3 rest)))
(and (eq (car lap0) (car lap3))
(eq (cdr lap0) (cdr lap3))
(eq (cdr lap1) (nth 4 rest)))))
(byte-compile-log-lap " %s %s %s %s %s\t-->\t%s %s %s"
lap0 lap1 lap2
(nth 3 rest) (nth 4 rest)
lap2 (nth 3 rest) (nth 4 rest))
(setcdr prev (cddr rest))
(setq keep-going t))
;;
;; NOEFFECT PRODUCER return --> PRODUCER return
;; where NOEFFECT lacks effects beyond stack change,
;; PRODUCER pushes a result without looking at the stack:
;; const, varref, point etc.
;;
((and (eq (car (nth 2 rest)) 'byte-return)
(memq (car lap1) producer-ops)
(or (memq (car lap0) '( byte-discard byte-discardN
byte-discardN-preserve-tos
byte-stack-set))
(memq (car lap0) side-effect-free)))
(setq keep-going t)
(setq add-depth 1)
(setcdr prev (cdr rest))
(byte-compile-log-lap " %s %s %s\t-->\t%s %s"
lap0 lap1 (nth 2 rest) lap1 (nth 2 rest)))
;;
;; (discardN-preserve-tos|dup) UNARY return --> UNARY return
;; where UNARY takes and produces a single value on the stack
;;
;; FIXME: ideally we should run this backwards, so that we could do
;; discardN-preserve-tos OP1...OPn return -> OP1..OPn return
;; but that would require a different approach.
;;
((and (eq (car (nth 2 rest)) 'byte-return)
(memq (car lap1) unary-ops)
(or (memq (car lap0) '(byte-discardN-preserve-tos byte-dup))
(and (eq (car lap0) 'byte-stack-set)
(eql (cdr lap0) 1))))
(setq keep-going t)
(setcdr prev (cdr rest)) ; eat lap0
(byte-compile-log-lap " %s %s %s\t-->\t%s %s"
lap0 lap1 (nth 2 rest) lap1 (nth 2 rest)))
;;
;; goto-*-else-pop X ... X: goto-if-* --> whatever
;; goto-*-else-pop X ... X: discard --> whatever
;;
((and (memq (car lap0) '(byte-goto-if-nil-else-pop
byte-goto-if-not-nil-else-pop))
(let ((tmp (cdr (memq (cdr lap0) (cdr lap-head)))))
(and
(memq (caar tmp)
(eval-when-compile
(cons 'byte-discard byte-conditional-ops)))
(not (eq lap0 (car tmp)))
(let ((tmp2 (car tmp))
(tmp3 (assq (car lap0)
'((byte-goto-if-nil-else-pop
byte-goto-if-nil)
(byte-goto-if-not-nil-else-pop
byte-goto-if-not-nil)))))
(if (memq (car tmp2) tmp3)
(progn (setcar lap0 (car tmp2))
(setcdr lap0 (cdr tmp2))
(byte-compile-log-lap
" %s-else-pop [%s]\t-->\t%s"
(car lap0) tmp2 lap0))
;; Get rid of the -else-pop's and jump one
;; step further.
(or (eq 'TAG (car (nth 1 tmp)))
(setcdr tmp (cons (byte-compile-make-tag)
(cdr tmp))))
(byte-compile-log-lap " %s [%s]\t-->\t%s <skip>"
(car lap0) tmp2 (nth 1 tmp3))
(setcar lap0 (nth 1 tmp3))
(setcdr lap0 (nth 1 tmp)))
(setq keep-going t)
t)))))
;;
;; const goto-X ... X: goto-if-* --> whatever
;; const goto-X ... X: discard --> whatever
;;
((and (eq (car lap0) 'byte-constant)
(eq (car lap1) 'byte-goto)
(let ((tmp (cdr (memq (cdr lap1) (cdr lap-head)))))
(and
(memq (caar tmp)
(eval-when-compile
(cons 'byte-discard byte-conditional-ops)))
(not (eq lap1 (car tmp)))
(let ((tmp2 (car tmp)))
(cond ((and (consp (cdr lap0))
(memq (car tmp2)
(if (null (car (cdr lap0)))
'(byte-goto-if-nil
byte-goto-if-nil-else-pop)
'(byte-goto-if-not-nil
byte-goto-if-not-nil-else-pop))))
(byte-compile-log-lap
" %s goto [%s]\t-->\t%s %s"
lap0 tmp2 lap0 tmp2)
(setcar lap1 (car tmp2))
(setcdr lap1 (cdr tmp2))
;; Let next step fix the (const,goto-if*) seq.
(setq keep-going t))
((or (consp (cdr lap0))
(eq (car tmp2) 'byte-discard))
;; Jump one step further
(byte-compile-log-lap
" %s goto [%s]\t-->\t<deleted> goto <skip>"
lap0 tmp2)
(or (eq 'TAG (car (nth 1 tmp)))
(setcdr tmp (cons (byte-compile-make-tag)
(cdr tmp))))
(setcdr lap1 (car (cdr tmp)))
(setcdr prev (cdr rest))
(setq keep-going t))
(t
(setq prev (cdr prev))))
t)))))
;;
;; X: varref-Y ... varset-Y goto-X -->
;; X: varref-Y Z: ... dup varset-Y goto-Z
;; (varset-X goto-BACK, BACK: varref-X --> copy the varref down.)
;; (This is so usual for while loops that it is worth handling).
;;
;; Here again, we could do it for stack-ref/stack-set, but
;; that's replacing a stack-ref-Y with a stack-ref-0, which
;; is a very minor improvement (if any), at the cost of
;; more stack use and more byte-code. Let's not do it.
;;
((and (eq (car lap1) 'byte-varset)
(eq (car lap2) 'byte-goto)
(not (memq (cdr lap2) rest)) ;Backwards jump
(let ((tmp (cdr (memq (cdr lap2) (cdr lap-head)))))
(and
(eq (car (car tmp)) 'byte-varref)
(eq (cdr (car tmp)) (cdr lap1))
(not (memq (car (cdr lap1)) byte-boolean-vars))
(let ((newtag (byte-compile-make-tag)))
(byte-compile-log-lap
" %s: %s ... %s %s\t-->\t%s: %s %s: ... %s %s %s"
(nth 1 (cdr lap2)) (car tmp)
lap1 lap2
(nth 1 (cdr lap2)) (car tmp)
(nth 1 newtag) 'byte-dup lap1
(cons 'byte-goto newtag)
)
(setcdr rest (cons (cons 'byte-dup 0) (cdr rest)))
(setcdr tmp (cons (setcdr lap2 newtag) (cdr tmp)))
(setq add-depth 1)
(setq keep-going t)
t)))))
;;
;; goto-X Y: ... X: goto-if*-Y --> goto-if-not-*-X+1 Y:
;; (This can pull the loop test to the end of the loop)
;;
((and (eq (car lap0) 'byte-goto)
(eq (car lap1) 'TAG)
(let ((tmp (cdr (memq (cdr lap0) (cdr lap-head)))))
(and
(eq lap1 (cdar tmp))
(memq (car (car tmp))
'( byte-goto byte-goto-if-nil byte-goto-if-not-nil
byte-goto-if-nil-else-pop))
(let ((newtag (byte-compile-make-tag)))
(byte-compile-log-lap
" %s %s ... %s %s\t-->\t%s ... %s"
lap0 lap1 (cdr lap0) (car tmp)
(cons (cdr (assq (car (car tmp))
'((byte-goto-if-nil
. byte-goto-if-not-nil)
(byte-goto-if-not-nil
. byte-goto-if-nil)
(byte-goto-if-nil-else-pop
. byte-goto-if-not-nil-else-pop)
(byte-goto-if-not-nil-else-pop
. byte-goto-if-nil-else-pop))))
newtag)
newtag)
(setcdr tmp (cons (setcdr lap0 newtag) (cdr tmp)))
(when (eq (car (car tmp)) 'byte-goto-if-nil-else-pop)
;; We can handle this case but not the
;; -if-not-nil case, because we won't know
;; which non-nil constant to push.
(setcdr rest
(cons (cons 'byte-constant
(byte-compile-get-constant nil))
(cdr rest))))
(setcar lap0 (nth 1 (memq (car (car tmp))
'(byte-goto-if-nil-else-pop
byte-goto-if-not-nil
byte-goto-if-nil
byte-goto-if-not-nil
byte-goto byte-goto))))
(setq keep-going t)
t)))))
;;
;; discardN-preserve-tos(X) discardN-preserve-tos(Y)
;; --> discardN-preserve-tos(X+Y)
;; where stack-set(1) is accepted as discardN-preserve-tos(1)
;;
((and (or (eq (car lap0) 'byte-discardN-preserve-tos)
(and (eq (car lap0) 'byte-stack-set)
(eql (cdr lap0) 1)))
(or (eq (car lap1) 'byte-discardN-preserve-tos)
(and (eq (car lap1) 'byte-stack-set)
(eql (cdr lap1) 1))))
(setq keep-going t)
(let ((new-op (cons 'byte-discardN-preserve-tos
;; This happens to work even when either
;; op is stack-set(1).
(+ (cdr lap0) (cdr lap1)))))
(byte-compile-log-lap " %s %s\t-->\t%s" lap0 lap1 new-op)
(setcar rest new-op)
(setcdr rest (cddr rest))))
;;
;; stack-set-M [discard/discardN ...] --> discardN-preserve-tos
;; stack-set-M [discard/discardN ...] --> discardN
;;
((and (eq (car lap0) 'byte-stack-set)
(memq (car lap1) '(byte-discard byte-discardN))
(let ((tmp2 (1- (cdr lap0)))
(tmp3 0)
(tmp (cdr rest)))
;; See if enough discard operations follow to expose or
;; destroy the value stored by the stack-set.
(while (memq (car (car tmp)) '(byte-discard byte-discardN))
(setq tmp3
(+ tmp3 (if (eq (car (car tmp)) 'byte-discard)
1
(cdr (car tmp)))))
(setq tmp (cdr tmp)))
(and
(>= tmp3 tmp2)
(progn
;; Do the optimization.
(setcdr prev (cdr rest))
(setcar lap1
(if (= tmp2 tmp3)
;; The value stored is the new TOS, so pop
;; one more value (to get rid of the old
;; value) using TOS-preserving discard.
'byte-discardN-preserve-tos
;; Otherwise, the value stored is lost,
;; so just use a normal discard.
'byte-discardN))
(setcdr lap1 (1+ tmp3))
(setcdr (cdr rest) tmp)
(byte-compile-log-lap
" %s [discard/discardN]...\t-->\t%s" lap0 lap1)
(setq keep-going t)
t
)))))
;;
;; discardN-preserve-tos return --> return
;; dup return --> return
;; stack-set(1) return --> return
;;
((and (eq (car lap1) 'byte-return)
(or (memq (car lap0) '(byte-discardN-preserve-tos byte-dup))
(and (eq (car lap0) 'byte-stack-set)
(= (cdr lap0) 1))))
(setq keep-going t)
;; The byte-code interpreter will pop the stack for us, so
;; we can just leave stuff on it.
(setcdr prev (cdr rest))
(byte-compile-log-lap " %s %s\t-->\t%s" lap0 lap1 lap1))
;;
;; stack-ref(X) discardN-preserve-tos(Y)
;; --> discard(Y) stack-ref(X-Y), X≥Y
;; discard(X) discardN-preserve-tos(Y-X-1), X<Y
;; where: stack-ref(0) = dup (works both ways)
;; discard(0) = no-op
;; discardN-preserve-tos(0) = no-op
;;
((and (memq (car lap0) '(byte-stack-ref byte-dup))
(or (eq (car lap1) 'byte-discardN-preserve-tos)
(and (eq (car lap1) 'byte-stack-set)
(eql (cdr lap1) 1)))
;; Don't apply if immediately preceding a `return',
;; since there are more effective rules for that case.
(not (eq (car lap2) 'byte-return)))
(let ((x (if (eq (car lap0) 'byte-dup) 0 (cdr lap0)))
(y (cdr lap1)))
(cl-assert (> y 0))
(cond
((>= x y) ; --> discard(Y) stack-ref(X-Y)
(let ((new0 (if (= y 1)
(cons 'byte-discard nil)
(cons 'byte-discardN y)))
(new1 (if (= x y)
(cons 'byte-dup nil)
(cons 'byte-stack-ref (- x y)))))
(byte-compile-log-lap " %s %s\t-->\t%s %s"
lap0 lap1 new0 new1)
(setcar rest new0)
(setcar (cdr rest) new1)))
((= x 0) ; --> discardN-preserve-tos(Y-1)
(setcdr prev (cdr rest)) ; eat lap0
(if (> y 1)
(let ((new (cons 'byte-discardN-preserve-tos (- y 1))))
(byte-compile-log-lap " %s %s\t-->\t%s"
lap0 lap1 new)
(setcar (cdr prev) new))
(byte-compile-log-lap " %s %s\t-->\t<deleted>" lap0 lap1)
(setcdr prev (cddr prev)))) ; eat lap1
((= y (+ x 1)) ; --> discard(X)
(setcdr prev (cdr rest)) ; eat lap0
(let ((new (if (= x 1)
(cons 'byte-discard nil)
(cons 'byte-discardN x))))
(byte-compile-log-lap " %s %s\t-->\t%s" lap0 lap1 new)
(setcar (cdr prev) new)))
(t ; --> discard(X) discardN-preserve-tos(Y-X-1)
(let ((new0 (if (= x 1)
(cons 'byte-discard nil)
(cons 'byte-discardN x)))
(new1 (cons 'byte-discardN-preserve-tos (- y x 1))))
(byte-compile-log-lap " %s %s\t-->\t%s %s"
lap0 lap1 new0 new1)
(setcar rest new0)
(setcar (cdr rest) new1)))))
(setq keep-going t))
;;
;; goto-X ... X: discard ==> discard goto-Y ... X: discard Y:
;;
((and (eq (car lap0) 'byte-goto)
(let ((tmp (cdr (memq (cdr lap0) (cdr lap-head)))))
(and
tmp
(or (memq (caar tmp) '(byte-discard byte-discardN))
;; Make sure we don't hoist a discardN-preserve-tos
;; that really should be merged or deleted instead.
(and (or (eq (caar tmp) 'byte-discardN-preserve-tos)
(and (eq (caar tmp) 'byte-stack-set)
(eql (cdar tmp) 1)))
(let ((next (cadr tmp)))
(not (or (memq (car next)
'(byte-discardN-preserve-tos
byte-return))
(and (eq (car next) 'byte-stack-set)
(eql (cdr next) 1)))))))
(progn
(byte-compile-log-lap
" goto-X .. X: \t-->\t%s goto-X.. X: %s Y:"
(car tmp) (car tmp))
(setq keep-going t)
(let* ((newtag (byte-compile-make-tag))
;; Make a copy, since we sometimes modify
;; insts in-place!
(newdiscard (cons (caar tmp) (cdar tmp)))
(newjmp (cons (car lap0) newtag)))
;; Push new tag after the discard.
(push newtag (cdr tmp))
(setcar rest newdiscard)
(push newjmp (cdr rest)))
t)))))
;;
;; UNARY discardN-preserve-tos --> discardN-preserve-tos UNARY
;; where UNARY takes and produces a single value on the stack
;;
((and (memq (car lap0) unary-ops)
(or (eq (car lap1) 'byte-discardN-preserve-tos)
(and (eq (car lap1) 'byte-stack-set)
(eql (cdr lap1) 1)))
;; unless followed by return (which will eat the discard)
(not (eq (car lap2) 'byte-return)))
(setq keep-going t)
(byte-compile-log-lap " %s %s\t-->\t%s %s" lap0 lap1 lap1 lap0)
(setcar rest lap1)
(setcar (cdr rest) lap0))
;;
;; PRODUCER discardN-preserve-tos(X) --> discard(X) PRODUCER
;; where PRODUCER pushes a result without looking at the stack:
;; const, varref, point etc.
;;
((and (memq (car lap0) producer-ops)
(or (eq (car lap1) 'byte-discardN-preserve-tos)
(and (eq (car lap1) 'byte-stack-set)
(eql (cdr lap1) 1)))
;; unless followed by return (which will eat the discard)
(not (eq (car lap2) 'byte-return)))
(setq keep-going t)
(let ((newdiscard (if (eql (cdr lap1) 1)
(cons 'byte-discard nil)
(cons 'byte-discardN (cdr lap1)))))
(byte-compile-log-lap
" %s %s\t-->\t%s %s" lap0 lap1 newdiscard lap0)
(setf (car rest) newdiscard)
(setf (cadr rest) lap0)))
(t
;; If no rule matched, advance and try again.
(setq prev (cdr prev))))))))
;; Cleanup stage:
;; Rebuild byte-compile-constants / byte-compile-variables.
;; Simple optimizations that would inhibit other optimizations if they
;; were done in the optimizing loop, and optimizations which there is no
;; need to do more than once.
(setq byte-compile-constants nil
byte-compile-variables nil)
(byte-compile-log-lap " ---- final pass")
(let ((prev lap-head))
(while (cdr prev)
(let* ((rest (cdr prev))
(lap0 (car rest))
(lap1 (nth 1 rest)))
;; FIXME: Would there ever be a `byte-constant2' op here?
(if (memq (car lap0) byte-constref-ops)
(if (memq (car lap0) '(byte-constant byte-constant2))
(unless (memq (cdr lap0) byte-compile-constants)
(setq byte-compile-constants (cons (cdr lap0)
byte-compile-constants)))
(unless (memq (cdr lap0) byte-compile-variables)
(setq byte-compile-variables (cons (cdr lap0)
byte-compile-variables)))))
(cond
;;
;; const-C varset-X const-C --> const-C dup varset-X
;; const-C varbind-X const-C --> const-C dup varbind-X
;;
((and (eq (car lap0) 'byte-constant)
(eq (car (nth 2 rest)) 'byte-constant)
(eq (cdr lap0) (cdr (nth 2 rest)))
(memq (car lap1) '(byte-varbind byte-varset)))
(byte-compile-log-lap " %s %s %s\t-->\t%s dup %s"
lap0 lap1 lap0 lap0 lap1)
(setcar (cdr (cdr rest)) (cons (car lap1) (cdr lap1)))
(setcar (cdr rest) (cons 'byte-dup 0))
(setq add-depth 1))
;;
;; const-X [dup/const-X ...] --> const-X [dup ...] dup
;; varref-X [dup/varref-X ...] --> varref-X [dup ...] dup
;;
((memq (car lap0) '(byte-constant byte-varref))
(let ((tmp rest)
(tmp2 nil))
(while (progn
(while (eq 'byte-dup (car (car (setq tmp (cdr tmp))))))
(and (eq (cdr lap0) (cdr (car tmp)))
(eq (car lap0) (car (car tmp)))))
(setcar tmp (cons 'byte-dup 0))
(setq tmp2 t))
(if tmp2
(byte-compile-log-lap
" %s [dup/%s]...\t-->\t%s dup..." lap0 lap0 lap0)
(setq prev (cdr prev)))))
;;
;; unbind-N unbind-M --> unbind-(N+M)
;;
((and (eq 'byte-unbind (car lap0))
(eq 'byte-unbind (car lap1)))
(byte-compile-log-lap " %s %s\t-->\t%s" lap0 lap1
(cons 'byte-unbind
(+ (cdr lap0) (cdr lap1))))
(setcdr prev (cdr rest))
(setcdr lap1 (+ (cdr lap1) (cdr lap0))))
;;
;; discard/discardN/discardN-preserve-tos-X discard/discardN-Y -->
;; discardN-(X+Y)
;;
((and (memq (car lap0)
'(byte-discard byte-discardN
byte-discardN-preserve-tos))
(memq (car lap1) '(byte-discard byte-discardN)))
(setcdr prev (cdr rest))
(byte-compile-log-lap
" %s %s\t-->\t(discardN %s)"
lap0 lap1
(+ (if (eq (car lap0) 'byte-discard) 1 (cdr lap0))
(if (eq (car lap1) 'byte-discard) 1 (cdr lap1))))
(setcdr lap1 (+ (if (eq (car lap0) 'byte-discard) 1 (cdr lap0))
(if (eq (car lap1) 'byte-discard) 1 (cdr lap1))))
(setcar lap1 'byte-discardN))
(t
(setq prev (cdr prev)))))))
(setq byte-compile-maxdepth (+ byte-compile-maxdepth add-depth))
(cdr lap-head)))