;;; ;;; ********************************************************************** ;;; This code was written by Douglas T. Crosher and has been placed in ;;; the Public domain, and is provided 'as is'. ;;; ;;; $Id: mp-test.lisp,v 1.12 1999/12/03 21:11:35 dtc Exp $ ;;; ;;; ********************************************************************** ;;; ;;; Stack-group and multi-process support for CMUCL x86. ;;; ;;; Test code and examples. ;;; (in-package "MP") ;;;; Bindings stack ;;; Show the current binding stack. (defun show-binding-stack () (let* ((binding-stack-pointer (kernel:binding-stack-pointer-sap)) (binding-stack (sys:int-sap (alien:extern-alien "binding_stack" alien:unsigned))) (size (sys:sap- binding-stack-pointer binding-stack))) (declare (type (unsigned-byte 29) size)) (do ((binding 0 (+ 8 binding))) ((= binding size)) (declare (type (unsigned-byte 29) binding)) (let* ((value (kernel:make-lisp-obj (sys:sap-int (sys:sap-ref-sap binding-stack binding)))) (symbol (kernel:make-lisp-obj (sys:sap-int (sys:sap-ref-sap binding-stack (+ binding 4)))))) (format t "~s ~s~%" symbol value))))) (defun tst-binding () (show-binding-stack) (unbind-binding-stack) (multiple-value-bind (stack size) (save-binding-stack #()) (restore-binding-stack stack size)) (rebind-binding-stack) (show-binding-stack)) ;;;; Alien stack (defun tst-alien () (alien:with-alien ((buf (array char 256))) (format t "~s~%" buf) (multiple-value-bind (save-stack size alien-stack) (save-alien-stack (make-array 0 :element-type '(unsigned-byte 32))) (restore-alien-stack save-stack size alien-stack)) (format t "~s~%" buf))) ;;;; Control stack (defun show-control-stack (control-stack-id) (declare (type lisp::index control-stack-id)) (let ((stack (aref x86::*control-stacks* control-stack-id))) (declare (type (or null (simple-array (unsigned-byte 32) (*))) stack)) (when stack (format t "Saved control stack ~d~%" control-stack-id) ;; First element has the stack-pointer. (let ((stack-pointer (aref stack 0)) (length (length stack))) (do ((addr (- (alien:extern-alien "control_stack_end" alien:unsigned) 4) (- addr 4)) (index 1 (1+ index))) ((or (< addr stack-pointer) (>= index length))) (declare (type (unsigned-byte 32) addr) (type (unsigned-byte 29) index)) (format t "0x~8x : 0x~8x~%" addr (aref stack index))) (format t "Stack pointer: 0x~x~%" (aref stack 0)) (format t "Return address: 0x~x~%" (aref stack (- length 2))) (format t "Frame pointer: 0x~x~%" (aref stack (- length 1))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; Multi-process example. ;;; All the processes are going to write to the standsard output. Use ;;; an output lock to prevent conflict; also provides a good test. (defvar *output-lock* nil) ;;; Results stack. (defvar *results* nil) ;;; Do some time consuming work. Occasionally write out some results, ;;; and when done place the result the the *results* stack. (defun work (itter msg) (declare (fixnum itter)) (let ((sum 1d0)) (declare (double-float sum)) (do ((i 0 (1+ i))) ((> i itter)) (declare (fixnum i)) (dotimes (i 1000000) (declare (fixnum i)) (incf sum 1d-6) (incf sum 1d-6)) (with-lock-held (*output-lock* "Waiting for output lock") (format t "~a ~d ~s~%" msg i sum) (finish-output)) ;; May want to yield occasionally if an interrupt isn't going to ;; force a yield. #+nil (process-yield)) (push sum *results*))) ;;;; ;;; Test catch, unwind, throw. (declaim (double-float *work2-sum*)) (defvar *work2-sum* 0d0) (defun work2 (itter msg) (declare (fixnum itter)) (let ((*work2-sum* 0d0)) (do ((i 0 (1+ i))) ((> i itter)) (declare (fixnum i)) (dotimes (i 100000) (declare (fixnum i)) (incf *work2-sum* (catch 'work2-sum (work2b)))) (with-lock-held (*output-lock* "Waiting for output lock") (format t "~a ~d ~s~%" msg i *work2-sum*) (finish-output)) ;; May want to yield occasionally if an interrupt isn't going to ;; force a yield. #+nil (process-yield)) (push *work2-sum* *results*))) (defun work2b () (unwind-protect (work2c) (incf *work2-sum* 1d-6))) (defun work2c () (unwind-protect (work2d) (incf *work2-sum* 1d-6))) (defun work2d () (unwind-protect (throw 'work2-sum 1d-6) (incf *work2-sum* 1d-6))) ;;;; ;;; Some process will require a lock to do any work. (defvar *work-lock* nil) (declaim (fixnum *int-count*)) (defvar *int-count* 0) (declaim (fixnum *count* *local-count*)) (defvar *count* 0) (defvar *local-count* 0) ;;; (defun tst (&key (scale 1) (workers 150) (workers-locked 150)) (init-multi-processing) ;; Start the yield interrupt - for the brave. (start-sigalrm-yield 0 5000) ;; (setf *output-lock* (make-lock "Output lock")) ;; Process to periodically show the processes. (let ((show-processes-process (make-process #'(lambda () (unwind-protect ; Test process unwinding when destroyed. (loop (process-wait-with-timeout "Sleeping" 20 #'(lambda () nil)) (with-lock-held (*output-lock* "Waiting for output lock") (format t "-=-=-=-=-=-~%") (format t "All processes:~%") (show-processes t) (format t "-=-=-=-=-=-~%") (finish-output))) (with-lock-held (*output-lock* "Waiting for output lock") (format t "Process ~s unwinding~%" *current-process*) (finish-output)))) :name "Show processes"))) (setf *results* nil) ;; Process to check and print and results pushed onto the ;; *results* stack. Will timeout if there have been no result for ;; 300 seconds, and kill the show-processes-process. (make-process #'(lambda () (loop (let ((results (process-wait-with-timeout "Waiting for results" (* 300 scale) #'(lambda () *results*)))) (when (null results) (with-lock-held (*output-lock* "Waiting for output lock") (format t "~s Timeout~%" *current-process*) (finish-output)) (destroy-process show-processes-process) (return)) (with-lock-held (*output-lock* "Waiting for output lock") (format t "Results: ~s~%" results) (finish-output)) (setf *results* nil)))) :name "Show results")) (dotimes (worker workers) (let* ((name (format nil "Worker ~D" worker)) (output (format nil "~A: working" name))) ;; Results generating processes, running in parallel. (make-process #'(lambda () (work (* 5 scale) output)) :name name))) ;; Processes competing over a lock to work. (setf *work-lock* (make-lock "Work lock")) (dotimes (worker workers-locked) (let* ((name (format nil "Worker (locked) ~D" worker)) (output (format nil "~A: working" name))) (make-process #'(lambda () (dotimes (i (* 5 scale)) (with-lock-held (*work-lock* "Waiting for work lock") (work (* 1 scale) output)) (process-yield))) :name name))) ;; Local special counter. (setq *count* 0) (setq *local-count* 0) ;; New processes do not inherit local special bindings and will thus ;; see the global value of *local-count* even though the parent ;; process makes a local binding. (let ((*local-count* 20)) (make-process #'(lambda () (dotimes (i (* 10 scale)) (with-lock-held (*output-lock* "Waiting for output lock") (incf *count*) (incf *local-count*) (format t "~s ~d ~d ~d~%" *current-process* i *local-count* *count*)) (process-wait-with-timeout "Sleeping" 2 #'(lambda () nil)))) :name "Counter 2")) ;; This process makes a local binding of *local-count*. (make-process #'(lambda () (let ((*local-count* 0)) (dotimes (i (* 10 scale)) (with-lock-held (*output-lock* "Waiting for output lock") (incf *count*) (incf *local-count*) (format t "~s ~d ~d ~d~%" *current-process* i *local-count* *count*) (finish-output)) (process-wait-with-timeout "Sleeping" 2 #'(lambda () nil))))) :name "Counter 1") ;; Recursively interrupted processes. (let (;; Setup three sleepers that will be interrupted. (ps1 (make-process #'(lambda () (process-wait-with-timeout "Sleeping" (* 20 scale) #'(lambda () nil))) :name "Sleeper 1")) (ps2 (make-process #'(lambda () (process-wait-with-timeout "Sleeping" (* 20 scale) #'(lambda () nil))) :name "Sleeper 2")) (ps3 (make-process #'(lambda () (process-wait-with-timeout "Sleeping" (* 20 scale) #'(lambda () nil))) :name "Sleeper 3")) interrupt) (setq *int-count* 0) (setq interrupt #'(lambda () (with-lock-held (*output-lock* "Waiting for output lock") (format t "Process ~s interrupted ~d~%" *current-process* *int-count*) (finish-output)) (incf *int-count*) (process-wait-with-timeout "Sleeping" 1 #'(lambda () nil)) (cond ((eq *current-process* ps1) (process-interrupt ps2 interrupt)) ((eq *current-process* ps2) (process-interrupt ps3 interrupt)) ((eq *current-process* ps3) (process-interrupt ps1 interrupt))))) ;; Start the ball rolling. (process-interrupt ps1 interrupt)) ;; Have the initial process do some work also. (work (* 10 scale) "Init. working")) (defun tst-comp () (start-sigalrm-yield 0 50000) ;; Try compiling two files simultaneously. (make-process #'(lambda () (compile-file "irrat" :error-output t :byte-compile nil :trace-file t)) :name "Compile irrat") (make-process #'(lambda () (compile-file "numbers" :error-output t :byte-compile nil :trace-file t)) :name "Compile numbers")) (defvar *tst-lock* (make-lock "Test lock")) (defun tst-lock () (declare (optimize (speed 3) (safety 0))) (let ((sum 0)) (declare (fixnum sum)) (dotimes (i 10000000) (declare (fixnum i)) (with-lock-held (*tst-lock* "Waiting for test lock") (incf sum))) sum)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun tst-alien-stack-save () (alien:with-alien ((buf (array char 256))) (format t "~s~%" buf) (multiple-value-bind (save-stack size alien-stack) (save-alien-stack (make-array 0 :element-type '(unsigned-byte 32))) (restore-alien-stack save-stack size alien-stack)) (format t "~s~%" buf))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Test the stack-group switching speed. (defun tst-sg-speed () (init-stack-groups) (let* ((num-switch 0) t1 t2) (declare (fixnum num-switch) (type (or stack-group null) t1 t2)) (setq t1 (make-stack-group "T1" #'(lambda () (dotimes (i 100000) (declare (fixnum i)) (incf num-switch) (stack-group-resume t2))))) (setq t2 (make-stack-group "T2" #'(lambda () (dotimes (i 100000) (declare (fixnum i)) (incf num-switch) (stack-group-resume t1))))) (stack-group-resume t1) (inactivate-stack-group t1) (inactivate-stack-group t2) num-switch))