diff --git a/lisp/emacs-lisp/inline.el b/lisp/emacs-lisp/inline.el index f761fda29a8..f31850dafad 100644 --- a/lisp/emacs-lisp/inline.el +++ b/lisp/emacs-lisp/inline.el @@ -266,5 +266,235 @@ inline--warning ;; inline--form) inline--form)) +(defun inline-extract-arglist (fxn-name) + "Construct arglist based on FXN docstring if provided in help format." + (let* ((s (or (documentation fxn-name t) "")) + (found (string-match "\n(fn \\([^\)]*\\))$" s)) + (n (length "\n\(fn "))) + (if (not found) + ;; punt + '(&rest args) + (let ((arglist-string + (format "\(%s" + (downcase (substring s (+ found n)))))) + (with-temp-buffer + (insert arglist-string) + (goto-char (point-min)) + (read (current-buffer))))))) + + +(defun inline-application-form (fxn args &optional rands) + "Construct an application form for function FXN with argument list ARGS and operands RANDS." + (let ((ls args) + (required 0) + params opt restp) + (while ls + (pcase ls + (`(&rest ,param) + (push param params) + (setq restp t) + (setq ls nil)) + (`(&rest . ,_ignored) + (error "argument list: %s: malformed &rest parameter %S" fxn args)) + (`(&optional . ,_ignored) + (when opt + (error "argument list: %s: multiple &optional markers %S" + fxn args)) + (pop ls) + (setq opt 0)) + (`(,param . ,_ignored) + (push param params) + (pop ls) + (if opt + (setq opt (1+ opt)) + (setq required (1+ required)))) + (_ + (error "malformed argument list: %s: %S" fxn args)))) + (setq params (nreverse params)) + (unless (or (subrp fxn) + (byte-code-function-p fxn) + (compiled-function-p fxn) + (symbolp fxn) + (and (consp fxn) + (eq (car fxn) 'function))) + (setq fxn (list 'function fxn))) + (unless opt + (setq opt 0)) + (if rands + (progn + ;; (display-warning '(inline) + ;; (format "Source operands: %S %S" fxn rands)) + `(,fxn ,@rands)) + (if restp + `(apply ,(if (symbolp fxn) `#',fxn fxn) ,@params) + `(,fxn ,@params))))) + +;; Derived from inline.el +(defun inline--testconst-exp-p (exp) + (or (macroexp-const-p exp) + (eq (car-safe exp) 'function))) + +;;;###autoload +(defmacro define-inline-pure-subr (name args &optional new-name) + "Define NEW-NAME to inline the subr currently bound to NAME. +The function must have the signature specified by ARGS. +This inlining enables compile-time evaluation during macroexpansion +rather than during the byte-compiler's optimization phase. +NEW-NAME defaults to NAME." + (declare (indent defun) (debug defun) (doc-string 3)) + ;; (message "Redefining as inline %s %S %s" name args new-name) + (when (and new-name (not (eq new-name name))) + (setplist new-name (seq-copy (symbol-plist name)))) + (unless new-name + (setq new-name name)) + (let ((doc (documentation name t)) + (fxn (symbol-function name)) + (fxn-sym (intern (format "inline-%s" new-name))) + (cm-name (intern (format "%s--inliner" new-name))) + app-form) + (while (symbolp fxn) + (setq fxn (symbol-function fxn))) + (unless (or (subrp fxn) + (byte-code-function-p fxn) + (compiled-function-p fxn) + (and (consp fxn) + (or (eq (car fxn) 'function) + (eq (car fxn) 'lambda)))) + (setq fxn (list 'function fxn))) + (function-put new-name 'compiler-macro nil) ; see define-inline + (setq app-form (inline-application-form fxn-sym args)) + (fset fxn-sym fxn) + `(progn + (fset ',fxn-sym ,fxn) + (defun ,new-name ,args + ,doc + (fset ',fxn-sym ,fxn) + ,(if (eq new-name name) + `(progn + (fset ',new-name ,fxn) + ;; see define-inline + (function-put ',new-name 'compiler-macro ',cm-name)) + ;; first pass ensure definition of underlying function + ;; then redefine to bypass this trampoline + `(,(if (memq (get name 'byte-optimizer) + '(nil byte-compile-inline-expand)) + 'defsubst + 'defun) + ,new-name ,args ,doc + (declare (compiler-macro ,cm-name)) + ,app-form)) + ,app-form) + (eval-and-compile + (defun ,cm-name ,(cons 'inline--form args) + (fset ',fxn-sym ,fxn) + (defun ,cm-name ,(cons 'inline--form args) + (let* ((rands (mapcar #'macroexpand-all (cdr inline--form))) + (expander-app-form + `(,',fxn-sym ,@rands))) + (if (seq-every-p #'inline--testconst-exp-p rands) + (progn + (let ((r (eval expander-app-form))) + (unless (macroexp-const-p r) + (setq r `(quote ,r))) + r)) + inline--form))) + ,(inline-application-form cm-name (cons 'inline--form args)))) + (function-put ',new-name 'compiler-macro ',cm-name)))) + +;;;###autoload +(defvar inline--inlined-primitives nil + "Association list of pure functions and their argument lists for inlining.") + +(defvar inline--defining-inlines nil) +(defun inline-all-pure-functions () + "Create macro-expansion evaluating versions of known pure functions. +These inline versions reduce constant arguments at macro-expansion time without +involvement of the byte-compiler." + (let ((inline--defining-inlines t)) + (setq inline--inlined-primitives + (let (purefuncs) + (mapatoms (lambda (x) + (and (fboundp x) (get x 'pure) + (push `(,x . ,x) purefuncs)))) + ;; these are not truly pure + ;; make inline-* variants available for explicit use + (push '(format . inline-format) purefuncs) + (push '(intern . inline-intern) purefuncs) + (setq purefuncs (nreverse purefuncs)) + (mapcar (lambda (x) + `(,(car x) ,(cdr x) . ,(inline-extract-arglist (car x)))) + purefuncs)))) + + (mapc (lambda (pr) + (eval `(define-inline-pure-subr ,(car pr) ,(cddr pr) ,(cadr pr)))) + inline--inlined-primitives)) + +(when (and (or (featurep 'bytecomp) (featurep 'byte-opt)) + (fboundp 'function-documentation) + (not inline--defining-inlines)) + (inline-all-pure-functions)) + +;; (defmacro define-inline-pure (name args &rest body) +;; "Define NAME as inlined pure function with signature ARGS. +;; BODY will be evaluated during macroexpansion if given constant arguments." +;; (declare (indent defun) (debug defun) (doc-string 3)) +;; (let ((doc (if (stringp (car-safe body)) (list (pop body)))) +;; (declares (if (eq (car-safe (car-safe body)) 'declare) (pop body))) +;; (cm-name (intern (format "%s--inliner" name))) +;; (bodyexp (macroexp-progn body)) +;; expanded-ct-body ct-fxn app-form) +;; (function-put name 'compiler-macro nil) ; see define-inline +;; (setq app-form (inline-application-form fxn args)) +;; (setq expanded-ct-body +;; `(catch 'inline--just-use +;; ,(macroexpand-all +;; bodyexp +;; `((inline-quote . inline--do-quote) +;; ;; (inline-\` . inline--do-quote) +;; (inline--leteval . inline--do-leteval) +;; (inline--letlisteval +;; . inline--do-letlisteval) +;; (inline-const-p . inline--testconst-p) +;; (inline-const-val . inline--getconst-val) +;; (inline-error . inline--warning) +;; ,@macroexpand-all-environment)))) +;; ;; construct a function that should not have +;; ;; circular dependency on the function symbol +;; ;; being inlined +;; (setq ct-fxn +;; (let ((x (cl-gensym "x-")) +;; (expanded-body +;; `(catch 'inline--just-use +;; ,expanded-ct-body))) +;; (byte-compile +;; `(lambda (,args) +;; (cl-labels ((,name ,args ,@expanded-ct-body)) +;; ,app-form))))) +;; `(progn +;; (defun ,name ,args +;; ,@doc +;; (declare (compiler-macro ,cm-name) ,@declares) +;; ,(macroexpand-all bodyexp +;; `((inline-quote . inline--dont-quote) +;; ;; (inline-\` . inline--dont-quote) +;; (inline--leteval . inline--dont-leteval) +;; (inline--letlisteval . inline--dont-letlisteval) +;; (inline-const-p . inline--alwaysconst-p) +;; (inline-const-val . inline--alwaysconst-val) +;; (inline-error . inline--error) +;; ,@macroexpand-all-environment))) +;; (eval-and-compile +;; (defun ,cm-name ,(cons 'inline--form args) +;; (let* ((rands (mapcar #'macroexpand-all (cdr inline--form))) +;; (expander-app-form `(,,fxn ,@rands))) +;; (if (seq-every-p #'inline--testconst-exp-p rands) +;; (let ((r +;; ;; (eval expander-app-form))) +;; (apply ct-fxn rands))) +;; (unless (macroexp-const-p r) +;; (setq r `(quote ,r))) +;; r) +;; ,@expanded-ct-body))))))) + (provide 'inline) ;;; inline.el ends here diff --git a/lisp/ldefs-boot.el b/lisp/ldefs-boot.el index ddaa3f83fbb..f5cebb54faa 100644 --- a/lisp/ldefs-boot.el +++ b/lisp/ldefs-boot.el @@ -18249,6 +18249,18 @@ "informat" (fn NAME ARGS &rest BODY)" nil t) (function-put 'define-inline 'lisp-indent-function 'defun) (function-put 'define-inline 'doc-string-elt 3) + + +(autoload 'define-inline-pure-subr "inline" "\ +Define NEW-NAME to inline the subr currently bound to NAME. +The function must have the signature specified by ARGS. +This inlining enables compile-time evaluation during macroexpansion +rather than during the byte-compiler's optimization phase. +NEW-NAME defaults to NAME.") + +(defvar inline--inlined-primitives nil "\ +Association list of pure functions and their argument lists for inlining.") + (register-definition-prefixes "inline" '("inline-"))