ecl.lisp (35049B)
1 ;;;; -*- indent-tabs-mode: nil -*- 2 ;;; 3 ;;; slynk-ecl.lisp --- SLY backend for ECL. 4 ;;; 5 ;;; This code has been placed in the Public Domain. All warranties 6 ;;; are disclaimed. 7 ;;; 8 9 ;;; Administrivia 10 11 (defpackage slynk-ecl 12 (:use cl slynk-backend)) 13 14 (in-package slynk-ecl) 15 16 (eval-when (:compile-toplevel :load-toplevel :execute) 17 (defun ecl-version () 18 (let ((version (find-symbol "+ECL-VERSION-NUMBER+" :EXT))) 19 (if version 20 (symbol-value version) 21 0))) 22 (when (< (ecl-version) 100301) 23 (error "~&IMPORTANT:~% ~ 24 The version of ECL you're using (~A) is too old.~% ~ 25 Please upgrade to at least 10.3.1.~% ~ 26 Sorry for the inconvenience.~%~%" 27 (lisp-implementation-version)))) 28 29 ;; Hard dependencies. 30 (eval-when (:compile-toplevel :load-toplevel :execute) 31 (require 'sockets)) 32 33 ;; Soft dependencies. 34 (eval-when (:compile-toplevel :load-toplevel :execute) 35 (when (probe-file "sys:profile.fas") 36 (require :profile) 37 (pushnew :profile *features*)) 38 (when (probe-file "sys:serve-event.fas") 39 (require :serve-event) 40 (pushnew :serve-event *features*))) 41 42 (declaim (optimize (debug 3))) 43 44 ;;; Slynk-mop 45 46 (eval-when (:compile-toplevel :load-toplevel :execute) 47 (import-slynk-mop-symbols 48 :clos 49 (and (< (ecl-version) 121201) 50 `(:eql-specializer 51 :eql-specializer-object 52 :generic-function-declarations 53 :specializer-direct-methods 54 ,@(unless (fboundp 'clos:compute-applicable-methods-using-classes) 55 '(:compute-applicable-methods-using-classes)))))) 56 57 (defimplementation gray-package-name () 58 "GRAY") 59 60 61 ;;;; UTF8 62 63 ;;; Convert the string STRING to a (simple-array (unsigned-byte 8)). 64 ;;; 65 ;;; string-to-utf8 (string) 66 67 ;;; Convert the (simple-array (unsigned-byte 8)) OCTETS to a string. 68 ;;; 69 ;;; utf8-to-string (octets) 70 71 72 ;;;; TCP Server 73 74 (defun resolve-hostname (name) 75 (car (sb-bsd-sockets:host-ent-addresses 76 (sb-bsd-sockets:get-host-by-name name)))) 77 78 (defimplementation create-socket (host port &key backlog) 79 (let ((socket (make-instance 'sb-bsd-sockets:inet-socket 80 :type :stream 81 :protocol :tcp))) 82 (setf (sb-bsd-sockets:sockopt-reuse-address socket) t) 83 (sb-bsd-sockets:socket-bind socket (resolve-hostname host) port) 84 (sb-bsd-sockets:socket-listen socket (or backlog 5)) 85 socket)) 86 87 (defimplementation local-port (socket) 88 (nth-value 1 (sb-bsd-sockets:socket-name socket))) 89 90 (defimplementation close-socket (socket) 91 (sb-bsd-sockets:socket-close socket)) 92 93 (defun accept (socket) 94 "Like socket-accept, but retry on EAGAIN." 95 (loop (handler-case 96 (return (sb-bsd-sockets:socket-accept socket)) 97 (sb-bsd-sockets:interrupted-error ())))) 98 99 (defimplementation accept-connection (socket 100 &key external-format 101 buffering timeout) 102 (declare (ignore timeout)) 103 (sb-bsd-sockets:socket-make-stream (accept socket) 104 :output t 105 :input t 106 :buffering (ecase buffering 107 ((t) :full) 108 ((nil) :none) 109 (:line :line)) 110 :element-type (if external-format 111 'character 112 '(unsigned-byte 8)) 113 :external-format external-format)) 114 115 ;;; Call FN whenever SOCKET is readable. 116 ;;; 117 ;;; add-sigio-handler (socket fn) 118 119 ;;; Remove all sigio handlers for SOCKET. 120 ;;; 121 ;;; remove-sigio-handlers (socket) 122 123 ;;; Call FN when Lisp is waiting for input and SOCKET is readable. 124 ;;; 125 ;;; add-fd-handler (socket fn) 126 127 ;;; Remove all fd-handlers for SOCKET. 128 ;;; 129 ;;; remove-fd-handlers (socket) 130 131 (defimplementation preferred-communication-style () 132 (cond 133 ((member :threads *features*) :spawn) 134 ((member :windows *features*) nil) 135 (t #|:fd-handler|# nil))) 136 137 ;;; Set the 'stream 'timeout. The timeout is either the real number 138 ;;; specifying the timeout in seconds or 'nil for no timeout. 139 ;;; 140 ;;; set-stream-timeout (stream timeout) 141 142 143 ;;; Hook called when the first connection from Emacs is established. 144 ;;; Called from the INIT-FN of the socket server that accepts the 145 ;;; connection. 146 ;;; 147 ;;; This is intended for setting up extra context, e.g. to discover 148 ;;; that the calling thread is the one that interacts with Emacs. 149 ;;; 150 ;;; emacs-connected () 151 152 153 ;;;; Unix Integration 154 155 (defimplementation getpid () 156 (si:getpid)) 157 158 ;;; Call FUNCTION on SIGINT (instead of invoking the debugger). 159 ;;; Return old signal handler. 160 ;;; 161 ;;; install-sigint-handler (function) 162 163 ;;; XXX! 164 ;;; If ECL is built with thread support, it'll spawn a helper thread 165 ;;; executing the SIGINT handler. We do not want to BREAK into that 166 ;;; helper but into the main thread, though. This is coupled with the 167 ;;; current choice of NIL as communication-style in so far as ECL's 168 ;;; main-thread is also the Sly's REPL thread. 169 170 (defun make-interrupt-handler (real-handler) 171 #+threads 172 (let ((main-thread (find 'si:top-level (mp:all-processes) 173 :key #'mp:process-name))) 174 #'(lambda (&rest args) 175 (declare (ignore args)) 176 (mp:interrupt-process main-thread real-handler))) 177 #-threads 178 #'(lambda (&rest args) 179 (declare (ignore args)) 180 (funcall real-handler))) 181 182 (defimplementation call-with-user-break-handler (real-handler function) 183 (let ((old-handler #'si:terminal-interrupt)) 184 (setf (symbol-function 'si:terminal-interrupt) 185 (make-interrupt-handler real-handler)) 186 (unwind-protect (funcall function) 187 (setf (symbol-function 'si:terminal-interrupt) old-handler)))) 188 189 (defimplementation quit-lisp () 190 (ext:quit)) 191 192 ;;; Default implementation is fine. 193 ;;; 194 ;;; lisp-implementation-type-name 195 ;;; lisp-implementation-program 196 197 (defimplementation socket-fd (socket) 198 (etypecase socket 199 (fixnum socket) 200 (two-way-stream (socket-fd (two-way-stream-input-stream socket))) 201 (sb-bsd-sockets:socket (sb-bsd-sockets:socket-file-descriptor socket)) 202 (file-stream (si:file-stream-fd socket)))) 203 204 ;;; Create a character stream for the file descriptor FD. This 205 ;;; interface implementation requires either `ffi:c-inline' or has to 206 ;;; wait for the exported interface. 207 ;;; 208 ;;; make-fd-stream (socket-stream) 209 210 ;;; Duplicate a file descriptor. If the syscall fails, signal a 211 ;;; condition. See dup(2). This interface requiers `ffi:c-inline' or 212 ;;; has to wait for the exported interface. 213 ;;; 214 ;;; dup (fd) 215 216 ;;; Does not apply to ECL which doesn't dump images. 217 ;;; 218 ;;; exec-image (image-file args) 219 220 (defimplementation command-line-args () 221 (ext:command-args)) 222 223 224 ;;;; pathnames 225 226 ;;; Return a pathname for FILENAME. 227 ;;; A filename in Emacs may for example contain asterisks which should not 228 ;;; be translated to wildcards. 229 ;;; 230 ;;; filename-to-pathname (filename) 231 232 ;;; Return the filename for PATHNAME. 233 ;;; 234 ;;; pathname-to-filename (pathname) 235 236 (defimplementation default-directory () 237 (namestring (ext:getcwd))) 238 239 (defimplementation set-default-directory (directory) 240 (ext:chdir (namestring directory)) ; adapts *DEFAULT-PATHNAME-DEFAULTS*. 241 (default-directory)) 242 243 244 ;;; Call FN with hooks to handle special syntax. Can we use it for 245 ;;; `ffi:c-inline' to be handled as C/C++ code? 246 ;;; 247 ;;; call-with-syntax-hooks 248 249 ;;; Return a suitable initial value for SLYNK:*READTABLE-ALIST*. 250 ;;; 251 ;;; default-readtable-alist 252 253 254 ;;;; Packages 255 256 #+package-local-nicknames 257 (defimplementation package-local-nicknames (package) 258 (ext:package-local-nicknames package)) 259 260 261 ;;;; Compilation 262 263 (defvar *buffer-name* nil) 264 (defvar *buffer-start-position*) 265 266 (defun signal-compiler-condition (&rest args) 267 (apply #'signal 'compiler-condition args)) 268 269 #-ecl-bytecmp 270 (defun handle-compiler-message (condition) 271 ;; ECL emits lots of noise in compiler-notes, like "Invoking 272 ;; external command". 273 (unless (typep condition 'c::compiler-note) 274 (signal-compiler-condition 275 :original-condition condition 276 :message (princ-to-string condition) 277 :severity (etypecase condition 278 (c:compiler-fatal-error :error) 279 (c:compiler-error :error) 280 (error :error) 281 (style-warning :style-warning) 282 (warning :warning)) 283 :location (condition-location condition)))) 284 285 #-ecl-bytecmp 286 (defun condition-location (condition) 287 (let ((file (c:compiler-message-file condition)) 288 (position (c:compiler-message-file-position condition))) 289 (if (and position (not (minusp position))) 290 (if *buffer-name* 291 (make-buffer-location *buffer-name* 292 *buffer-start-position* 293 position) 294 (make-file-location file position)) 295 (make-error-location "No location found.")))) 296 297 (defimplementation call-with-compilation-hooks (function) 298 #+ecl-bytecmp 299 (funcall function) 300 #-ecl-bytecmp 301 (handler-bind ((c:compiler-message #'handle-compiler-message)) 302 (funcall function))) 303 304 (defvar *tmpfile-map* (make-hash-table :test #'equal)) 305 306 (defun note-buffer-tmpfile (tmp-file buffer-name) 307 ;; EXT:COMPILED-FUNCTION-FILE below will return a namestring. 308 (let ((tmp-namestring (namestring (truename tmp-file)))) 309 (setf (gethash tmp-namestring *tmpfile-map*) buffer-name) 310 tmp-namestring)) 311 312 (defun tmpfile-to-buffer (tmp-file) 313 (gethash tmp-file *tmpfile-map*)) 314 315 (defimplementation slynk-compile-string 316 (string &key buffer position filename line column policy) 317 (declare (ignore line column policy)) 318 (with-compilation-hooks () 319 (let ((*buffer-name* buffer) ; for compilation hooks 320 (*buffer-start-position* position)) 321 (let ((tmp-file (si:mkstemp "TMP:ecl-slynk-tmpfile-")) 322 (fasl-file) 323 (warnings-p) 324 (failure-p)) 325 (unwind-protect 326 (with-open-file (tmp-stream tmp-file :direction :output 327 :if-exists :supersede) 328 (write-string string tmp-stream) 329 (finish-output tmp-stream) 330 (multiple-value-setq (fasl-file warnings-p failure-p) 331 (compile-file tmp-file 332 :load t 333 :source-truename (or filename 334 (note-buffer-tmpfile tmp-file buffer)) 335 :source-offset (1- position)))) 336 (when (probe-file tmp-file) 337 (delete-file tmp-file)) 338 (when fasl-file 339 (delete-file fasl-file))) 340 (not failure-p))))) 341 342 (defimplementation slynk-compile-file (input-file output-file 343 load-p external-format 344 &key policy) 345 (declare (ignore policy)) 346 (with-compilation-hooks () 347 (compile-file input-file :output-file output-file 348 :load load-p 349 :external-format external-format))) 350 351 (defvar *external-format-to-coding-system* 352 '((:latin-1 353 "latin-1" "latin-1-unix" "iso-latin-1-unix" 354 "iso-8859-1" "iso-8859-1-unix") 355 (:utf-8 "utf-8" "utf-8-unix"))) 356 357 (defun external-format (coding-system) 358 (or (car (rassoc-if (lambda (x) (member coding-system x :test #'equal)) 359 *external-format-to-coding-system*)) 360 (find coding-system (ext:all-encodings) :test #'string-equal))) 361 362 (defimplementation find-external-format (coding-system) 363 #+unicode (external-format coding-system) 364 ;; Without unicode support, ECL uses the one-byte encoding of the 365 ;; underlying OS, and will barf on anything except :DEFAULT. We 366 ;; return NIL here for known multibyte encodings, so 367 ;; SLYNK:CREATE-SERVER will barf. 368 #-unicode (let ((xf (external-format coding-system))) 369 (if (member xf '(:utf-8)) 370 nil 371 :default))) 372 373 374 ;;; Default implementation is fine 375 ;;; 376 ;;; guess-external-format 377 378 379 ;;;; Streams 380 381 ;;; Implemented in `gray' 382 ;;; 383 ;;; make-output-stream 384 ;;; make-input-stream 385 386 387 ;;;; Documentation 388 389 (defimplementation arglist (name) 390 (multiple-value-bind (arglist foundp) 391 (ext:function-lambda-list name) 392 (if foundp arglist :not-available))) 393 394 (defimplementation type-specifier-p (symbol) 395 (or (subtypep nil symbol) 396 (not (eq (type-specifier-arglist symbol) :not-available)))) 397 398 (defimplementation function-name (f) 399 (typecase f 400 (generic-function (clos:generic-function-name f)) 401 (function (si:compiled-function-name f)))) 402 403 ;;; Default implementation is fine (CL). 404 ;;; 405 ;;; valid-function-name-p (form) 406 407 #+walker 408 (defimplementation macroexpand-all (form &optional env) 409 (walker:macroexpand-all form env)) 410 411 ;;; Default implementation is fine. 412 ;;; 413 ;;; compiler-macroexpand-1 414 ;;; compiler-macroexpand 415 416 (defimplementation describe-symbol-for-emacs (symbol) 417 (let ((result '())) 418 (flet ((frob (type boundp) 419 (when (funcall boundp symbol) 420 (let ((doc (describe-definition symbol type))) 421 (setf result (list* type doc result)))))) 422 (frob :VARIABLE #'boundp) 423 (frob :FUNCTION #'fboundp) 424 (frob :CLASS (lambda (x) (find-class x nil)))) 425 result)) 426 427 (defimplementation describe-definition (name type) 428 (case type 429 (:variable (documentation name 'variable)) 430 (:function (documentation name 'function)) 431 (:class (documentation name 'class)) 432 (t nil))) 433 434 435 ;;;; Debugging 436 437 (eval-when (:compile-toplevel :load-toplevel :execute) 438 (import 439 '(si::*break-env* 440 si::*ihs-top* 441 si::*ihs-current* 442 si::*ihs-base* 443 si::*frs-base* 444 si::*frs-top* 445 si::*tpl-commands* 446 si::*tpl-level* 447 si::frs-top 448 si::ihs-top 449 si::ihs-fun 450 si::ihs-env 451 si::sch-frs-base 452 si::set-break-env 453 si::set-current-ihs 454 si::tpl-commands))) 455 456 (defun make-invoke-debugger-hook (hook) 457 (when hook 458 #'(lambda (condition old-hook) 459 ;; Regard *debugger-hook* if set by user. 460 (if *debugger-hook* 461 nil ; decline, *DEBUGGER-HOOK* will be tried next. 462 (funcall hook condition old-hook))))) 463 464 (defimplementation install-debugger-globally (function) 465 (setq *debugger-hook* function) 466 (setq ext:*invoke-debugger-hook* (make-invoke-debugger-hook function))) 467 468 (defimplementation call-with-debugger-hook (hook fun) 469 (let ((*debugger-hook* hook) 470 (ext:*invoke-debugger-hook* (make-invoke-debugger-hook hook))) 471 (funcall fun))) 472 473 (defvar *backtrace* '()) 474 475 ;;; Commented out; it's not clear this is a good way of doing it. In 476 ;;; particular because it makes errors stemming from this file harder 477 ;;; to debug, and given the "young" age of ECL's slynk backend, that's 478 ;;; a bad idea. 479 ;;; 480 ;;; Also before thinking whether to uncomment this consider that SLY 481 ;;; might not be loaded with slynk-loader.lisp at all. 482 483 ;; (defun in-slynk-package-p (x) 484 ;; (and 485 ;; (symbolp x) 486 ;; (member (symbol-package x) 487 ;; (list #.(find-package :slynk) 488 ;; #.(find-package :slynk-backend) 489 ;; #.(ignore-errors (find-package :slynk-mop)) 490 ;; #.(ignore-errors (find-package :slynk-loader)))) 491 ;; t)) 492 493 ;; (defun is-slynk-source-p (name) 494 ;; (setf name (pathname name)) 495 ;; (pathname-match-p 496 ;; name 497 ;; (make-pathname :defaults slynk-loader::*source-directory* 498 ;; :name (pathname-name name) 499 ;; :type (pathname-type name) 500 ;; :version (pathname-version name)))) 501 502 ;; (defun is-ignorable-fun-p (x) 503 ;; (or 504 ;; (in-slynk-package-p (frame-name x)) 505 ;; (multiple-value-bind (file position) 506 ;; (ignore-errors (si::bc-file (car x))) 507 ;; (declare (ignore position)) 508 ;; (if file (is-slynk-source-p file))))) 509 510 (defimplementation call-with-debugging-environment (debugger-loop-fn) 511 (declare (type function debugger-loop-fn)) 512 (let* ((*ihs-top* (ihs-top)) 513 (*ihs-current* *ihs-top*) 514 (*frs-base* (or (sch-frs-base *frs-top* *ihs-base*) (1+ (frs-top)))) 515 (*frs-top* (frs-top)) 516 (*tpl-level* (1+ *tpl-level*)) 517 (*backtrace* (loop for ihs from 0 below *ihs-top* 518 collect (list (si::ihs-fun ihs) 519 (si::ihs-env ihs) 520 nil)))) 521 (declare (special *ihs-current*)) 522 (loop for f from *frs-base* until *frs-top* 523 do (let ((i (- (si::frs-ihs f) *ihs-base* 1))) 524 (when (plusp i) 525 (let* ((x (elt *backtrace* i)) 526 (name (si::frs-tag f))) 527 (unless (si::fixnump name) 528 (push name (third x))))))) 529 ;; (setf *backtrace* (remove-if #'is-ignorable-fun-p (nreverse *backtrace*))) 530 (setf *backtrace* (nreverse *backtrace*)) 531 (set-break-env) 532 (set-current-ihs) 533 (let ((*ihs-base* *ihs-top*)) 534 (funcall debugger-loop-fn)))) 535 536 (defimplementation compute-backtrace (start end) 537 (subseq *backtrace* start 538 (and (numberp end) 539 (min end (length *backtrace*))))) 540 541 (defun frame-name (frame) 542 (let ((x (first frame))) 543 (if (symbolp x) 544 x 545 (function-name x)))) 546 547 (defun function-position (fun) 548 (multiple-value-bind (file position) 549 (si::bc-file fun) 550 (when file 551 (make-file-location file position)))) 552 553 (defun frame-function (frame) 554 (let* ((x (first frame)) 555 fun position) 556 (etypecase x 557 (symbol (and (fboundp x) 558 (setf fun (fdefinition x) 559 position (function-position fun)))) 560 (function (setf fun x position (function-position x)))) 561 (values fun position))) 562 563 (defun frame-decode-env (frame) 564 (let ((functions '()) 565 (blocks '()) 566 (variables '())) 567 (setf frame (si::decode-ihs-env (second frame))) 568 (dolist (record (remove-if-not #'consp frame)) 569 (let* ((record0 (car record)) 570 (record1 (cdr record))) 571 (cond ((or (symbolp record0) (stringp record0)) 572 (setq variables (acons record0 record1 variables))) 573 ((not (si::fixnump record0)) 574 (push record1 functions)) 575 ((symbolp record1) 576 (push record1 blocks)) 577 (t 578 )))) 579 (values functions blocks variables))) 580 581 (defimplementation print-frame (frame stream) 582 (format stream "~A" (first frame))) 583 584 ;;; Is the frame FRAME restartable?. 585 ;;; Return T if `restart-frame' can safely be called on the frame. 586 ;;; 587 ;;; frame-restartable-p (frame) 588 589 (defimplementation frame-source-location (frame-number) 590 (let ((frame (elt *backtrace* frame-number))) 591 (or (nth-value 1 (frame-function frame)) 592 (make-error-location "Unknown source location for ~A." (car frame))))) 593 594 (defimplementation frame-catch-tags (frame-number) 595 (third (elt *backtrace* frame-number))) 596 597 (defimplementation frame-locals (frame-number) 598 (loop for (name . value) in (nth-value 2 (frame-decode-env 599 (elt *backtrace* frame-number))) 600 collect (list :name name :id 0 :value value))) 601 602 (defimplementation frame-var-value (frame-number var-number) 603 (destructuring-bind (name . value) 604 (elt 605 (nth-value 2 (frame-decode-env (elt *backtrace* frame-number))) 606 var-number) 607 (declare (ignore name)) 608 value)) 609 610 (defimplementation disassemble-frame (frame-number) 611 (let ((fun (frame-function (elt *backtrace* frame-number)))) 612 (disassemble fun))) 613 614 (defimplementation eval-in-frame (form frame-number) 615 (let ((env (second (elt *backtrace* frame-number)))) 616 (si:eval-with-env form env))) 617 618 ;;; frame-package 619 ;;; frame-call 620 ;;; return-from-frame 621 ;;; restart-frame 622 ;;; print-condition 623 ;;; condition-extras 624 625 (defimplementation gdb-initial-commands () 626 ;; These signals are used by the GC. 627 #+linux '("handle SIGPWR noprint nostop" 628 "handle SIGXCPU noprint nostop")) 629 630 ;;; active-stepping 631 ;;; sldb-break-on-return 632 ;;; sldb-break-at-start 633 ;;; sldb-stepper-condition-p 634 ;;; sldb-setp-into 635 ;;; sldb-step-next 636 ;;; sldb-step-out 637 638 639 ;;;; Definition finding 640 641 (defvar +TAGS+ (namestring 642 (merge-pathnames "TAGS" (translate-logical-pathname "SYS:")))) 643 644 (defun make-file-location (file file-position) 645 ;; File positions in CL start at 0, but Emacs' buffer positions 646 ;; start at 1. We specify (:ALIGN T) because the positions comming 647 ;; from ECL point at right after the toplevel form appearing before 648 ;; the actual target toplevel form; (:ALIGN T) will DTRT in that case. 649 (make-location `(:file ,(namestring (translate-logical-pathname file))) 650 `(:position ,(1+ file-position)) 651 `(:align t))) 652 653 (defun make-buffer-location (buffer-name start-position &optional (offset 0)) 654 (make-location `(:buffer ,buffer-name) 655 `(:offset ,start-position ,offset) 656 `(:align t))) 657 658 (defun make-TAGS-location (&rest tags) 659 (make-location `(:etags-file ,+TAGS+) 660 `(:tag ,@tags))) 661 662 (defimplementation find-definitions (name) 663 (let ((annotations (ext:get-annotation name 'si::location :all))) 664 (cond (annotations 665 (loop for annotation in annotations 666 collect (destructuring-bind (dspec file . pos) annotation 667 `(,dspec ,(make-file-location file pos))))) 668 (t 669 (mapcan #'(lambda (type) (find-definitions-by-type name type)) 670 (classify-definition-name name)))))) 671 672 (defun classify-definition-name (name) 673 (let ((types '())) 674 (when (fboundp name) 675 (cond ((special-operator-p name) 676 (push :special-operator types)) 677 ((macro-function name) 678 (push :macro types)) 679 ((typep (fdefinition name) 'generic-function) 680 (push :generic-function types)) 681 ((si:mangle-name name t) 682 (push :c-function types)) 683 (t 684 (push :lisp-function types)))) 685 (when (boundp name) 686 (cond ((constantp name) 687 (push :constant types)) 688 (t 689 (push :global-variable types)))) 690 types)) 691 692 (defun find-definitions-by-type (name type) 693 (ecase type 694 (:lisp-function 695 (when-let (loc (source-location (fdefinition name))) 696 (list `((defun ,name) ,loc)))) 697 (:c-function 698 (when-let (loc (source-location (fdefinition name))) 699 (list `((c-source ,name) ,loc)))) 700 (:generic-function 701 (loop for method in (clos:generic-function-methods (fdefinition name)) 702 for specs = (clos:method-specializers method) 703 for loc = (source-location method) 704 when loc 705 collect `((defmethod ,name ,specs) ,loc))) 706 (:macro 707 (when-let (loc (source-location (macro-function name))) 708 (list `((defmacro ,name) ,loc)))) 709 (:constant 710 (when-let (loc (source-location name)) 711 (list `((defconstant ,name) ,loc)))) 712 (:global-variable 713 (when-let (loc (source-location name)) 714 (list `((defvar ,name) ,loc)))) 715 (:special-operator))) 716 717 ;;; FIXME: There ought to be a better way. 718 (eval-when (:compile-toplevel :load-toplevel :execute) 719 (defun c-function-name-p (name) 720 (and (symbolp name) (si:mangle-name name t) t)) 721 (defun c-function-p (object) 722 (and (functionp object) 723 (let ((fn-name (function-name object))) 724 (and fn-name (c-function-name-p fn-name)))))) 725 726 (deftype c-function () 727 `(satisfies c-function-p)) 728 729 (defun assert-source-directory () 730 (unless (probe-file #P"SRC:") 731 (error "ECL's source directory ~A does not exist. ~ 732 You can specify a different location via the environment ~ 733 variable `ECLSRCDIR'." 734 (namestring (translate-logical-pathname #P"SYS:"))))) 735 736 (defun assert-TAGS-file () 737 (unless (probe-file +TAGS+) 738 (error "No TAGS file ~A found. It should have been installed with ECL." 739 +TAGS+))) 740 741 (defun package-names (package) 742 (cons (package-name package) (package-nicknames package))) 743 744 (defun source-location (object) 745 (converting-errors-to-error-location 746 (typecase object 747 (c-function 748 (assert-source-directory) 749 (assert-TAGS-file) 750 (let ((lisp-name (function-name object))) 751 (assert lisp-name) 752 (multiple-value-bind (flag c-name) (si:mangle-name lisp-name t) 753 (assert flag) 754 ;; In ECL's code base sometimes the mangled name is used 755 ;; directly, sometimes ECL's DPP magic of @SI::SYMBOL or 756 ;; @EXT::SYMBOL is used. We cannot predict here, so we just 757 ;; provide several candidates. 758 (apply #'make-TAGS-location 759 c-name 760 (loop with s = (symbol-name lisp-name) 761 for p in (package-names (symbol-package lisp-name)) 762 collect (format nil "~A::~A" p s) 763 collect (format nil "~(~A::~A~)" p s)))))) 764 (function 765 (multiple-value-bind (file pos) (ext:compiled-function-file object) 766 (cond ((not file) 767 (return-from source-location nil)) 768 ((tmpfile-to-buffer file) 769 (make-buffer-location (tmpfile-to-buffer file) pos)) 770 (t 771 (assert (probe-file file)) 772 (assert (not (minusp pos))) 773 (make-file-location file pos))))) 774 (method 775 ;; FIXME: This will always return NIL at the moment; ECL does not 776 ;; store debug information for methods yet. 777 (source-location (clos:method-function object))) 778 ((member nil t) 779 (multiple-value-bind (flag c-name) (si:mangle-name object) 780 (assert flag) 781 (make-TAGS-location c-name)))))) 782 783 (defimplementation find-source-location (object) 784 (or (source-location object) 785 (make-error-location "Source definition of ~S not found." object))) 786 787 ;;; buffer-first-change 788 789 790 ;;;; XREF 791 792 ;;; who-calls 793 ;;; calls-who 794 ;;; who-references 795 ;;; who-binds 796 ;;; who-sets 797 ;;; who-macroexpands 798 ;;; who-specializes 799 ;;; list-callers 800 ;;; list-callees 801 802 803 ;;;; Profiling 804 805 ;;; XXX: use monitor.lisp (ccl,clisp) 806 807 #+profile 808 (progn 809 810 (defimplementation profile (fname) 811 (when fname (eval `(profile:profile ,fname)))) 812 813 (defimplementation unprofile (fname) 814 (when fname (eval `(profile:unprofile ,fname)))) 815 816 (defimplementation unprofile-all () 817 (profile:unprofile-all) 818 "All functions unprofiled.") 819 820 (defimplementation profile-report () 821 (profile:report)) 822 823 (defimplementation profile-reset () 824 (profile:reset) 825 "Reset profiling counters.") 826 827 (defimplementation profiled-functions () 828 (profile:profile)) 829 830 (defimplementation profile-package (package callers methods) 831 (declare (ignore callers methods)) 832 (eval `(profile:profile ,(package-name (find-package package))))) 833 ) ; #+profile (progn ... 834 835 836 ;;;; Trace 837 838 ;;; Toggle tracing of the function(s) given with SPEC. 839 ;;; SPEC can be: 840 ;;; (setf NAME) ; a setf function 841 ;;; (:defmethod NAME QUALIFIER... (SPECIALIZER...)) ; a specific method 842 ;;; (:defgeneric NAME) ; a generic function with all methods 843 ;;; (:call CALLER CALLEE) ; trace calls from CALLER to CALLEE. 844 ;;; (:labels TOPLEVEL LOCAL) 845 ;;; (:flet TOPLEVEL LOCAL) 846 ;;; 847 ;;; toggle-trace (spec) 848 849 850 ;;;; Inspector 851 852 ;;; FIXME: Would be nice if it was possible to inspect objects 853 ;;; implemented in C. 854 855 ;;; Return a list of bindings corresponding to OBJECT's slots. 856 ;;; eval-context (object) 857 858 ;;; Return a string describing the primitive type of object. 859 ;;; describe-primitive-type (object) 860 861 862 ;;;; Multithreading 863 864 ;;; Not needed in ECL 865 ;;; 866 ;;; initialize-multiprocessing 867 868 #+threads 869 (progn 870 (defvar *thread-id-counter* 0) 871 872 (defparameter *thread-id-map* (make-hash-table)) 873 874 (defvar *thread-id-map-lock* 875 (mp:make-lock :name "thread id map lock")) 876 877 (defimplementation spawn (fn &key name) 878 (mp:process-run-function name fn)) 879 880 (defimplementation thread-id (target-thread) 881 (block thread-id 882 (mp:with-lock (*thread-id-map-lock*) 883 ;; Does TARGET-THREAD have an id already? 884 (maphash (lambda (id thread-pointer) 885 (let ((thread (si:weak-pointer-value thread-pointer))) 886 (cond ((not thread) 887 (remhash id *thread-id-map*)) 888 ((eq thread target-thread) 889 (return-from thread-id id))))) 890 *thread-id-map*) 891 ;; TARGET-THREAD not found in *THREAD-ID-MAP* 892 (let ((id (incf *thread-id-counter*)) 893 (thread-pointer (si:make-weak-pointer target-thread))) 894 (setf (gethash id *thread-id-map*) thread-pointer) 895 id)))) 896 897 (defimplementation find-thread (id) 898 (mp:with-lock (*thread-id-map-lock*) 899 (let* ((thread-ptr (gethash id *thread-id-map*)) 900 (thread (and thread-ptr (si:weak-pointer-value thread-ptr)))) 901 (unless thread 902 (remhash id *thread-id-map*)) 903 thread))) 904 905 (defimplementation thread-name (thread) 906 (mp:process-name thread)) 907 908 (defimplementation thread-status (thread) 909 (if (mp:process-active-p thread) 910 "RUNNING" 911 "STOPPED")) 912 913 ;; thread-attributes 914 915 (defimplementation current-thread () 916 mp:*current-process*) 917 918 (defimplementation all-threads () 919 (mp:all-processes)) 920 921 (defimplementation thread-alive-p (thread) 922 (mp:process-active-p thread)) 923 924 (defimplementation interrupt-thread (thread fn) 925 (mp:interrupt-process thread fn)) 926 927 (defimplementation kill-thread (thread) 928 (mp:process-kill thread)) 929 930 (defvar *mailbox-lock* (mp:make-lock :name "mailbox lock")) 931 (defvar *mailboxes* (list)) 932 (declaim (type list *mailboxes*)) 933 934 (defstruct (mailbox (:conc-name mailbox.)) 935 thread 936 (mutex (mp:make-lock)) 937 (cvar (mp:make-condition-variable)) 938 (queue '() :type list)) 939 940 (defun mailbox (thread) 941 "Return THREAD's mailbox." 942 (mp:with-lock (*mailbox-lock*) 943 (or (find thread *mailboxes* :key #'mailbox.thread) 944 (let ((mb (make-mailbox :thread thread))) 945 (push mb *mailboxes*) 946 mb)))) 947 948 (defimplementation send (thread message) 949 (let* ((mbox (mailbox thread)) 950 (mutex (mailbox.mutex mbox))) 951 (mp:with-lock (mutex) 952 (setf (mailbox.queue mbox) 953 (nconc (mailbox.queue mbox) (list message))) 954 (mp:condition-variable-broadcast (mailbox.cvar mbox))))) 955 956 ;; receive 957 958 (defimplementation receive-if (test &optional timeout) 959 (let* ((mbox (mailbox (current-thread))) 960 (mutex (mailbox.mutex mbox))) 961 (assert (or (not timeout) (eq timeout t))) 962 (loop 963 (check-sly-interrupts) 964 (mp:with-lock (mutex) 965 (let* ((q (mailbox.queue mbox)) 966 (tail (member-if test q))) 967 (when tail 968 (setf (mailbox.queue mbox) (nconc (ldiff q tail) (cdr tail))) 969 (return (car tail)))) 970 (when (eq timeout t) (return (values nil t))) 971 (mp:condition-variable-wait (mailbox.cvar mbox) mutex))))) 972 973 ;; Trigger a call to CHECK-SLIME-INTERRUPTS in THREAD without using 974 ;; asynchronous interrupts. 975 ;; 976 ;; Doesn't have to implement this if RECEIVE-IF periodically calls 977 ;; CHECK-SLIME-INTERRUPTS, but that's energy inefficient. 978 ;; 979 ;; wake-thread (thread) 980 981 ;; Copied from sbcl.lisp and adjusted to ECL. 982 (let ((alist '()) 983 (mutex (mp:make-lock :name "register-thread"))) 984 985 (defimplementation register-thread (name thread) 986 (declare (type symbol name)) 987 (mp:with-lock (mutex) 988 (etypecase thread 989 (null 990 (setf alist (delete name alist :key #'car))) 991 (mp:process 992 (let ((probe (assoc name alist))) 993 (cond (probe (setf (cdr probe) thread)) 994 (t (setf alist (acons name thread alist)))))))) 995 nil) 996 997 (defimplementation find-registered (name) 998 (mp:with-lock (mutex) 999 (cdr (assoc name alist))))) 1000 1001 ;; Not needed in ECL (?). 1002 ;; 1003 ;; set-default-initial-binding (var form) 1004 1005 ) ; #+threads 1006 1007 ;;; Instead of busy waiting with communication-style NIL, use select() 1008 ;;; on the sockets' streams. 1009 #+serve-event 1010 (defimplementation wait-for-input (streams &optional timeout) 1011 (assert (member timeout '(nil t))) 1012 (flet ((poll-streams (streams timeout) 1013 (let* ((serve-event::*descriptor-handlers* 1014 (copy-list serve-event::*descriptor-handlers*)) 1015 (active-fds '()) 1016 (fd-stream-alist 1017 (loop for s in streams 1018 for fd = (socket-fd s) 1019 collect (cons fd s) 1020 do (serve-event:add-fd-handler fd :input 1021 #'(lambda (fd) 1022 (push fd active-fds)))))) 1023 (serve-event:serve-event timeout) 1024 (loop for fd in active-fds collect (cdr (assoc fd fd-stream-alist)))))) 1025 (loop 1026 (cond ((check-slime-interrupts) (return :interrupt)) 1027 (timeout (return (poll-streams streams 0))) 1028 (t 1029 (when-let (ready (poll-streams streams 0.2)) 1030 (return ready))))))) 1031 1032 #-serve-event 1033 (defimplementation wait-for-input (streams &optional timeout) 1034 (assert (member timeout '(nil t))) 1035 (loop 1036 (cond ((check-slime-interrupts) (return :interrupt)) 1037 (timeout (return (remove-if-not #'listen streams))) 1038 (t 1039 (let ((ready (remove-if-not #'listen streams))) 1040 (if ready (return ready)) 1041 (sleep 0.1)))))) 1042 1043 1044 ;;;; Locks 1045 1046 #+threads 1047 (defimplementation make-lock (&key name) 1048 (mp:make-lock :name name :recursive t)) 1049 1050 (defimplementation call-with-lock-held (lock function) 1051 (declare (type function function)) 1052 (mp:with-lock (lock) (funcall function))) 1053 1054 1055 ;;;; Weak datastructures 1056 1057 ;;; XXX: this should work but causes SLIME REPL hang at some point of time. May 1058 ;;; be ECL or SLIME bug - disabling for now. 1059 #+(and ecl-weak-hash (or)) 1060 (progn 1061 (defimplementation make-weak-key-hash-table (&rest args) 1062 (apply #'make-hash-table :weakness :key args)) 1063 1064 (defimplementation make-weak-value-hash-table (&rest args) 1065 (apply #'make-hash-table :weakness :value args)) 1066 1067 (defimplementation hash-table-weakness (hashtable) 1068 (ext:hash-table-weakness hashtable))) 1069 1070 1071 ;;;; Character names 1072 1073 ;;; Default implementation is fine. 1074 ;;; 1075 ;;; character-completion-set (prefix matchp) 1076 1077 1078 ;;;; Heap dumps 1079 1080 ;;; Doesn't apply to ECL. 1081 ;;; 1082 ;;; save-image (filename &optional restart-function) 1083 ;;; background-save-image (filename &key restart-function completion-function) 1084 1085 1086 ;;;; Wrapping 1087 1088 ;;; Intercept future calls to SPEC and surround them in callbacks. 1089 ;;; Very much similar to so-called advices for normal functions. 1090 ;;; 1091 ;;; wrap (spec indicator &key before after replace) 1092 ;;; unwrap (spec indicator) 1093 ;;; wrapped-p (spec indicator)