diff options
| author | Tom Tromey | 2018-01-20 12:25:26 -0700 |
|---|---|---|
| committer | Tom Tromey | 2018-01-22 22:11:26 -0700 |
| commit | 916094a84f0ab31be31aa6c3632f14176b4e882a (patch) | |
| tree | 5d14b3b849b7b63f19577bd45bbbd85cdba0b702 | |
| parent | a6b4b9b4af5405b62cbd59f5ce23ca0fe0027ac7 (diff) | |
| download | emacs-feature/byte-unwind-protect.tar.gz emacs-feature/byte-unwind-protect.zip | |
Add new bytecodes for unwind-protectfeature/byte-unwind-protect
* lisp/emacs-lisp/byte-opt.el (disassemble-offset): Handle
byte-pushunwindprotect.
* lisp/emacs-lisp/bytecomp.el (byte-pushunwindprotect)
(byte-endunwindprotect): New bytecodes.
(byte-goto-ops): Add byte-pushunwindprotect.
(byte-compile-unwind-protect): Emit new bytecodes.
(byte-compile-goto): Handle byte-pushunwindprotect.
* lisp/emacs-lisp/cconv.el (cconv-convert): Don't special-case
unwind-protect when byte-compile--use-old-handlers.
(cconv-analyze-form): Likewise.
* src/bytecode.c (Bpushunwindprotect, Bendunwindprotect): New bytecodes.
(exec_byte_code): Implement new bytecodes.
* test/src/bytecode-tests.el: New file.
| -rw-r--r-- | lisp/emacs-lisp/byte-opt.el | 3 | ||||
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 66 | ||||
| -rw-r--r-- | lisp/emacs-lisp/cconv.el | 7 | ||||
| -rw-r--r-- | src/bytecode.c | 43 | ||||
| -rw-r--r-- | test/src/bytecode-tests.el | 58 |
5 files changed, 152 insertions, 25 deletions
diff --git a/lisp/emacs-lisp/byte-opt.el b/lisp/emacs-lisp/byte-opt.el index e5e5f4ee590..5292deda6ea 100644 --- a/lisp/emacs-lisp/byte-opt.el +++ b/lisp/emacs-lisp/byte-opt.el | |||
| @@ -1324,7 +1324,8 @@ | |||
| 1324 | (<= bytedecomp-op byte-goto-if-not-nil-else-pop)) | 1324 | (<= bytedecomp-op byte-goto-if-not-nil-else-pop)) |
| 1325 | (memq bytedecomp-op (eval-when-compile | 1325 | (memq bytedecomp-op (eval-when-compile |
| 1326 | (list byte-stack-set2 byte-pushcatch | 1326 | (list byte-stack-set2 byte-pushcatch |
| 1327 | byte-pushconditioncase)))) | 1327 | byte-pushconditioncase |
| 1328 | byte-pushunwindprotect)))) | ||
| 1328 | ;; Offset in next 2 bytes. | 1329 | ;; Offset in next 2 bytes. |
| 1329 | (setq bytedecomp-ptr (1+ bytedecomp-ptr)) | 1330 | (setq bytedecomp-ptr (1+ bytedecomp-ptr)) |
| 1330 | (+ (aref bytes bytedecomp-ptr) | 1331 | (+ (aref bytes bytedecomp-ptr) |
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index a64c88c4f0d..5e04a620f33 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el | |||
| @@ -603,8 +603,12 @@ Each element is (INDEX . VALUE)") | |||
| 603 | (byte-defop 48 0 byte-pophandler) | 603 | (byte-defop 48 0 byte-pophandler) |
| 604 | (byte-defop 50 -1 byte-pushcatch) | 604 | (byte-defop 50 -1 byte-pushcatch) |
| 605 | (byte-defop 49 -1 byte-pushconditioncase) | 605 | (byte-defop 49 -1 byte-pushconditioncase) |
| 606 | ;; New (in Emacs 27.1) bytecode for efficient handling of | ||
| 607 | ;; unwind-protect. | ||
| 608 | (byte-defop 51 0 byte-pushunwindprotect) | ||
| 609 | (byte-defop 52 -1 byte-endunwindprotect) | ||
| 606 | 610 | ||
| 607 | ;; unused: 51-55 | 611 | ;; unused: 53-55 |
| 608 | 612 | ||
| 609 | (byte-defop 56 -1 byte-nth) | 613 | (byte-defop 56 -1 byte-nth) |
| 610 | (byte-defop 57 0 byte-symbolp) | 614 | (byte-defop 57 0 byte-symbolp) |
| @@ -781,7 +785,8 @@ the value maps to, if any.") | |||
| 781 | (defconst byte-goto-ops '(byte-goto byte-goto-if-nil byte-goto-if-not-nil | 785 | (defconst byte-goto-ops '(byte-goto byte-goto-if-nil byte-goto-if-not-nil |
| 782 | byte-goto-if-nil-else-pop | 786 | byte-goto-if-nil-else-pop |
| 783 | byte-goto-if-not-nil-else-pop | 787 | byte-goto-if-not-nil-else-pop |
| 784 | byte-pushcatch byte-pushconditioncase) | 788 | byte-pushcatch byte-pushconditioncase |
| 789 | byte-pushunwindprotect) | ||
| 785 | "List of byte-codes whose offset is a pc.") | 790 | "List of byte-codes whose offset is a pc.") |
| 786 | 791 | ||
| 787 | (defconst byte-goto-always-pop-ops '(byte-goto-if-nil byte-goto-if-not-nil)) | 792 | (defconst byte-goto-always-pop-ops '(byte-goto-if-nil byte-goto-if-not-nil)) |
| @@ -4459,18 +4464,33 @@ binding slots have been popped." | |||
| 4459 | (byte-compile-out 'byte-catch 0))) | 4464 | (byte-compile-out 'byte-catch 0))) |
| 4460 | 4465 | ||
| 4461 | (defun byte-compile-unwind-protect (form) | 4466 | (defun byte-compile-unwind-protect (form) |
| 4462 | (pcase (cddr form) | 4467 | (if (not byte-compile--use-old-handlers) |
| 4463 | (`(:fun-body ,f) | 4468 | (let ((except-tag (byte-compile-make-tag))) |
| 4464 | (byte-compile-form | 4469 | ;; If the goto is called, we'll have 2 extra items on the |
| 4465 | (if byte-compile--use-old-handlers `(list (list 'funcall ,f)) f))) | 4470 | ;; stack. |
| 4466 | (handlers | 4471 | (byte-compile-goto 'byte-pushunwindprotect except-tag) |
| 4467 | (if byte-compile--use-old-handlers | 4472 | (byte-compile-form (cadr form) nil) |
| 4468 | (byte-compile-push-constant | 4473 | (byte-compile-out 'byte-pophandler) |
| 4469 | (byte-compile-top-level-body handlers t)) | 4474 | ;; The value of the body is on the stack; now push a flag so |
| 4470 | (byte-compile-form `#'(lambda () ,@handlers))))) | 4475 | ;; that the coming endunwindprotect instruction knows what to |
| 4471 | (byte-compile-out 'byte-unwind-protect 0) | 4476 | ;; do. |
| 4472 | (byte-compile-form-do-effect (car (cdr form))) | 4477 | (byte-compile-push-constant nil) |
| 4473 | (byte-compile-out 'byte-unbind 1)) | 4478 | ;; The unwind forms. |
| 4479 | (byte-compile-out-tag except-tag) | ||
| 4480 | (byte-compile-body (cddr form) t) | ||
| 4481 | (byte-compile-out 'byte-endunwindprotect)) | ||
| 4482 | (pcase (cddr form) | ||
| 4483 | (`(:fun-body ,f) | ||
| 4484 | (byte-compile-form | ||
| 4485 | (if byte-compile--use-old-handlers `(list (list 'funcall ,f)) f))) | ||
| 4486 | (handlers | ||
| 4487 | (if byte-compile--use-old-handlers | ||
| 4488 | (byte-compile-push-constant | ||
| 4489 | (byte-compile-top-level-body handlers t)) | ||
| 4490 | (byte-compile-form `#'(lambda () ,@handlers))))) | ||
| 4491 | (byte-compile-out 'byte-unwind-protect 0) | ||
| 4492 | (byte-compile-form-do-effect (car (cdr form))) | ||
| 4493 | (byte-compile-out 'byte-unbind 1))) | ||
| 4474 | 4494 | ||
| 4475 | (defun byte-compile-condition-case (form) | 4495 | (defun byte-compile-condition-case (form) |
| 4476 | (if byte-compile--use-old-handlers | 4496 | (if byte-compile--use-old-handlers |
| @@ -4810,11 +4830,19 @@ binding slots have been popped." | |||
| 4810 | 4830 | ||
| 4811 | (defun byte-compile-goto (opcode tag) | 4831 | (defun byte-compile-goto (opcode tag) |
| 4812 | (push (cons opcode tag) byte-compile-output) | 4832 | (push (cons opcode tag) byte-compile-output) |
| 4813 | (setcdr (cdr tag) (if (memq opcode byte-goto-always-pop-ops) | 4833 | (setcdr (cdr tag) |
| 4814 | (1- byte-compile-depth) | 4834 | (cond |
| 4815 | byte-compile-depth)) | 4835 | ((memq opcode byte-goto-always-pop-ops) |
| 4816 | (setq byte-compile-depth (and (not (eq opcode 'byte-goto)) | 4836 | (1- byte-compile-depth)) |
| 4817 | (1- byte-compile-depth)))) | 4837 | ((eq opcode 'byte-pushunwindprotect) |
| 4838 | (+ 2 byte-compile-depth)) | ||
| 4839 | (t byte-compile-depth))) | ||
| 4840 | (setq byte-compile-depth | ||
| 4841 | (cond | ||
| 4842 | ((eq opcode 'byte-goto) nil) | ||
| 4843 | ((eq opcode 'byte-pushunwindprotect) | ||
| 4844 | byte-compile-depth) | ||
| 4845 | (t (1- byte-compile-depth))))) | ||
| 4818 | 4846 | ||
| 4819 | (defun byte-compile-stack-adjustment (op operand) | 4847 | (defun byte-compile-stack-adjustment (op operand) |
| 4820 | "Return the amount by which an operation adjusts the stack. | 4848 | "Return the amount by which an operation adjusts the stack. |
diff --git a/lisp/emacs-lisp/cconv.el b/lisp/emacs-lisp/cconv.el index 02fe794467b..925292483fd 100644 --- a/lisp/emacs-lisp/cconv.el +++ b/lisp/emacs-lisp/cconv.el | |||
| @@ -87,7 +87,6 @@ | |||
| 87 | ;; command-history). | 87 | ;; command-history). |
| 88 | ;; - canonize code in macro-expand so we don't have to handle (let (var) body) | 88 | ;; - canonize code in macro-expand so we don't have to handle (let (var) body) |
| 89 | ;; and other oddities. | 89 | ;; and other oddities. |
| 90 | ;; - new byte codes for unwind-protect so that closures aren't needed at all. | ||
| 91 | ;; - a reference to a var that is known statically to always hold a constant | 90 | ;; - a reference to a var that is known statically to always hold a constant |
| 92 | ;; should be turned into a byte-constant rather than a byte-stack-ref. | 91 | ;; should be turned into a byte-constant rather than a byte-stack-ref. |
| 93 | ;; Hmm... right, that's called constant propagation and could be done here, | 92 | ;; Hmm... right, that's called constant propagation and could be done here, |
| @@ -487,7 +486,8 @@ places where they originally did not directly appear." | |||
| 487 | handlers)))) | 486 | handlers)))) |
| 488 | 487 | ||
| 489 | (`(,(and head (or (and `catch (guard byte-compile--use-old-handlers)) | 488 | (`(,(and head (or (and `catch (guard byte-compile--use-old-handlers)) |
| 490 | `unwind-protect)) | 489 | (and `unwind-protect |
| 490 | (guard byte-compile--use-old-handlers)))) | ||
| 491 | ,form . ,body) | 491 | ,form . ,body) |
| 492 | `(,head ,(cconv-convert form env extend) | 492 | `(,head ,(cconv-convert form env extend) |
| 493 | :fun-body ,(cconv--convert-function () body env form))) | 493 | :fun-body ,(cconv--convert-function () body env form))) |
| @@ -728,9 +728,8 @@ and updates the data stored in ENV." | |||
| 728 | (if var (cconv--analyze-use (cons (list var) (cdr varstruct)) | 728 | (if var (cconv--analyze-use (cons (list var) (cdr varstruct)) |
| 729 | form "variable")))) | 729 | form "variable")))) |
| 730 | 730 | ||
| 731 | ;; FIXME: The bytecode for unwind-protect forces us to wrap the unwind. | ||
| 732 | (`(,(or (and `catch (guard byte-compile--use-old-handlers)) | 731 | (`(,(or (and `catch (guard byte-compile--use-old-handlers)) |
| 733 | `unwind-protect) | 732 | (and `unwind-protect (guard byte-compile--use-old-handlers))) |
| 734 | ,form . ,body) | 733 | ,form . ,body) |
| 735 | (cconv-analyze-form form env) | 734 | (cconv-analyze-form form env) |
| 736 | (cconv--analyze-function () body env form)) | 735 | (cconv--analyze-function () body env form)) |
diff --git a/src/bytecode.c b/src/bytecode.c index 55b193ffb2f..62ba2ca69d0 100644 --- a/src/bytecode.c +++ b/src/bytecode.c | |||
| @@ -132,6 +132,8 @@ DEFINE (Bunbind7, 057) \ | |||
| 132 | DEFINE (Bpophandler, 060) \ | 132 | DEFINE (Bpophandler, 060) \ |
| 133 | DEFINE (Bpushconditioncase, 061) \ | 133 | DEFINE (Bpushconditioncase, 061) \ |
| 134 | DEFINE (Bpushcatch, 062) \ | 134 | DEFINE (Bpushcatch, 062) \ |
| 135 | DEFINE (Bpushunwindprotect, 063) \ | ||
| 136 | DEFINE (Bendunwindprotect, 064) \ | ||
| 135 | \ | 137 | \ |
| 136 | DEFINE (Bnth, 070) \ | 138 | DEFINE (Bnth, 070) \ |
| 137 | DEFINE (Bsymbolp, 071) \ | 139 | DEFINE (Bsymbolp, 071) \ |
| @@ -770,6 +772,45 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, | |||
| 770 | NEXT; | 772 | NEXT; |
| 771 | } | 773 | } |
| 772 | 774 | ||
| 775 | CASE (Bpushunwindprotect): /* New in 27.1. */ | ||
| 776 | { | ||
| 777 | struct handler *c = push_handler (Qt, CATCHER_ALL); | ||
| 778 | c->bytecode_dest = FETCH2; | ||
| 779 | c->bytecode_top = top; | ||
| 780 | |||
| 781 | if (sys_setjmp (c->jmp)) | ||
| 782 | { | ||
| 783 | struct handler *c = handlerlist; | ||
| 784 | top = c->bytecode_top; | ||
| 785 | op = c->bytecode_dest; | ||
| 786 | handlerlist = c->next; | ||
| 787 | /* Push the exception value, plus a flag indicating | ||
| 788 | that re-throwing is necessary. This will be used | ||
| 789 | by Bendunwindprotect. */ | ||
| 790 | PUSH (c->val); | ||
| 791 | PUSH (Qt); | ||
| 792 | goto op_branch; | ||
| 793 | } | ||
| 794 | |||
| 795 | NEXT; | ||
| 796 | } | ||
| 797 | CASE (Bendunwindprotect): /* New in 27.1. */ | ||
| 798 | { | ||
| 799 | Lisp_Object flag = POP; | ||
| 800 | |||
| 801 | if (!NILP (flag)) | ||
| 802 | { | ||
| 803 | Lisp_Object err = POP; | ||
| 804 | |||
| 805 | if (EQ (XCAR (err), Qsignal)) | ||
| 806 | Fsignal (XCAR (XCDR (err)), XCDR (XCDR (err))); | ||
| 807 | else | ||
| 808 | Fthrow (XCAR (XCDR (err)), XCDR (XCDR (err))); | ||
| 809 | } | ||
| 810 | |||
| 811 | NEXT; | ||
| 812 | } | ||
| 813 | |||
| 773 | CASE (Bpushcatch): /* New in 24.4. */ | 814 | CASE (Bpushcatch): /* New in 24.4. */ |
| 774 | type = CATCHER; | 815 | type = CATCHER; |
| 775 | goto pushhandler; | 816 | goto pushhandler; |
| @@ -798,7 +839,7 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, | |||
| 798 | handlerlist = handlerlist->next; | 839 | handlerlist = handlerlist->next; |
| 799 | NEXT; | 840 | NEXT; |
| 800 | 841 | ||
| 801 | CASE (Bunwind_protect): /* FIXME: avoid closure for lexbind. */ | 842 | CASE (Bunwind_protect): /* Obsolete since 27.1. */ |
| 802 | { | 843 | { |
| 803 | Lisp_Object handler = POP; | 844 | Lisp_Object handler = POP; |
| 804 | /* Support for a function here is new in 24.4. */ | 845 | /* Support for a function here is new in 24.4. */ |
diff --git a/test/src/bytecode-tests.el b/test/src/bytecode-tests.el new file mode 100644 index 00000000000..51cbfe7576c --- /dev/null +++ b/test/src/bytecode-tests.el | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | ;;; bytecode-tests.el --- unit tests for src/bytecode.c -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2018 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | ;; it under the terms of the GNU General Public License as published by | ||
| 9 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 10 | ;; (at your option) any later version. | ||
| 11 | |||
| 12 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;; GNU General Public License for more details. | ||
| 16 | |||
| 17 | ;; You should have received a copy of the GNU General Public License | ||
| 18 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | ;;; Commentary: | ||
| 21 | |||
| 22 | ;; Unit tests for src/bytecode.c. | ||
| 23 | |||
| 24 | ;;; Code: | ||
| 25 | |||
| 26 | (require 'ert) | ||
| 27 | |||
| 28 | (defun bctest-throw-something () | ||
| 29 | (throw 'something 23)) | ||
| 30 | |||
| 31 | (defun bctest-signal () | ||
| 32 | (signal 'error 23)) | ||
| 33 | |||
| 34 | (ert-deftest bctest-unwind-protect-signal () | ||
| 35 | (let ((val nil)) | ||
| 36 | (should-error (unwind-protect | ||
| 37 | (bctest-signal) | ||
| 38 | (setq val t))) | ||
| 39 | (should val))) | ||
| 40 | |||
| 41 | (ert-deftest bctest-unwind-protect-throw () | ||
| 42 | (let ((val nil)) | ||
| 43 | (should (eq (catch 'something | ||
| 44 | (unwind-protect | ||
| 45 | (bctest-throw-something) | ||
| 46 | (setq val t)) | ||
| 47 | 'fail) | ||
| 48 | 23)) | ||
| 49 | (should val))) | ||
| 50 | |||
| 51 | (ert-deftest bctest-unwind-protect-fallthrough () | ||
| 52 | (let ((val nil)) | ||
| 53 | (unwind-protect | ||
| 54 | (setq val 'x) | ||
| 55 | (setq val t)) | ||
| 56 | (should val))) | ||
| 57 | |||
| 58 | ;;; bytecode-tests.el ends here | ||