aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias EngdegÄrd2019-06-07 17:04:10 +0200
committerMattias EngdegÄrd2019-06-19 11:22:21 +0200
commitd3a7f3e6cd0124e62ed2b5ffc87eee57fee39a9a (patch)
tree865b52b1a0a33438b35a766de7198cea8aea3488
parent14a81524c27ab54850e0fda736e4ee0c92e447b5 (diff)
downloademacs-d3a7f3e6cd0124e62ed2b5ffc87eee57fee39a9a.tar.gz
emacs-d3a7f3e6cd0124e62ed2b5ffc87eee57fee39a9a.zip
Compile any subsequence of `cond' clauses to switch (bug#36139)
A single `cond' form can how be compiled to any number of switch ops, optionally interspersed with non-switch conditions. Previously, switch ops would only be used for whole `cond' forms containing no other tests. * lisp/emacs-lisp/bytecomp.el (byte-compile--cond-vars): Rename from `byte-compile-cond-vars'. (byte-compile--default-val): Remove. (byte-compile--cond-switch-prefix): Replace `byte-compile-cond-jump-table-info'; now also returns trailing non-switch clauses. (byte-compile-cond-jump-table): New arguments; no longer compiles the default case. (byte-compile-cond): Look for and compile switches at any place in the list of clauses. * test/lisp/emacs-lisp/bytecomp-tests.el (byte-opt-testsuite-arith-data): Add test expression.
-rw-r--r--lisp/emacs-lisp/bytecomp.el342
-rw-r--r--test/lisp/emacs-lisp/bytecomp-tests.el15
2 files changed, 176 insertions, 181 deletions
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 3a23543f6a7..c01c74a4569 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -4122,7 +4122,7 @@ that suppresses all warnings during execution of BODY."
4122 (byte-compile-out-tag donetag)))) 4122 (byte-compile-out-tag donetag))))
4123 (setq byte-compile--for-effect nil)) 4123 (setq byte-compile--for-effect nil))
4124 4124
4125(defun byte-compile-cond-vars (obj1 obj2) 4125(defun byte-compile--cond-vars (obj1 obj2)
4126 ;; We make sure that of OBJ1 and OBJ2, one of them is a symbol, 4126 ;; We make sure that of OBJ1 and OBJ2, one of them is a symbol,
4127 ;; and the other is a constant expression whose value can be 4127 ;; and the other is a constant expression whose value can be
4128 ;; compared with `eq' (with `macroexp-const-p'). 4128 ;; compared with `eq' (with `macroexp-const-p').
@@ -4130,193 +4130,175 @@ that suppresses all warnings during execution of BODY."
4130 (and (symbolp obj1) (macroexp-const-p obj2) (cons obj1 (eval obj2))) 4130 (and (symbolp obj1) (macroexp-const-p obj2) (cons obj1 (eval obj2)))
4131 (and (symbolp obj2) (macroexp-const-p obj1) (cons obj2 (eval obj1))))) 4131 (and (symbolp obj2) (macroexp-const-p obj1) (cons obj2 (eval obj1)))))
4132 4132
4133(defconst byte-compile--default-val (cons nil nil) "A unique object.")
4134
4135(defun byte-compile--common-test (test-1 test-2) 4133(defun byte-compile--common-test (test-1 test-2)
4136 "Most specific common test of `eq', `eql' and `equal'" 4134 "Most specific common test of `eq', `eql' and `equal'"
4137 (cond ((or (eq test-1 'equal) (eq test-2 'equal)) 'equal) 4135 (cond ((or (eq test-1 'equal) (eq test-2 'equal)) 'equal)
4138 ((or (eq test-1 'eql) (eq test-2 'eql)) 'eql) 4136 ((or (eq test-1 'eql) (eq test-2 'eql)) 'eql)
4139 (t 'eq))) 4137 (t 'eq)))
4140 4138
4141(defun byte-compile-cond-jump-table-info (clauses) 4139(defun byte-compile--cond-switch-prefix (clauses)
4142 "If CLAUSES is a `cond' form where: 4140 "Find a switch corresponding to a prefix of CLAUSES, or nil if none.
4143The condition for each clause is of the form (TEST VAR VALUE). 4141Return (TAIL VAR TEST CASES), where:
4144VAR is a variable. 4142 TAIL is the remaining part of CLAUSES after the switch, including
4145TEST and VAR are the same throughout all conditions. 4143 any default clause,
4146VALUE satisfies `macroexp-const-p'. 4144 VAR is the variable being switched on,
4147 4145 TEST is the equality test (`eq', `eql' or `equal'),
4148Return a list of the form ((TEST . VAR) ((VALUES BODY) ...))" 4146 CASES is a list of (VALUES . BODY) where VALUES is a list of values
4149 (let ((cases '()) 4147 corresponding to BODY (always non-empty)."
4150 (ok t) 4148 (let ((cases nil) ; Reversed list of (VALUES BODY).
4151 (all-keys nil) 4149 (keys nil) ; Switch keys seen so far.
4152 (prev-test 'eq) 4150 (switch-var nil)
4153 prev-var) 4151 (switch-test 'eq))
4154 (and (catch 'break 4152 (while (pcase (car clauses)
4155 (dolist (clause (cdr clauses) ok) 4153 (`((,fn ,expr1 ,expr2) . ,body)
4156 (let* ((condition (car clause)) 4154 (let* ((vars (byte-compile--cond-vars expr1 expr2))
4157 (test (car-safe condition)) 4155 (var (car vars))
4158 (vars (when (consp condition) 4156 (value (cdr vars)))
4159 (byte-compile-cond-vars (cadr condition) (cl-caddr condition)))) 4157 (and var (or (eq var switch-var) (not switch-var))
4160 (obj1 (car-safe vars)) 4158 (cond
4161 (obj2 (cdr-safe vars)) 4159 ((memq fn '(eq eql equal))
4162 (body (cdr-safe clause))) 4160 (setq switch-var var)
4163 (unless prev-var 4161 (setq switch-test
4164 (setq prev-var obj1)) 4162 (byte-compile--common-test switch-test fn))
4165 (cond 4163 (unless (member value keys)
4166 ((and obj1 (memq test '(eq eql equal)) 4164 (push value keys)
4167 (eq obj1 prev-var)) 4165 (push (cons (list value) (or body '(t))) cases))
4168 (setq prev-test (byte-compile--common-test prev-test test)) 4166 t)
4169 ;; Discard values already tested for. 4167 ((and (memq fn '(memq memql member))
4170 (unless (member obj2 all-keys) 4168 (listp value)
4171 (push obj2 all-keys) 4169 ;; Require a non-empty body, since the member
4172 (push (list (list obj2) body) cases))) 4170 ;; function value depends on the switch
4173 4171 ;; argument.
4174 ((and obj1 (memq test '(memq memql member)) 4172 body)
4175 (eq obj1 prev-var) 4173 (setq switch-var var)
4176 (listp obj2) 4174 (setq switch-test
4177 ;; Require a non-empty body, since the member function 4175 (byte-compile--common-test
4178 ;; value depends on the switch argument. 4176 switch-test (cdr (assq fn '((memq . eq)
4179 body) 4177 (memql . eql)
4180 (setq prev-test 4178 (member . equal))))))
4181 (byte-compile--common-test 4179 (let ((vals nil))
4182 prev-test (cdr (assq test '((memq . eq) 4180 (dolist (elem value)
4183 (memql . eql) 4181 (unless (funcall fn elem keys)
4184 (member . equal)))))) 4182 (push elem vals)))
4185 (let ((vals nil)) 4183 (when vals
4186 ;; Discard values already tested for. 4184 (setq keys (append vals keys))
4187 (dolist (elem obj2) 4185 (push (cons (nreverse vals) body) cases)))
4188 (unless (funcall test elem all-keys) 4186 t))))))
4189 (push elem vals))) 4187 (setq clauses (cdr clauses)))
4190 (when vals 4188 ;; Assume that a single switch is cheaper than two or more discrete
4191 (setq all-keys (append vals all-keys)) 4189 ;; compare clauses. This could be tuned, possibly taking into
4192 (push (list vals body) cases)))) 4190 ;; account the total number of values involved.
4193 4191 (and (> (length cases) 1)
4194 ((and (macroexp-const-p condition) condition) 4192 (list clauses switch-var switch-test (nreverse cases)))))
4195 (push (list byte-compile--default-val 4193
4196 (or body `(,condition))) 4194(defun byte-compile-cond-jump-table (switch donetag)
4197 cases) 4195 "Generate code for SWITCH, ending at DONETAG."
4198 (throw 'break t)) 4196 (let* ((var (car switch))
4199 (t (setq ok nil) 4197 (test (nth 1 switch))
4200 (throw 'break nil)))))) 4198 (cases (nth 2 switch))
4201 (list (cons prev-test prev-var) (nreverse cases))))) 4199 jump-table test-objects body tag default-tag)
4202 4200 ;; TODO: Once :linear-search is implemented for `make-hash-table'
4203(defun byte-compile-cond-jump-table (clauses) 4201 ;; set it to `t' for cond forms with a small number of cases.
4204 (let* ((table-info (byte-compile-cond-jump-table-info clauses)) 4202 (let ((nvalues (apply #'+ (mapcar (lambda (case) (length (car case)))
4205 (test (caar table-info)) 4203 cases))))
4206 (var (cdar table-info)) 4204 (setq jump-table (make-hash-table
4207 (cases (cadr table-info)) 4205 :test test
4208 jump-table test-objects body tag donetag default-tag default-case) 4206 :purecopy t
4209 (when (and cases (not (= (length cases) 1))) 4207 :size nvalues)))
4210 ;; TODO: Once :linear-search is implemented for `make-hash-table' 4208 (setq default-tag (byte-compile-make-tag))
4211 ;; set it to `t' for cond forms with a small number of cases. 4209 ;; The structure of byte-switch code:
4212 (let ((nvalues (apply #'+ (mapcar (lambda (case) (length (car case))) 4210 ;;
4213 cases)))) 4211 ;; varref var
4214 (setq jump-table (make-hash-table 4212 ;; constant #s(hash-table purecopy t data (val1 (TAG1) val2 (TAG2)))
4215 :test test 4213 ;; switch
4216 :purecopy t 4214 ;; goto DEFAULT-TAG
4217 :size (if (assq byte-compile--default-val cases) 4215 ;; TAG1
4218 (1- nvalues) 4216 ;; <clause body>
4219 nvalues)))) 4217 ;; goto DONETAG
4220 (setq default-tag (byte-compile-make-tag)) 4218 ;; TAG2
4221 (setq donetag (byte-compile-make-tag)) 4219 ;; <clause body>
4222 ;; The structure of byte-switch code: 4220 ;; goto DONETAG
4223 ;; 4221 ;; DEFAULT-TAG
4224 ;; varref var 4222 ;; <body for remaining (non-switch) clauses>
4225 ;; constant #s(hash-table purecopy t data (val1 (TAG1) val2 (TAG2))) 4223 ;; DONETAG
4226 ;; switch 4224
4227 ;; goto DEFAULT-TAG 4225 (byte-compile-variable-ref var)
4228 ;; TAG1 4226 (byte-compile-push-constant jump-table)
4229 ;; <clause body> 4227 (byte-compile-out 'byte-switch)
4230 ;; goto DONETAG 4228
4231 ;; TAG2 4229 ;; When the opcode argument is `byte-goto', `byte-compile-goto' sets
4232 ;; <clause body> 4230 ;; `byte-compile-depth' to `nil'. However, we need `byte-compile-depth'
4233 ;; goto DONETAG 4231 ;; to be non-nil for generating tags for all cases. Since
4234 ;; DEFAULT-TAG 4232 ;; `byte-compile-depth' will increase by at most 1 after compiling
4235 ;; <body for `t' clause, if any (else `constant nil')> 4233 ;; all of the clause (which is further enforced by cl-assert below)
4236 ;; DONETAG 4234 ;; it should be safe to preserve its value.
4237 4235 (let ((byte-compile-depth byte-compile-depth))
4238 (byte-compile-variable-ref var) 4236 (byte-compile-goto 'byte-goto default-tag))
4239 (byte-compile-push-constant jump-table) 4237
4240 (byte-compile-out 'byte-switch) 4238 (dolist (case cases)
4241 4239 (setq tag (byte-compile-make-tag)
4242 ;; When the opcode argument is `byte-goto', `byte-compile-goto' sets 4240 test-objects (car case)
4243 ;; `byte-compile-depth' to `nil'. However, we need `byte-compile-depth' 4241 body (cdr case))
4244 ;; to be non-nil for generating tags for all cases. Since 4242 (byte-compile-out-tag tag)
4245 ;; `byte-compile-depth' will increase by at most 1 after compiling 4243 (dolist (value test-objects)
4246 ;; all of the clause (which is further enforced by cl-assert below) 4244 (puthash value tag jump-table))
4247 ;; it should be safe to preserve its value. 4245
4248 (let ((byte-compile-depth byte-compile-depth)) 4246 (let ((byte-compile-depth byte-compile-depth)
4249 (byte-compile-goto 'byte-goto default-tag)) 4247 (init-depth byte-compile-depth))
4250 4248 ;; Since `byte-compile-body' might increase `byte-compile-depth'
4251 (let ((default-match (assq byte-compile--default-val cases))) 4249 ;; by 1, not preserving its value will cause it to potentially
4252 (when default-match 4250 ;; increase by one for every clause body compiled, causing
4253 (setq default-case (cadr default-match) 4251 ;; depth/tag conflicts or violating asserts down the road.
4254 cases (butlast cases)))) 4252 ;; To make sure `byte-compile-body' itself doesn't violate this,
4255 4253 ;; we use `cl-assert'.
4256 (dolist (case cases) 4254 (byte-compile-body body byte-compile--for-effect)
4257 (setq tag (byte-compile-make-tag) 4255 (cl-assert (or (= byte-compile-depth init-depth)
4258 test-objects (nth 0 case) 4256 (= byte-compile-depth (1+ init-depth))))
4259 body (nth 1 case)) 4257 (byte-compile-goto 'byte-goto donetag)
4260 (byte-compile-out-tag tag) 4258 (setcdr (cdr donetag) nil)))
4261 (dolist (value test-objects) 4259
4262 (puthash value tag jump-table)) 4260 (byte-compile-out-tag default-tag)
4263 4261 (push jump-table byte-compile-jump-tables)))
4264 (let ((byte-compile-depth byte-compile-depth)
4265 (init-depth byte-compile-depth))
4266 ;; Since `byte-compile-body' might increase `byte-compile-depth'
4267 ;; by 1, not preserving its value will cause it to potentially
4268 ;; increase by one for every clause body compiled, causing
4269 ;; depth/tag conflicts or violating asserts down the road.
4270 ;; To make sure `byte-compile-body' itself doesn't violate this,
4271 ;; we use `cl-assert'.
4272 (if (null body)
4273 (byte-compile-form t byte-compile--for-effect)
4274 (byte-compile-body body byte-compile--for-effect))
4275 (cl-assert (or (= byte-compile-depth init-depth)
4276 (= byte-compile-depth (1+ init-depth))))
4277 (byte-compile-goto 'byte-goto donetag)
4278 (setcdr (cdr donetag) nil)))
4279
4280 (byte-compile-out-tag default-tag)
4281 (if default-case
4282 (byte-compile-body-do-effect default-case)
4283 (byte-compile-constant nil))
4284 (byte-compile-out-tag donetag)
4285 (push jump-table byte-compile-jump-tables))))
4286 4262
4287(defun byte-compile-cond (clauses) 4263(defun byte-compile-cond (clauses)
4288 (or (and byte-compile-cond-use-jump-table 4264 (let ((donetag (byte-compile-make-tag))
4289 (byte-compile-cond-jump-table clauses)) 4265 nexttag clause)
4290 (let ((donetag (byte-compile-make-tag)) 4266 (setq clauses (cdr clauses))
4291 nexttag clause) 4267 (while clauses
4292 (while (setq clauses (cdr clauses)) 4268 (let ((switch-prefix (and byte-compile-cond-use-jump-table
4293 (setq clause (car clauses)) 4269 (byte-compile--cond-switch-prefix clauses))))
4294 (cond ((or (eq (car clause) t) 4270 (if switch-prefix
4295 (and (eq (car-safe (car clause)) 'quote) 4271 (progn
4296 (car-safe (cdr-safe (car clause))))) 4272 (byte-compile-cond-jump-table (cdr switch-prefix) donetag)
4297 ;; Unconditional clause 4273 (setq clauses (car switch-prefix)))
4298 (setq clause (cons t clause) 4274 (setq clause (car clauses))
4299 clauses nil)) 4275 (cond ((or (eq (car clause) t)
4300 ((cdr clauses) 4276 (and (eq (car-safe (car clause)) 'quote)
4301 (byte-compile-form (car clause)) 4277 (car-safe (cdr-safe (car clause)))))
4302 (if (null (cdr clause)) 4278 ;; Unconditional clause
4303 ;; First clause is a singleton. 4279 (setq clause (cons t clause)
4304 (byte-compile-goto-if t byte-compile--for-effect donetag) 4280 clauses nil))
4305 (setq nexttag (byte-compile-make-tag)) 4281 ((cdr clauses)
4306 (byte-compile-goto 'byte-goto-if-nil nexttag) 4282 (byte-compile-form (car clause))
4307 (byte-compile-maybe-guarded (car clause) 4283 (if (null (cdr clause))
4308 (byte-compile-body (cdr clause) byte-compile--for-effect)) 4284 ;; First clause is a singleton.
4309 (byte-compile-goto 'byte-goto donetag) 4285 (byte-compile-goto-if t byte-compile--for-effect donetag)
4310 (byte-compile-out-tag nexttag))))) 4286 (setq nexttag (byte-compile-make-tag))
4311 ;; Last clause 4287 (byte-compile-goto 'byte-goto-if-nil nexttag)
4312 (let ((guard (car clause))) 4288 (byte-compile-maybe-guarded (car clause)
4313 (and (cdr clause) (not (eq guard t)) 4289 (byte-compile-body (cdr clause) byte-compile--for-effect))
4314 (progn (byte-compile-form guard) 4290 (byte-compile-goto 'byte-goto donetag)
4315 (byte-compile-goto-if nil byte-compile--for-effect donetag) 4291 (byte-compile-out-tag nexttag))))
4316 (setq clause (cdr clause)))) 4292 (setq clauses (cdr clauses)))))
4317 (byte-compile-maybe-guarded guard 4293 ;; Last clause
4318 (byte-compile-body-do-effect clause))) 4294 (let ((guard (car clause)))
4319 (byte-compile-out-tag donetag)))) 4295 (and (cdr clause) (not (eq guard t))
4296 (progn (byte-compile-form guard)
4297 (byte-compile-goto-if nil byte-compile--for-effect donetag)
4298 (setq clause (cdr clause))))
4299 (byte-compile-maybe-guarded guard
4300 (byte-compile-body-do-effect clause)))
4301 (byte-compile-out-tag donetag)))
4320 4302
4321(defun byte-compile-and (form) 4303(defun byte-compile-and (form)
4322 (let ((failtag (byte-compile-make-tag)) 4304 (let ((failtag (byte-compile-make-tag))
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el
index 0f18a34578d..5bd36898702 100644
--- a/test/lisp/emacs-lisp/bytecomp-tests.el
+++ b/test/lisp/emacs-lisp/bytecomp-tests.el
@@ -334,7 +334,20 @@
334 ((memql x '(9 0.5 1.5 q)) 66) 334 ((memql x '(9 0.5 1.5 q)) 66)
335 (t 99))) 335 (t 99)))
336 '(a b c d (d) (a . b) "X" 0.5 1.5 3.14 9 9.0)) 336 '(a b c d (d) (a . b) "X" 0.5 1.5 3.14 9 9.0))
337 ) 337 ;; Multi-switch cond form
338 (mapcar (lambda (p) (let ((x (car p)) (y (cadr p)))
339 (cond ((consp x) 11)
340 ((eq x 'a) 22)
341 ((memql x '(b 7 a -3)) 33)
342 ((equal y "a") 44)
343 ((memq y '(c d e)) 55)
344 ((booleanp x) 66)
345 ((eq x 'q) 77)
346 ((memq x '(r s)) 88)
347 ((eq x 't) 99)
348 (t 999))))
349 '((a c) (b c) (7 c) (-3 c) (nil nil) (t c) (q c) (r c) (s c)
350 (t c) (x "a") (x "c") (x c) (x d) (x e))))
338 "List of expression for test. 351 "List of expression for test.
339Each element will be executed by interpreter and with 352Each element will be executed by interpreter and with
340bytecompiled code, and their results compared.") 353bytecompiled code, and their results compared.")