diff options
author | Tom Tromey <tom@tromey.com> | 2018-01-20 12:25:26 -0700 |
---|---|---|
committer | Tom Tromey <tom@tromey.com> | 2018-01-22 22:11:26 -0700 |
commit | 916094a84f0ab31be31aa6c3632f14176b4e882a (patch) | |
tree | 5d14b3b849b7b63f19577bd45bbbd85cdba0b702 | |
parent | a6b4b9b4af5405b62cbd59f5ce23ca0fe0027ac7 (diff) | |
download | emacs-feature/byte-unwind-protect.tar.gz |
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 @@ (<= bytedecomp-op byte-goto-if-not-nil-else-pop)) (memq bytedecomp-op (eval-when-compile (list byte-stack-set2 byte-pushcatch - byte-pushconditioncase)))) + byte-pushconditioncase + byte-pushunwindprotect)))) ;; Offset in next 2 bytes. (setq bytedecomp-ptr (1+ bytedecomp-ptr)) (+ (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)") (byte-defop 48 0 byte-pophandler) (byte-defop 50 -1 byte-pushcatch) (byte-defop 49 -1 byte-pushconditioncase) +;; New (in Emacs 27.1) bytecode for efficient handling of +;; unwind-protect. +(byte-defop 51 0 byte-pushunwindprotect) +(byte-defop 52 -1 byte-endunwindprotect) -;; unused: 51-55 +;; unused: 53-55 (byte-defop 56 -1 byte-nth) (byte-defop 57 0 byte-symbolp) @@ -781,7 +785,8 @@ the value maps to, if any.") (defconst byte-goto-ops '(byte-goto byte-goto-if-nil byte-goto-if-not-nil byte-goto-if-nil-else-pop byte-goto-if-not-nil-else-pop - byte-pushcatch byte-pushconditioncase) + byte-pushcatch byte-pushconditioncase + byte-pushunwindprotect) "List of byte-codes whose offset is a pc.") (defconst byte-goto-always-pop-ops '(byte-goto-if-nil byte-goto-if-not-nil)) @@ -4459,18 +4464,33 @@ binding slots have been popped." (byte-compile-out 'byte-catch 0))) (defun byte-compile-unwind-protect (form) - (pcase (cddr form) - (`(:fun-body ,f) - (byte-compile-form - (if byte-compile--use-old-handlers `(list (list 'funcall ,f)) f))) - (handlers - (if byte-compile--use-old-handlers - (byte-compile-push-constant - (byte-compile-top-level-body handlers t)) - (byte-compile-form `#'(lambda () ,@handlers))))) - (byte-compile-out 'byte-unwind-protect 0) - (byte-compile-form-do-effect (car (cdr form))) - (byte-compile-out 'byte-unbind 1)) + (if (not byte-compile--use-old-handlers) + (let ((except-tag (byte-compile-make-tag))) + ;; If the goto is called, we'll have 2 extra items on the + ;; stack. + (byte-compile-goto 'byte-pushunwindprotect except-tag) + (byte-compile-form (cadr form) nil) + (byte-compile-out 'byte-pophandler) + ;; The value of the body is on the stack; now push a flag so + ;; that the coming endunwindprotect instruction knows what to + ;; do. + (byte-compile-push-constant nil) + ;; The unwind forms. + (byte-compile-out-tag except-tag) + (byte-compile-body (cddr form) t) + (byte-compile-out 'byte-endunwindprotect)) + (pcase (cddr form) + (`(:fun-body ,f) + (byte-compile-form + (if byte-compile--use-old-handlers `(list (list 'funcall ,f)) f))) + (handlers + (if byte-compile--use-old-handlers + (byte-compile-push-constant + (byte-compile-top-level-body handlers t)) + (byte-compile-form `#'(lambda () ,@handlers))))) + (byte-compile-out 'byte-unwind-protect 0) + (byte-compile-form-do-effect (car (cdr form))) + (byte-compile-out 'byte-unbind 1))) (defun byte-compile-condition-case (form) (if byte-compile--use-old-handlers @@ -4810,11 +4830,19 @@ binding slots have been popped." (defun byte-compile-goto (opcode tag) (push (cons opcode tag) byte-compile-output) - (setcdr (cdr tag) (if (memq opcode byte-goto-always-pop-ops) - (1- byte-compile-depth) - byte-compile-depth)) - (setq byte-compile-depth (and (not (eq opcode 'byte-goto)) - (1- byte-compile-depth)))) + (setcdr (cdr tag) + (cond + ((memq opcode byte-goto-always-pop-ops) + (1- byte-compile-depth)) + ((eq opcode 'byte-pushunwindprotect) + (+ 2 byte-compile-depth)) + (t byte-compile-depth))) + (setq byte-compile-depth + (cond + ((eq opcode 'byte-goto) nil) + ((eq opcode 'byte-pushunwindprotect) + byte-compile-depth) + (t (1- byte-compile-depth))))) (defun byte-compile-stack-adjustment (op operand) "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 @@ ;; command-history). ;; - canonize code in macro-expand so we don't have to handle (let (var) body) ;; and other oddities. -;; - new byte codes for unwind-protect so that closures aren't needed at all. ;; - a reference to a var that is known statically to always hold a constant ;; should be turned into a byte-constant rather than a byte-stack-ref. ;; Hmm... right, that's called constant propagation and could be done here, @@ -487,7 +486,8 @@ places where they originally did not directly appear." handlers)))) (`(,(and head (or (and `catch (guard byte-compile--use-old-handlers)) - `unwind-protect)) + (and `unwind-protect + (guard byte-compile--use-old-handlers)))) ,form . ,body) `(,head ,(cconv-convert form env extend) :fun-body ,(cconv--convert-function () body env form))) @@ -728,9 +728,8 @@ and updates the data stored in ENV." (if var (cconv--analyze-use (cons (list var) (cdr varstruct)) form "variable")))) - ;; FIXME: The bytecode for unwind-protect forces us to wrap the unwind. (`(,(or (and `catch (guard byte-compile--use-old-handlers)) - `unwind-protect) + (and `unwind-protect (guard byte-compile--use-old-handlers))) ,form . ,body) (cconv-analyze-form form env) (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) \ DEFINE (Bpophandler, 060) \ DEFINE (Bpushconditioncase, 061) \ DEFINE (Bpushcatch, 062) \ +DEFINE (Bpushunwindprotect, 063) \ +DEFINE (Bendunwindprotect, 064) \ \ DEFINE (Bnth, 070) \ DEFINE (Bsymbolp, 071) \ @@ -770,6 +772,45 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, NEXT; } + CASE (Bpushunwindprotect): /* New in 27.1. */ + { + struct handler *c = push_handler (Qt, CATCHER_ALL); + c->bytecode_dest = FETCH2; + c->bytecode_top = top; + + if (sys_setjmp (c->jmp)) + { + struct handler *c = handlerlist; + top = c->bytecode_top; + op = c->bytecode_dest; + handlerlist = c->next; + /* Push the exception value, plus a flag indicating + that re-throwing is necessary. This will be used + by Bendunwindprotect. */ + PUSH (c->val); + PUSH (Qt); + goto op_branch; + } + + NEXT; + } + CASE (Bendunwindprotect): /* New in 27.1. */ + { + Lisp_Object flag = POP; + + if (!NILP (flag)) + { + Lisp_Object err = POP; + + if (EQ (XCAR (err), Qsignal)) + Fsignal (XCAR (XCDR (err)), XCDR (XCDR (err))); + else + Fthrow (XCAR (XCDR (err)), XCDR (XCDR (err))); + } + + NEXT; + } + CASE (Bpushcatch): /* New in 24.4. */ type = CATCHER; goto pushhandler; @@ -798,7 +839,7 @@ exec_byte_code (Lisp_Object bytestr, Lisp_Object vector, Lisp_Object maxdepth, handlerlist = handlerlist->next; NEXT; - CASE (Bunwind_protect): /* FIXME: avoid closure for lexbind. */ + CASE (Bunwind_protect): /* Obsolete since 27.1. */ { Lisp_Object handler = POP; /* 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 @@ +;;; bytecode-tests.el --- unit tests for src/bytecode.c -*- lexical-binding: t; -*- + +;; Copyright (C) 2018 Free Software Foundation, Inc. + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: + +;; Unit tests for src/bytecode.c. + +;;; Code: + +(require 'ert) + +(defun bctest-throw-something () + (throw 'something 23)) + +(defun bctest-signal () + (signal 'error 23)) + +(ert-deftest bctest-unwind-protect-signal () + (let ((val nil)) + (should-error (unwind-protect + (bctest-signal) + (setq val t))) + (should val))) + +(ert-deftest bctest-unwind-protect-throw () + (let ((val nil)) + (should (eq (catch 'something + (unwind-protect + (bctest-throw-something) + (setq val t)) + 'fail) + 23)) + (should val))) + +(ert-deftest bctest-unwind-protect-fallthrough () + (let ((val nil)) + (unwind-protect + (setq val 'x) + (setq val t)) + (should val))) + +;;; bytecode-tests.el ends here |