#| -*- Mode:LISP; Package:(FCALL global); Fonts:(cptfontb); Base:10 -*- Copyright LISP Machine, Inc. 1985 See filename "Copyright" for licensing and release information. A self-contained example of how to build an interprocessor FORTRAN-CALL library mechanism. 5/18/85 13:16:28 -George Carrette The goal would be to have a system such that if, We define: (DEFINE-FORTRAN-LIBRARY "documentation" :executable-image "/tmp/streamlib" :subroutine (FFT (X :REAL-ARRAY) (Y :REAL-ARRAY) (N :INTEGER)) :subroutine (IFT (X :REAL-ARRAY) (Y :REAL-ARRAY) (N :INTEGER))) Then: (GENERATE-MAIN-PROGRAM ') ==> causes a main program in "C" to be generated. (GENERATE-EXECUTABLE ') ==> compiles the "C" program and calls the linker. Before a subroutine may be called there must be an executable program started up on the connected shared processor, to do this we would instantiated a job, duty or processing thread, whatever you want to call it. Prequisite to run: (SETQ *ABLE* (INSTANTIATE-FORTRAN-LIBRARY ')) To run: (SETQ X (MAKE-ARRAY 16 :ELEMENT-TYPE 'FLOAT)) (SETQ Y (MAKE-ARRAY 16 :ELEMENT-TYPE 'FLOAT)) (SEND *ABLE* :CALL "FFT" X Y 16) A simple :CALL such as the above would not return until the computation on the coprocessor had completed. However, that need not be the case, we could say instead: (SEND *ABLE* :CO-CALL "FFT" X Y 16) Which would return immediately. To found out when the computation is complete and the values in the arrays X and Y are consistent we send a message: (EQ (SEND *ABLE* :STATE) :DONE) => true if the computation is complete. An example of doing two things at the same time would be: (LET ((DONE NIL)) (PROCESS-RUN-FUNCTION "symbolic way" #'(lambda () (find-the-answer-using-AI-techniques X Y Z) (setq done t))) (SEND *ABLE* :CO-CALL "SOLVE9" X Y Z) (PROCESS-WAIT "thinking" #'(lambda () (or done (eq :done (send *able* :STATE))))) (if done (format t "The answer was found first using AI techniques") (format t "The answer was found first using the old FORTRAN numerical way"))) Implementation: fort.h A file of declarations shared by all files. fmain.c The main subroutine, which opens the syncronizing device, initializes pointers to shared memory, and calls fexec for each subroutine call request. fexec.c This gets an opcode from shared memory, and calls fload to get a link (dynamically or statically) to a FORTRAN subroutine. It then sets up an argument vector by normalizing the arglist in shared memory by the base of shared memory in the user process. The linked FORTRAN subroutine is then called, and a shared memory location is set to zero to indicate completion of the subroutine. fload.c In our example this is a static linking of an array of pointers to FORTRAN subroutines. ftable.c The dispatch table of FORTRAN subroutines. flib.f The fortran subroutines. Note: In the example we do not address the issue of interlocking of shared memory resources such as would be needed with multiple instantiations of fortran libraries in or across lambda processors. |# (defmacro DEFINE-FORTRAN-LIBRARY (name documentation &rest body) `(*define-fortran-library ',name ,documentation ',body)) (defun *define-fortran-library (name documentation arguments) (when (record-source-file-name name 'define-fortran-library) (setf (function-documentation name 'fortran-library) documentation) (do ((l arguments (cddr l)) (plist)) ((null l) (setf (get name 'fortran-library) plist)) (ecase (car l) ((:unix-filename-append :executable-image :link-table :files) (setf (getf plist (car l)) (cadr l))) (:subroutine (let ((subroutine (if (atom (cadr l)) (list (cadr l)) (cadr l)))) (push subroutine (getf plist :subroutines)))))))) (defflavor fortran-image ((name nil) sync-stream (processor nil) eval-server-process (eval-server-output "") command return-values being-called (flushed nil) opcode-pointer) () :initable-instance-variables :settable-instance-variables) (defmethod (fortran-image :print-self) (stream &rest ignored) (format stream "#" name processor eval-server-output)) (defun instantiate-fortran-library (name &optional (port-number 5)) ;; in fact the port number should be auto incremented (check-arg name (fortran-libraryp name) "a fortran library") (let ((port (open (format nil "UNIX-STREAM-~D:" port-number))) (proc (attached-unix-host)) (os (make-array 200 :fill-pointer 0 :element-type 'string-char)) (command (format nil "~a //dev//ttyl~D" (send (fortran-library-pathname name :executable-image) :string-for-host) port-number))) (let ((obj (make-instance 'fortran-image :name name :sync-stream port :processor proc :eval-server-output os :command command))) (send obj :start-process) (process-wait "unix startup" #'(lambda (x) (> (length (send x :eval-server-output)) (length (send x :command)))) obj) obj))) (defmethod (fortran-image :start-process) () (setq eval-server-process (process-run-function (format nil "fortran library ~A" name) #'fortran-image-simple-unix-eval processor command (make-string-output-stream eval-server-output) self))) (defun fortran-image-simple-unix-eval (processor command output done) (simple-unix-eval processor command output) (send done :set-flushed t)) (defvar *shared-memory-type-array* ()) (defvar *value-free-pointer* 0) (defun allocate-value-space () ;; this is where interlocking between processors might happen. (or (and *shared-memory-type-array* (= (length *shared-memory-type-array*) (quotient (length si:*global-shared-memory-16*) 2))) (setq *shared-memory-type-array* (make-array (quotient (length si:*global-shared-memory-16*) 2)))) (setq *value-free-pointer* 0)) (defsetf read-value write-value) (defun read-value (location type) "Read a value and do representation conversion" (let ((n (dpb (aref si:*global-shared-memory-16* (1+ (ash location 1))) #o2020 (aref si:*global-shared-memory-16* (ash location 1))))) (ecase type (:real (68000-float->lispm n)) (:integer (signed-68000->lispm n)) (nil n)))) (defun write-value (location type value) "Write a value of a given type into the shared location, doing representation conversion for types of :REAL and :INTEGER." (let ((n (ecase type (:real (lispm-float->68000 value)) (:integer (signed-lispm->68000 value))))) (setf (aref si:*global-shared-memory-16* (ash location 1)) (ldb #o0020 n)) (setf (aref si:*global-shared-memory-16* (1+ (ash location 1))) (ldb #o2020 n)) (setf (aref *Shared-memory-type-array* location) type))) (defun push-value (value type) (setf (read-value *value-free-pointer* type) value) (incf *value-free-pointer*)) (defun read-known-value (location) (read-value location (aref *shared-memory-type-array* location))) (defun dump-value-space (&optional (end (length *shared-memory-type-array*))) (format t "~&Location Type Value~%") (dotimes (j end) (let ((type (aref *shared-memory-type-array* j))) (format t "~4D ~7A ~S~%" J TYPE (read-value j type))))) (defmethod (fortran-image :co-call) (subroutine &rest arguments) (let* ((subroutines (getf (get name 'fortran-library) :subroutines)) (desc (ass #'string-equal subroutine subroutines))) (or desc (ferror nil "No subroutine called ~A in fortran library ~A" subroutine name)) (setq being-called desc) (let ((nargs (length arguments)) (opcode (1+ (find-position-in-list desc subroutines))) (argspointer)) (or (= nargs (length (cdr desc))) (ferror nil "Fortran subroutine ~A expects ~D argument~p, got ~D" subroutine (length (cdr desc)) (length (cdr desc)) nargs)) (allocate-value-space) (setq opcode-pointer *value-free-pointer*) (push-value opcode :integer) (push-value nargs :integer) (setq argspointer *value-free-pointer*) (incf *value-free-pointer* nargs) (setq return-values nil) (do ((actuals arguments (cdr actuals)) (formals (cdr desc) (cdr formals)) (j 0 (1+ j))) ((null actuals)) (let ((pointer *value-free-pointer*) (datatype (cadr (car formals))) (data (car actuals))) (if (typep data 'sequence) (push (list data datatype pointer) return-values)) (setf (read-value (+ argspointer j) :integer) pointer) (ecase datatype ((:real :integer) (push-value (if (typep data 'sequence) (elt data 0) data) datatype)) ((:real-array :integer-array) (let ((element-type (cadr (assq datatype '((:real-array :real) (:integer-array :integer)))))) (dotimes (k (length data)) (push-value (elt data k) element-type))))))) (send sync-stream :tyo #/X) (send sync-stream :tyo 13) (send sync-stream :force-output) ))) (defmethod (fortran-image :stop) () (cond ((not flushed) (send sync-stream :tyo #/S) (send sync-stream :tyo 13) (send sync-stream :force-output) (process-wait "unix to stop" #'(lambda (x) (send x :flushed)) self) (close sync-stream) (setq sync-stream nil) (setq eval-server-process nil))) self) (defmethod (fortran-image :state) () (if (zerop (read-value opcode-pointer :integer)) :done :computing)) (defmethod (fortran-image :call) (subroutine &rest arguments) (lexpr-send self :co-call subroutine arguments) (process-wait "computing" #'(lambda (x) (eq :done (send x :state))) self) (send self :get-return-values)) (defmethod (fortran-image :get-return-values) () (dolist (item return-values) (let ((sequence (car item)) (pointer (caddr item))) (dotimes (j (length sequence)) (setf (elt sequence j) (read-known-value (+ pointer j))))))) (compile-flavor-methods fortran-image) (defun simple-unix-eval (host command &optional (stream standard-output)) (with-open-stream (s (chaos:open-stream host (format nil "EVAL ~a" command))) (format stream "~&% ~A~%" command) (do ((c (send s ':tyi) (send s ':tyi))) ((null c)) (send stream ':tyo (selectq c ((#o12 #o15) #\return) (#o11 #\tab) (t c)))))) (defun attached-unix-host () "Returns host object for attached unix-host if it exits otherwise NIL" ;; Relevant variable: ;; si:*other-processors* list of structures of type SI:OTHER-PROCESSOR (dolist (op si:*other-processors*) (let ((host (SI:GET-HOST-FROM-ADDRESS (si:%processor-conf-chaos-address (si:op-proc-conf op)) ':CHAOS))) (if (typep host 'fs:unix-host) (return host))))) ;; data conversion: ;; In the lisp environment all 68000 data is manipulated as 32 bit integers. (defun 68000-byteswap (integer) (dpb (ldb #o0010 integer) #o3010 (dpb (ldb #o1010 integer) #o2010 (dpb (ldb #o2010 integer) #o1010 (ldb #o3010 integer))))) (defun signed-lispm->68000 (integer) (68000-byteswap integer)) (defun signed-68000->lispm (integer) (let ((n (68000-byteswap integer))) (if (bit-test 1_31 n) (- n 1_32) n))) (defun 68000-float->lispm (x) "Take 32bits, a 68000 float, and return a lisp float object" (// (* (expt -1 (ldb #o3701 x)) (expt 2.0 (- (ldb #o3007 x) #o100)) (+ (ldb #o2010 x) (ash (+ (ldb #o1010 x) (ash (ldb #o0010 x) 8)) 8))) #o100000000)) (defun lispm-float->68000 (x &aux sign exp frac) "Take a lispmachine floating point number and return 32 bits suitable for the 68000" (cond ((zerop x) (return-from lispm-float->68000 0)) ((< x 0.0) (setq sign 1) (setq x (- x))) ('else (setq sign 0))) (etypecase x (short-float (setq exp (+ (- (si:%short-float-exponent x) #o200) #o100)) (setq frac (ash (si:%short-float-mantissa x) (- 23 16)))) (single-float (setq exp (+ (- (si:%single-float-exponent x) #o2000) #o100)) (setq frac (ash (si:%single-float-mantissa x) (- 23 30))))) (if (or (< exp 0) (> exp #o177)) (ferror nil "Exponent too big to represent in 68000: ~S" x)) (dpb sign #o3701 (dpb exp #o3007 (dpb (ldb #o0010 frac) #o2010 (dpb (ldb #o1010 frac) #o1010 (ldb #o2010 frac)))))) ;; generation of code and linking. (defvar *udir* "//tmp//" "Where temp. files go") (defun fortran-libraryp (name) (get name 'fortran-library)) (defun fortran-library-pathname (name type &optional (to-host (attached-unix-host))) (let ((p (fortran-libraryp name))) (let ((l (getf p type)) (a (getf p :unix-filename-append))) (if (and (symbolp a) (boundp a)) (setq a (symeval a))) (cond ((atom l) (fs:parse-pathname (if a (string-append a l) l) to-host)) ('else (mapcar #'(lambda (x) (fs:parse-pathname (if a (string-append a x) x) to-host)) l)))))) (defun generate-main-program (name &aux plist subroutines) "generates the link table for fortran library NAME" (check-arg name (setq plist (fortran-libraryp name)) "a fortran library") (setq subroutines (mapcar #'(lambda (x) (if (> (length (cdr x)) 9) (ferror nil "Too many arguments in subroutine: ~S" x)) (string-downcase (car x))) (getf plist :subroutines))) (with-open-file (s (fortran-library-pathname name :link-table) :out) (format s "~ ~70,,,'*~ ~%~70< * Automatically generated C code for the FORTRAN link ~;*~>~ ~%~70< * Compiled from ~S by ~S ~;*~>~ ~%~70< * ~A ~;*~>~ ~%~71,,,'*< *~;//~>~ ~%~%#include /"fort.h/"~2%~ ~%~{int ~a();~%~}~ ~%int (*ftable[])() = {noop,~%~{ ~a~^,~%~}};~%" name si:user-id (time:print-current-date nil) subroutines subroutines ))) (defun generate-executable (name) (check-arg name (fortran-libraryp name) "a fortran library") (flet ((hosty (x) (send x :string-for-host))) (simple-unix-eval (attached-unix-host) (format nil (string-append "cd " *udir* ";" "cc -o ~a ~{~a ~} ~a -lshare -lF77 -lI77") (hosty (fortran-library-pathname name :executable-image)) (mapcar #'hosty (fortran-library-pathname name :files)) (hosty (fortran-library-pathname name :link-table)))))) ;; the example (define-fortran-library example "an example" :subroutine (VADD (X :real-array) (y :real-array) (z :real-array) (n :integer)) :subroutine (vmult (X :real-array) (y :real-array) (z :real-array) (n :integer)) :subroutine (vprint (x :real-array) (n :integer)) :unix-filename-append *udir* :executable-image "example" :link-table "ftable.c" :files ("fmain.o" "fload.o" "fexec.o" "flib.o")) (defun setup-unix-source-files (&optional (h (string-append (send (attached-unix-host) :name) ":"))) (let (ofile) (unwind-protect (with-open-file (s (send (si:get-source-file-name 'setup-unix-source-files ) :new-pathname :type "LISP" :version :NEWEST)) (do ((line)) ((null (setq line (readline s nil)))) (cond ((string-equal "Filename:" line :end2 (length "Filename:")) (when ofile (close ofile) (setq ofile nil)) (setq line (string-append h *udir* (string-trim '(#\space) (substring line (length "Filename:"))))) (print line) (setq ofile (open line :direction :Output))) ((string-equal "||#" line :end2 (length "||#")) (when ofile (close ofile) (setq ofile nil))) (ofile (princ line ofile) (terpri ofile) (princ "."))))) (and ofile (close ofile))))) #|| To run the example, create a Unix login for "anonymous" (if necessary) and put the files fmain.c, fload.c, fexec.c flib.f in its directory (generally, /tmp; if not, change '/tmp' throughout this file, save it, and recompile it). You can copy the files by running (SETUP-UNIX-SOURCE-FILES). Then generate ftable.c with the command (from lisp): (generate-main-program 'example) Set the protection on compile.sh: # chmod +rwx /tmp/compile.sh Then run the Unix command '% sh compile.sh', which does at least: cc -c fmain.c cc -c fload.c cc -c fexec.c f77 -c flib.f Or you can try (SIMPLE-UNIX-EVAL "" "//tmp//compile.sh"). Everything may be linked together with the command (from lisp): (generate-executable 'example) Then instantiate a runnable image of the library: (SETQ LIB (INSTANTIATE-FORTRAN-LIBRARY 'EXAMPLE)) (setq x '(1.0 2.0 3.0)) (setq y '(1.0 2.0 3.0)) (setq z '(0.0 0.0 0.0)) (SEND LIB :CALL "VMULT" X Y Z 3) Then look at Z. The C and fortran code follows: Filename: fort.h /************************************** * Copyright LISP Machine, Inc. 1985 * * See filename "Copyright" for * * licensing and release information. * **************************************/ int noop(); int (*(fload()))(); int (*ftable[])(); Filename: fmain.c /************************************** * Copyright LISP Machine, Inc. 1985 * * See filename "Copyright" for * * licensing and release information. * **************************************/ #include #include main(argc,argv) int argc; char **argv; { int n,chan; char c; if (argc != 2) {printf("\n%s \n",argv[0]);exit(1);} chan = open(argv[1],2); if (chan < 0) {printf("\ncouldnt open syncronizing device: %s\n",argv[1]); exit(1);} if (share_setup() < 0) {printf("share_setup failed");exit(1);} while(1) { n = read(chan,&c,1); if (n != 1) {printf("\nsyncronizing device read failed\n");exit(1);} if ( c == 'S') {printf("\nbeen told to stop\n");exit(1);} if ( c == 'X') fexec(sharebase);}} Filename: fexec.c /************************************** * Copyright LISP Machine, Inc. 1985 * * See filename "Copyright" for * * licensing and release information. * **************************************/ /* execute a subroutine call out of arguments passed in the heap. All arguments are call-by-reference, which is FORTRAN style. Heap format: 0 [Opcode] 1 [Number-Of-Arguments] 2 [ARG1] 3 [ARG2] ... 9 [ARG9] 10 begining of actual argument value storage heap */ #include "fort.h" #include fexec(heap) int *heap; { int (*func)(), v[9]; func = fload(heap[0]); setargs(heap,&heap[2],v,heap[1]); switch (heap[1]) { case 0: (*func)(); break; case 1: (*func)(v[0]); break; case 2: (*func)(v[0],v[1]); break; case 3: (*func)(v[0],v[1],v[2]); break; case 4: (*func)(v[0],v[1],v[2],v[3]); break; case 5: (*func)(v[0],v[1],v[2],v[3],v[4]); break; case 6: (*func)(v[0],v[1],v[2],v[3],v[4],v[5]); break; case 7: (*func)(v[0],v[1],v[2],v[3],v[4],v[5],v[6]); break; case 8: (*func)(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]); break; case 9: (*func)(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7],v[8]); break; default: break;} heap[0] = 0;} setargs(absbase,reloffsets,v,n) int *absbase, *reloffsets, **v,n; { int j; for (j = 0; j