diff options
| author | Mattias EngdegÄrd | 2019-06-07 17:04:10 +0200 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2019-06-19 11:22:21 +0200 |
| commit | d3a7f3e6cd0124e62ed2b5ffc87eee57fee39a9a (patch) | |
| tree | 865b52b1a0a33438b35a766de7198cea8aea3488 | |
| parent | 14a81524c27ab54850e0fda736e4ee0c92e447b5 (diff) | |
| download | emacs-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.el | 342 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-tests.el | 15 |
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. |
| 4143 | The condition for each clause is of the form (TEST VAR VALUE). | 4141 | Return (TAIL VAR TEST CASES), where: |
| 4144 | VAR is a variable. | 4142 | TAIL is the remaining part of CLAUSES after the switch, including |
| 4145 | TEST and VAR are the same throughout all conditions. | 4143 | any default clause, |
| 4146 | VALUE satisfies `macroexp-const-p'. | 4144 | VAR is the variable being switched on, |
| 4147 | 4145 | TEST is the equality test (`eq', `eql' or `equal'), | |
| 4148 | Return 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. |
| 339 | Each element will be executed by interpreter and with | 352 | Each element will be executed by interpreter and with |
| 340 | bytecompiled code, and their results compared.") | 353 | bytecompiled code, and their results compared.") |