emacs/lisp/emacs-lisp/disass.el
Stefan Monnier f2bccae22b Use a dedicated type to represent interpreted-function values
Change `function` so that when evaluating #'(lambda ...)
we return an object of type `interpreted-function` rather than
a list starting with one of `lambda` or `closure`.
The new type reuses the existing PVEC_CLOSURE (nee PVEC_COMPILED)
tag and tries to align the corresponding elements:

- the arglist, the docstring, and the interactive-form go in the
  same slots as for byte-code functions.
- the body of the function goes in the slot used for the bytecode string.
- the lexical context goes in the slot used for the constants of
  bytecoded functions.

The first point above means that `help-function-arglist`,
`documentation`, and `interactive-form`s don't need to
distinguish interpreted and bytecode functions any more.

Main benefits of the change:

- We can now reliably distinguish a list from a function value.
- `cl-defmethod` can dispatch on `interactive-function` and `closure`.
  Dispatch on `function` also works now for interpreted functions but still
  won't work for functions represented as lists or as symbols, of course.
- Function values are now self-evaluating.  That was alrready the case
  when byte-compiled, but not when interpreted since
  (eval '(closure ...)) signals a void-function error.
  That also avoids false-positive warnings about "don't quote your lambdas"
  when doing things like `(mapcar ',func ...)`.

* src/eval.c (Fmake_interpreted_closure): New function.
(Ffunction): Use it and change calling convention of
`Vinternal_make_interpreted_closure_function`.
(FUNCTIONP, Fcommandp, eval_sub, funcall_general, funcall_lambda)
(Ffunc_arity, lambda_arity): Simplify.
(funcall_lambda): Adjust to new representation.
(syms_of_eval): `defsubr` the new function.  Remove definition of `Qclosure`.

* lisp/emacs-lisp/cconv.el (cconv-make-interpreted-closure):
Change calling convention and use `make-interpreted-closure`.

* src/data.c (Fcl_type_of): Distinguish `byte-code-function`s from
`interpreted-function`s.
(Fclosurep, finterpreted_function_p): New functions.
(Fbyte_code_function_p): Don't be confused by `interpreted-function`s.
(Finteractive_form, Fcommand_modes): Simplify.
(syms_of_data): Define new type symbols and `defsubr` the two
new functions.

* lisp/emacs-lisp/cl-print.el (cl-print-object) <interpreted-function>:
New method.

* lisp/emacs-lisp/oclosure.el (oclosure): Refine the parent
to be `closure`.
(oclosure--fix-type, oclosure-type): Simplify.
(oclosure--copy, oclosure--get, oclosure--set): Adjust to
new representation.

* src/callint.c (Fcall_interactively): Adjust to new representation.

* src/lread.c (bytecode_from_rev_list):

* lisp/simple.el (function-documentation):
* lisp/help.el (help-function-arglist): Remove the old `closure` case
and adjust the byte-code case so it handles `interpreted-function`s.

* lisp/emacs-lisp/cl-preloaded.el (closure): New type.
(byte-code-function): Add it as a parent.
(interpreted-function): Adjust parent (the type itself was already
added earlier by accident).

* lisp/emacs-lisp/bytecomp.el (byte-compile--reify-function): Adjust to
new representation.
(byte-compile): Use `interpreted-function-p`.

* lisp/emacs-lisp/byte-opt.el (byte-compile-inline-expand): Adjust to
new representation.
(side-effect-free-fns): Add `interpreted-function-p` and `closurep`.

* src/profiler.c (trace_hash, ffunction_equal): Simplify.
* lisp/profiler.el (profiler-function-equal): Simplify.

* lisp/emacs-lisp/nadvice.el (advice--interactive-form-1):
Use `interpreted-function-p`; adjust to new representation; and take
advantage of the fact that function values are now self-evaluating.

* lisp/emacs-lisp/lisp-mode.el (closure):
Remove `lisp-indent-function` property.

* lisp/emacs-lisp/disass.el (disassemble-internal): Adjust to
new representation.
* lisp/emacs-lisp/edebug.el (edebug--strip-instrumentation):
Use `interpreted-function-p`.
* lisp/emacs-lisp/comp-common.el (comp-known-type-specifiers):
Add `closurep` and `interpreted-function-p`.

* test/lisp/help-fns-tests.el (help-fns-test-lisp-defun): Adjust to
more precise type info in `describe-function`.
* test/lisp/erc/resources/erc-d/erc-d-tests.el (erc-d--render-entries):
Use `interpreted-function-p`.
* test/lisp/emacs-lisp/macroexp-resources/vk.el (vk-f4, vk-f5):
Don't hardcode function values.

* doc/lispref/functions.texi (Anonymous Functions): Don't suggest that
function values are lists.  Reword "self-quoting" to reflect the
fact that #' doesn't return the exact same object.  Update examples
with the new shape of the return value.

* doc/lispref/variables.texi (Lexical Binding):
* doc/lispref/lists.texi (Rearrangement):
* doc/lispref/control.texi (Handling Errors): Update examples to reflect
new representation of function values.
2024-04-28 11:58:12 -04:00

316 lines
12 KiB
EmacsLisp

;;; disass.el --- disassembler for compiled Emacs Lisp code -*- lexical-binding:t -*-
;; Copyright (C) 1986, 1991, 2002-2024 Free Software Foundation, Inc.
;; Author: Doug Cutting <doug@csli.stanford.edu>
;; Jamie Zawinski <jwz@lucid.com>
;; Maintainer: emacs-devel@gnu.org
;; Keywords: internal
;; 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:
;; The single entry point, `disassemble', disassembles a code object generated
;; by the Emacs Lisp byte-compiler. This doesn't invert the compilation
;; operation, not by a long shot, but it's useful for debugging.
;;
;; Original version by Doug Cutting (doug@csli.stanford.edu)
;; Substantially modified by Jamie Zawinski <jwz@lucid.com> for
;; the new lapcode-based byte compiler.
;;; Code:
(require 'macroexp)
(require 'cl-lib)
;; The variable byte-code-vector is defined by the new bytecomp.el.
;; The function byte-decompile-lapcode is defined in byte-opt.el.
;; Since we don't use byte-decompile-lapcode, let's try not loading byte-opt.
(require 'byte-compile "bytecomp")
(declare-function comp-c-func-name "comp.el")
(defvar disassemble-column-1-indent 8 "*")
(defvar disassemble-column-2-indent 10 "*")
(defvar disassemble-recursive-indent 3 "*")
;;;###autoload
(defun disassemble (object &optional buffer indent interactive-p)
"Print disassembled code for OBJECT in (optional) BUFFER.
OBJECT can be a symbol defined as a function, or a function itself
\(a lambda expression or a byte-code-function object).
If OBJECT is not already compiled, we compile it, but do not
redefine OBJECT if it is a symbol."
(interactive
(let* ((fn (function-called-at-point))
(def (and fn (symbol-name fn))))
(list (intern (completing-read (format-prompt "Disassemble function" fn)
obarray 'fboundp t nil nil def))
nil 0 t)))
(let ((lb lexical-binding))
(if (and (consp object) (not (functionp object)))
(setq object `(lambda () ,object)))
(or indent (setq indent 0)) ;Default indent to zero
(save-excursion
(if (or interactive-p (null buffer))
(with-output-to-temp-buffer "*Disassemble*"
(set-buffer standard-output)
(let ((lexical-binding lb))
(disassemble-internal object indent (not interactive-p))))
(set-buffer buffer)
(let ((lexical-binding lb))
(disassemble-internal object indent nil)))))
nil)
(declare-function native-comp-unit-file "data.c")
(declare-function subr-native-comp-unit "data.c")
(cl-defun disassemble-internal (obj indent interactive-p)
(let ((macro 'nil)
(name (when (symbolp obj)
(prog1 obj
(setq obj (indirect-function obj)))))
args)
(setq obj (autoload-do-load obj name))
(if (subrp obj)
(if (and (fboundp 'subr-native-elisp-p)
(subr-native-elisp-p obj))
(progn
(require 'comp)
(let ((eln (native-comp-unit-file (subr-native-comp-unit obj))))
(if (file-exists-p eln)
(call-process "objdump" nil (current-buffer) t "-S" eln)
(error "Missing eln file for #<subr %s>" name)))
(goto-char (point-min))
(re-search-forward (concat "^.*<_?"
(regexp-quote
(comp-c-func-name
(subr-name obj) "F" t))
">:"))
(beginning-of-line)
(delete-region (point-min) (point))
(when (re-search-forward "^.*<.*>:" nil t 2)
(delete-region (match-beginning 0) (point-max)))
(asm-mode)
(setq buffer-read-only t)
(cl-return-from disassemble-internal))
(error "Can't disassemble #<subr %s>" name)))
(if (eq (car-safe obj) 'macro) ;Handle macros.
(setq macro t
obj (cdr obj)))
(if (eq (car-safe obj) 'byte-code)
(setq obj `(lambda () ,obj)))
(when (consp obj)
(unless (functionp obj) (error "Not a function"))
(if (assq 'byte-code obj)
nil
(if interactive-p (message (if name
"Compiling %s's definition..."
"Compiling definition...")
name))
(setq obj (byte-compile obj))
(if interactive-p (message "Done compiling. Disassembling..."))))
(cond ((consp obj)
(setq args (help-function-arglist obj)) ;save arg list
(setq obj (cdr obj)) ;throw lambda away
(setq obj (cdr obj)))
((closurep obj)
(setq args (help-function-arglist obj)))
(t (error "Compilation failed")))
(if (zerop indent) ; not a nested function
(progn
(indent-to indent)
(insert (format "byte code%s%s%s:\n"
(if (or macro name) " for" "")
(if macro " macro" "")
(if name (format " %s" name) "")))))
(let ((doc (if (consp obj)
(and (stringp (car obj)) (car obj))
;; Use documentation to get lazy-loaded doc string
(documentation obj t))))
(if (and doc (stringp doc))
(progn (and (consp obj) (setq obj (cdr obj)))
(indent-to indent)
(princ " doc: " (current-buffer))
(if (string-match "\n" doc)
(setq doc (concat (substring doc 0 (match-beginning 0))
" ...")))
(insert doc "\n"))))
(indent-to indent)
(insert " args: ")
(prin1 args (current-buffer))
(insert "\n")
(let ((interactive (interactive-form obj)))
(if interactive
(progn
(setq interactive (nth 1 interactive))
(if (eq (car-safe (car-safe obj)) 'interactive)
(setq obj (cdr obj)))
(indent-to indent)
(insert " interactive: ")
(if (eq (car-safe interactive) 'byte-code)
(progn
(insert "\n")
(disassemble-1 interactive
(+ indent disassemble-recursive-indent)))
(let ((print-escape-newlines t))
(prin1 interactive (current-buffer))))
(insert "\n"))))
(cond ((and (consp obj) (assq 'byte-code obj))
(disassemble-1 (assq 'byte-code obj) indent))
((byte-code-function-p obj)
(disassemble-1 obj indent))
(t
(insert "Uncompiled body: ")
(let ((print-escape-newlines t))
(prin1 (macroexp-progn (if (interpreted-function-p obj)
(aref obj 1)
obj))
(current-buffer))))))
(if interactive-p
(message "")))
(defun disassemble-1 (obj indent)
"Print the byte-code call OBJ in the current buffer.
OBJ should be a call to BYTE-CODE generated by the byte compiler."
(let (bytes constvec)
(if (consp obj)
(setq bytes (car (cdr obj)) ;the byte code
constvec (car (cdr (cdr obj)))) ;constant vector
(setq bytes (aref obj 1)
constvec (aref obj 2)))
(cl-assert (not (multibyte-string-p bytes)))
(let ((lap (byte-decompile-bytecode bytes constvec))
op arg opname pc-value)
(let ((tagno 0)
tmp
(lap lap))
(while (setq tmp (assq 'TAG lap))
(setcar (cdr tmp) (setq tagno (1+ tagno)))
(setq lap (cdr (memq tmp lap)))))
(while lap
;; Take off the pc value of the next thing
;; and put it in pc-value.
(setq pc-value nil)
(if (numberp (car lap))
(setq pc-value (car lap)
lap (cdr lap)))
;; Fetch the next op and its arg.
(setq op (car (car lap))
arg (cdr (car lap)))
(setq lap (cdr lap))
(indent-to indent)
(if (eq 'TAG op)
(progn
;; We have a label. Display it, but first its pc value.
(if pc-value
(insert (format "%d:" pc-value)))
(insert (int-to-string (car arg))))
;; We have an instruction. Display its pc value first.
(if pc-value
(insert (format "%d" pc-value)))
(indent-to (+ indent disassemble-column-1-indent))
(if (and op
(string-match "^byte-" (setq opname (symbol-name op))))
(setq opname (substring opname 5))
(setq opname "<not-an-opcode>"))
(if (eq op 'byte-constant2)
(insert " #### shouldn't have seen constant2 here!\n "))
(insert opname)
(indent-to (+ indent disassemble-column-1-indent
disassemble-column-2-indent
-1))
(insert " ")
(cond ((memq op byte-goto-ops)
(insert (int-to-string (nth 1 arg))))
((memq op '(byte-call byte-unbind
byte-listN byte-concatN byte-insertN
byte-stack-ref byte-stack-set byte-stack-set2
byte-discardN byte-discardN-preserve-tos))
(insert (int-to-string arg)))
((memq op '(byte-varref byte-varset byte-varbind))
(prin1 (car arg) (current-buffer)))
((memq op '(byte-constant byte-constant2))
;; it's a constant
(setq arg (car arg))
;; if the succeeding op is byte-switch, display the jump table
;; used
(cond ((eq (car-safe (car-safe (cdr lap))) 'byte-switch)
(insert (format "<jump-table-%s (" (hash-table-test arg)))
(let ((first-time t))
(maphash #'(lambda (value tag)
(if first-time
(setq first-time nil)
(insert " "))
(insert (format "%s %s" value (cadr tag))))
arg))
(insert ")>"))
;; if the value of the constant is compiled code, then
;; recursively disassemble it.
((or (byte-code-function-p arg)
(and (eq (car-safe arg) 'macro)
(byte-code-function-p (cdr arg))))
(cond ((byte-code-function-p arg)
(insert "<compiled-function>\n"))
(t (insert "<compiled macro>\n")))
(disassemble-internal
arg
(+ indent disassemble-recursive-indent 1)
nil))
((eq (car-safe arg) 'byte-code)
(insert "<byte code>\n")
(disassemble-1 ;recurse on byte-code object
arg
(+ indent disassemble-recursive-indent)))
((eq (car-safe (car-safe arg)) 'byte-code)
(insert "(<byte code>...)\n")
(mapc ;Recurse on list of byte-code objects.
(lambda (obj)
(disassemble-1
obj
(+ indent disassemble-recursive-indent)))
arg))
(t
;; really just a constant
(let ((print-escape-newlines t))
(prin1 arg (current-buffer))))))
)
(insert "\n")))))
nil)
(defun re-disassemble (regexp &optional case-table)
"Describe the compiled form of REGEXP in a separate window.
If CASE-TABLE is non-nil, use it as translation table for case-folding.
This function is mainly intended for maintenance of Emacs itself
and may change at any time. It requires Emacs to be built with
`--enable-checking'."
(interactive "XRegexp (Lisp expression): ")
(let ((desc (with-temp-buffer
(when case-table
(set-case-table case-table))
(let ((case-fold-search (and case-table t)))
(re--describe-compiled regexp)))))
(with-output-to-temp-buffer "*Regexp-disassemble*"
(with-current-buffer standard-output
(insert desc)))))
(provide 'disass)
;;; disass.el ends here