Re: Unboxed float arguments
Marco Antoniotti <[email protected]> Sun, 10 Jun 2012 08:39:21 +0200
| Newsgroups | gmane.lisp.cmucl.devel |
|---|---|
| Message-ID | <[email protected]> |
Cool. I have not really looked at the code, but how generalizable would this approach be? I.e. why stop at FTYPEd floats? I am asking because of Pascal Costanza's recent post on the PRO mailing list about stack-allocation of values (a DYNAMIC-EXTENT on steroids) Cheers -- Marco On Jun 9, 2012, at 09:46 , Helmut Eller wrote: > Here is an idea for a calling convention that supports unboxed floats: > > 1. When DEFUN compiles a function that has a ftype declaration involving > floats, it creates a special entry-point that accepts unboxed arguments. > > 2. Named calls to such functions are compiled so that the arguments are > represented as indicated by the ftype declaration. > > 3. We also keep a linker table to connect those special entry-points to > call-sites. When the function gets redefined, we go through the > existing call-sites and patch them so that the new definition is called. > If the type of the new definition is different than the one expected by > the call-site, we create an "adapter function" that converts the > representation to the new type or if the conversion is not possible > signals an error. > > Attached is a prototype implementation to illustrate how this could be > done. See the comments in the file for the main points. A more > polished implementation would probably change more places then > MAKE-XEP-LAMBDA. > > What do you think: is such a calling convention worth having? > > Helmut > > > ;; Toy implementation for a calling convention with unboxed arguments. > > (in-package cl-user) > > ;; Conceptionally we'd like to create functions that have two entry > ;; points: one that accepts only arguments of a specific > ;; type/representation and a general entry point that accept all > ;; possible types as a fallback. > ;; > ;; A "typed entry point" is an entry point that only accepts arguments > ;; of that specfic type and makes no typechecks. There is a direct > ;; mapping from the function's type to the representation of the > ;; arguments and return values. So if we know the type, e.g. from a > ;; ftype declaration, we can predict the representation. > ;; > ;; This implementation actually uses two functions instead of one > ;; function with two entry points. > > ;; We use function names like (:typed-entry-point foo) for functions > ;; which have a typed entry point. > (ext:define-function-name-syntax :typed-entry-point (name) > (ext:valid-function-name-p (cadr name))) > > ;; A callsite describes what we need to know to link/re-link a call to > ;; a typed entry point. The TYPE slot is the type of the callsite. We > ;; use FDEFN to call the entry. In theory, we could patch the call > ;; instruction with the correct address of the entry point, but code > ;; patching is always tricky, so for now we use an indirection through > ;; fdefn objects and only patch the address in the fdefn. > ;; > ;; When a function gets redefined we patch existing callsites so that > ;; the new function gets called. We record the type of the callsite > ;; so that we can verify that the type of the new definition matches > ;; existing callsites. If the types match, we simply patch the fdefn > ;; and are done. If the types don't match we generate an "adapter > ;; function". The job of an adapter is it to take arguments of some > ;; particular type and box/unbox them so that it becomes possible to > ;; call the new definition. The current adapter implementation would > ;; sometimes create endless recursions, so we have and ADAPT flag to > ;; disable the creation of adapters for some callsites. > ;; > (defstruct callsite > (type (ext:required-argument) :type kernel:function-type :read-only t) > (fdefn (ext:required-argument) :type kernel:fdefn :read-only t) > (adapt (ext:required-argument) :type boolean :read-only t)) > > (defstruct linkage > (callsites nil :type (or callsite list)) > (adapters nil :type (or function list))) > > ;; For functions with typed entry points we maintiain a list of > ;; callsites and record that in the info db. > ;; > ;; If we use typed entry points only for functions with ftype > ;; declarations then this table is probably relatively small. > (ext:define-info-type function linkage linkage) > > (defun listify (x) > (if (listp x) x (list x))) > > (defmacro push-unlistified (new-value (reader object)) > `(let ((new-value ,new-value) (object ,object)) > (let ((old-value (,reader ,object))) > (setf (,reader object) > (typecase old-value > (null new-value) > (cons (cons new-value old-value)) > (t (list new-value old-value))))))) > > ;; find-typed-entry-point is called at load-time and returns the > ;; fdefn that should be called. > ;; > ;; 1. We go through the list of existing callsites to see if we > ;; already have one with the same type and reuse it if possible. > ;; > ;; 2. We look at the current definition. If the types match, we > ;; create a callsite object, store it in the info db, and return the > ;; fdefn. > ;; > ;; 3. Now we know that the types don't match we need to use adapters. > ;; First again, we look at existing adapters and reuse them if possible. > ;; > ;; 4. An adapter is created that boxes the arguments and forwards the > ;; call to the "normal" entry point. > ;; > ;; 5. If we are not allowed to create adapters, we look again at the > ;; current definition to handle the case where no current definition > ;; exists. If so, we return an empty fdefn object that will call the > ;; undefined-tramp assembly routine. > ;; > ;; 6. If all else fails we link the callsite to our error handler. > ;; > (declaim (ftype (function (t t boolean) kernel:fdefn) find-typed-entry-point)) > (defun find-typed-entry-point (name callsite-typespec adapt) > (let* ((cs-type (kernel:specifier-type callsite-typespec)) > (linkage (multiple-value-bind (info foundp) > (ext:info function linkage name) > (cond (foundp info) > (t (setf (ext:info function linkage name) > (make-linkage))))))) > (cond ((dolist (cs (listify (linkage-callsites linkage))) > (let* ((ep-type (callsite-type cs))) > (when (and (function-types-compatible-p cs-type ep-type) > (eq (callsite-adapt cs) adapt)) > (return (callsite-fdefn cs)))))) > ((let ((fdefn (lisp::fdefinition-object name nil))) > (when fdefn > (let ((fun (kernel:fdefn-function fdefn))) > (when fun > (let ((ep-type (kernel:extract-function-type fun))) > (when (function-types-compatible-p cs-type ep-type) > (let* ((aname (kernel:%function-name fun)) > (fdefn (kernel:make-fdefn aname)) > (cs (make-callsite :type cs-type :fdefn fdefn > :adapt adapt))) > (setf (kernel:fdefn-function fdefn) fun) > (push-unlistified cs (linkage-callsites linkage)) > fdefn)))))))) > ((and adapt > (dolist (fun (listify (linkage-adapters linkage))) > (let ((ep-type (kernel:extract-function-type fun))) > (when (function-types-compatible-p cs-type ep-type) > (let* ((aname (kernel:%function-name fun)) > (fdefn (kernel:make-fdefn aname)) > (cs (make-callsite :type cs-type :fdefn fdefn > :adapt adapt))) > (setf (kernel:fdefn-function fdefn) fun) > (push-unlistified cs (linkage-callsites linkage)) > (return fdefn))))))) > ((and adapt > (let* ((fun (generate-adapter-function cs-type (second name))) > (fdefn (kernel:make-fdefn (kernel:%function-name fun))) > (cs (make-callsite :type cs-type :fdefn fdefn > :adapt adapt))) > (setf (kernel:fdefn-function fdefn) fun) > (push-unlistified fun (linkage-adapters linkage)) > (push-unlistified cs (linkage-callsites linkage)) > fdefn))) > ((and (not adapt) > (or (not (lisp::fdefinition-object name nil)) > (not (kernel:fdefn-function > (lisp::fdefinition-object name nil))))) > (let* ((aname `(:typed-entry-point #:undefined)) > (fdefn (kernel:make-fdefn aname)) > (cs (make-callsite :type cs-type :fdefn fdefn :adapt adapt))) > (push-unlistified cs (linkage-callsites linkage)) > fdefn)) > (t > (let* ((fun (generate-adapter-function cs-type 'linkage-error)) > (fdefn (kernel:make-fdefn (kernel:%function-name fun))) > (cs (make-callsite :type cs-type :fdefn fdefn :adapt adapt))) > (setf (kernel:fdefn-function fdefn) fun) > (push-unlistified cs (linkage-callsites linkage)) > fdefn))))) > > (defun linkage-error (&rest args) > (declare (ignore args)) > (error "Linking callsite to typed-entry-point failed")) > > ;; Generate an adapter function that changes the representation of the > ;; arguments (specified with FTYPE) and forwards the call to NAME. > ;; The adapter has also a typed entry point. It should also check > ;; that the values returned by NAME match FTYPE. > ;; > ;; In practice, the compiler infered type may not match exactly FTYPE, > ;; even if we add lotso declarations. This is annyoingly brittle. > (defun generate-adapter-function (ftype name) > (let* ((atypes (kernel:function-type-required ftype)) > (tmps (loop for nil in atypes collect (gensym))) > (fname `(:typed-entry-point > :boxing-adapter ,(make-symbol (string name)))) > (ftypespec (kernel:type-specifier ftype))) > (proclaim `(ftype ,ftypespec ,fname)) > (compile fname > `(lambda ,tmps > (declare > ,@(loop for tmp in tmps > for type in atypes > collect `(type ,(kernel:type-specifier type) ,tmp))) > (the ,(kernel:type-specifier > (kernel:function-type-returns ftype)) > (funcall (function ,name) . ,tmps)))) > (let ((fun (fdefinition fname))) > (unless (eq name 'linkage-error) > (fix-ftype fun ftype)) > fun))) > > (defun fix-ftype (fun ftype) > (let ((etype (kernel:extract-function-type fun))) > (unless (function-types-compatible-p ftype etype t) > (break))) > fun) > > ;; This is our rule to decide when a type at a callsite matches the > ;; type of the entry point. > ;; > ;; 1. The arguments at the callsite should be subtypes of the > ;; arguments at the entry point. > ;; > ;; 2. The return value at the callsite should be supertypes of the > ;; return values at the entry point. > ;; > ;; 3. The representations must agree. Representations should probably > ;; decided in the backend, but for now we assume only double-floats > ;; are unboxed. > (defun function-types-compatible-p (callsite-type entrypoint-type > &optional ignore-representation) > (flet ((return-types (ftype) > (let ((type (kernel:function-type-returns ftype))) > (cond ((kernel:values-type-p type) > (assert (and (not (kernel:values-type-rest type)) > (not (kernel:values-type-keyp type)))) > (kernel:values-type-required type)) > (t > (list type))))) > (ptype= (type1 type2) > (let ((double-float (kernel:specifier-type 'double-float))) > (cond (ignore-representation t) > ((kernel:type= type1 double-float) > (kernel:type= type2 double-float)) > ((kernel:type= type2 double-float) > nil) > (t t))))) > (and (every #'kernel:csubtypep > (kernel:function-type-required callsite-type) > (kernel:function-type-required entrypoint-type)) > (every #'ptype= > (kernel:function-type-required callsite-type) > (kernel:function-type-required entrypoint-type)) > (or > (and (every #'kernel:csubtypep > (return-types entrypoint-type) > (return-types callsite-type)) > (every #'ptype= > (return-types entrypoint-type) > (return-types callsite-type))) > (kernel:type= (kernel:function-type-returns entrypoint-type) > (kernel:specifier-type 'nil)))))) > > > ;; check-function-redefinition is used as setf-fdefinition-hook. > ;; We go through all existing callsites and > ;; > ;; 1. If the new type matches, we patch the callsite with the new function. > ;; > ;; 2. If the types don't match and if allowed, we redirect the > ;; callsite to and adapter. > ;; > ;; 3. If the callsites doesn't want adapters we link the callsite to > ;; an error handler. > (defun check-function-redefinition (name new-fun) > (when (and (consp name) > (eq (car name) :typed-entry-point) > (not (eq (cadr name) :boxing-adapter))) > (multiple-value-bind (linkage foundp) (ext:info function linkage name) > (when foundp > (let ((new-type (kernel:extract-function-type new-fun))) > (dolist (cs (listify (linkage-callsites linkage))) > (let ((cs-type (callsite-type cs)) > (fdefn (callsite-fdefn cs))) > (cond ((function-types-compatible-p cs-type new-type) > (patch-fdefn fdefn new-fun)) > ((and > (callsite-adapt cs) > (dolist (fun (listify (linkage-adapters linkage))) > (let ((ep-type (kernel:extract-function-type fun))) > (when (function-types-compatible-p cs-type ep-type) > (patch-fdefn fdefn fun) > (return t)))))) > ((callsite-adapt cs) > (let ((fun (generate-adapter-function > cs-type (second name)))) > (push-unlistified fun (linkage-adapters linkage)) > (patch-fdefn fdefn fun))) > (t > (format nil "~3t") > (warn "New type of ~s incompatible with callsite:~%~ > ~2t new-type: ~s~%~ > ~2t cs-type: ~s" name > (kernel:type-specifier new-type) > (kernel:type-specifier cs-type)) > (let ((fun (generate-adapter-function > cs-type 'linkage-error))) > (patch-fdefn fdefn fun))))))))))) > > ;; This lets us set the name in fdefn objects. We use that for > ;; debugging. > (eval-when (:compile-toplevel) > (c:defknown set-fdefn-name (kernel:fdefn t) t) > (c:def-setter set-fdefn-name vm:fdefn-name-slot vm:other-pointer-type)) > > (defun patch-fdefn (fdefn new-fun) > (setf (kernel:fdefn-function fdefn) new-fun) > (let ((name (kernel:%function-name new-fun))) > (set-fdefn-name fdefn name)) > fdefn) > > (pushnew 'check-function-redefinition ext:*setf-fdefinition-hook*) > > (in-package x86) > > ;; make-typed-call-tns chooses the representation for a function type. > ;; This is similar to c::make-call-out-tns and should probably also be > ;; a vm-support-routine. > ;; > ;; The current convention passes double-floats unboxed and all other > ;; types remain boxed. Registers XMM4-XMM7 are used for the first 4 > ;; double arguments. Boxed values are passed in standard locations. > ;; > ;; Returning values on the stack is currenlty not implemented, so all > ;; return values must fit in registers. > (defun make-typed-call-tns (ftype) > (declare (type function-type ftype)) > (labels ((ptype (name) (primitive-type-or-lose name *backend*)) > (double-float-arg (state) > (cond ((<= (getf state :xmms-reg) xmm7-offset) > (make-wired-tn (ptype 'double-float) > double-reg-sc-number > (prog1 (getf state :xmms-reg) > (incf (getf state :xmms-reg))))) > (t > (make-wired-tn (ptype 'double-float) > double-stack-sc-number > (prog1 (getf state :frame-size) > (incf (getf state :frame-size) 2)))))) > (boxed-arg (state) > (cond ((<= (getf state :reg-args) register-arg-count) > (let ((n (getf state :reg-args))) > (incf (getf state :reg-args)) > (x86-standard-argument-location n))) > (t > (make-wired-tn (ptype 't) > control-stack-sc-number > (prog1 (getf state :frame-size) > (incf (getf state :frame-size) 1)))))) > (double-float-type-p (type) > (and (numeric-type-p type) > (eq (numeric-type-class type) 'float) > (eq (numeric-type-format type) 'double-float))) > (arg-tn (type state) > (cond ((double-float-type-p type) (double-float-arg state)) > (t (boxed-arg state)))) > (ret-tn (type state) > (let ((tn (arg-tn type state))) > (assert (member (sc-name (tn-sc tn)) > '(double-reg descriptor-reg))) > tn))) > (let* ((arg-state (list :frame-size 2 :xmms-reg xmm4-offset :reg-args 0)) > (ret-state (list :frame-size 2 :xmms-reg xmm4-offset :reg-args 0)) > (returns (function-type-returns ftype)) > (rtypes (typecase returns > (values-type (values-type-required returns)) > (t (list returns))))) > (values > (loop for type in (function-type-required ftype) > collect (arg-tn type arg-state)) > (loop for type in rtypes > collect (ret-tn type ret-state)) > (x86-make-stack-pointer-tn) > (max (getf arg-state :frame-size) > (getf ret-state :frame-size)) > (x86-make-number-stack-pointer-tn) > 0)))) > > ;; This VOP performs the call. Note the (:move-args :local-call) > ;; which, hopefully, coerces and moves arguments to the correct > ;; representation. > (define-vop (cl-user::call-typed-named) > (:args (new-fp) > (new-nfp) > (fdefn :scs (descriptor-reg control-stack) > :target eax) > (args :more t :scs (descriptor-reg))) > (:results (results :more t)) > (:save-p t) > (:move-args :local-call) > (:vop-var vop) > (:info arg-locs real-frame-size) > (:ignore new-nfp args arg-locs results) > (:temporary (:sc descriptor-reg :offset eax-offset) > eax) > (:generator 30 > ;; FIXME: allocate the real frame size here. We had to emit > ;; ALLOCATE-FRAME before this vop so that we can use the > ;; (:move-args :local-call) option here. Without the > ;; ALLOCATE-FRAME vop we get a failed assertion. > (inst lea esp-tn (make-ea :dword :base new-fp > :disp (- (* real-frame-size word-bytes)))) > > ;; Move fdefn to eax before switching frames. > (move eax fdefn) > > ;; Write old frame pointer (epb) into new frame. > (storew ebp-tn new-fp (- (1+ ocfp-save-offset))) > > ;; Switch to new frame. > (move ebp-tn new-fp) > > (note-this-location vop :call-site) > > ;; Load address out of fdefn and call it. > (inst call (make-ea :dword :base eax > :disp (- (* fdefn-raw-addr-slot word-bytes) > other-pointer-type))) > > )) > > (in-package c) > > ;; To generate typed entry points we modify MAKE-XEP-LAMBDA. > ;; Currently we use the function name as marker: if the name looks > ;; like (:typed-entry-point <foo>) then we know we need to our thing. > ;; > ;; We lookup the currently declared type and create wrap code around > ;; the main function. Our code receives and returns the values with > ;; typed calling convention. The n-supplied argument is not used, but > ;; the rest of the compiler want it to be there (generates no code > ;; thogh). > ;; > ;; FIXME: instead of using the function name as marker it might be > ;; better to have a declaration to request typed entry points. > ;; > ;; FIXME: %return-results-with-type is currently not inlined > ;; (let-converted) proberly and instead generates local-call. Getting > ;; the optimisations correctly dones also seems rather brittle. > (fwrappers:define-fwrapper make-xep-lambda-wrapper (fun) > (declare (type functional fun)) > (cond ((and (consp (functional-name fun)) > (eq (car (functional-name fun)) > :typed-entry-point)) > (let* ((ftype (the function-type > (info function type (functional-name fun)))) > (fspec (type-specifier ftype)) > (returns (function-type-returns ftype)) > (rtypes (typecase returns > (values-type (values-type-required returns)) > (t (list returns)))) > (n-supplied (gensym)) > (temps (loop for nil in (lambda-vars fun) collect (gensym))) > (results (loop for nil in rtypes collect (gensym)))) > `(lambda (,n-supplied) > (declare (ignore ,n-supplied)) > (multiple-value-bind ,temps > (cl-user::%receive-arguments-with-type ',fspec) > (multiple-value-bind ,results > (the ,(type-specifier returns) > (%funcall ,fun . ,temps)) > (cl-user::%return-results-with-type > ',fspec . ,results)))))) > (t > (fwrappers:call-next-function)))) > > (fwrappers:fwrap 'make-xep-lambda 'make-xep-lambda-wrapper) > > > ;; %receive-arguments-with-type knows how to access the arguments from > ;; the passing locations. > (defknown cl-user::%receive-arguments-with-type (cons) *) > > (defoptimizer (cl-user::%receive-arguments-with-type derive-type) ((ftypespec)) > (unless (constant-continuation-p ftypespec) > (error "Function-type must be constant.")) > (let ((ftype (the function-type > (specifier-type (continuation-value ftypespec))))) > (values-specifier-type > `(values . ,(mapcar #'type-specifier (function-type-required ftype)))))) > > ;; Here we generate the IR2 to receive arguments. make-typed-call-tns > ;; chooses the representation and move-continuation-result does the > ;; rest. > (defoptimizer (cl-user::%receive-arguments-with-type ir2-convert) > ((ftypespec) node block) > (let* ((ftype (the function-type > (specifier-type (continuation-value ftypespec)))) > (cont (node-cont node)) > (arg-tns (x86::make-typed-call-tns ftype))) > (move-continuation-result node block arg-tns cont))) > > (defknown cl-user::%return-results-with-type (cons &rest *) nil) > > (defoptimizer (cl-user::%return-results-with-type ltn-annotate) > ((type &rest args) node policy) > (dolist (arg args) > (annotate-ordinary-continuation arg policy))) > > ;; %return-results-with-type is similar to a known-return just that we > ;; let make-typed-call-tns choose the representation. > (defoptimizer (cl-user::%return-results-with-type ir2-convert) > ((type &rest args) node block) > (let* ((ftype (the function-type > (specifier-type (continuation-value type)))) > (home (lambda-home (lexenv-lambda (node-lexenv node)))) > (env (environment-info (lambda-environment home))) > (old-fp (ir2-environment-old-fp env)) > (return-pc (ir2-environment-return-pc env))) > (multiple-value-bind (arg-tns result-tns) (x86::make-typed-call-tns ftype) > (declare (ignore arg-tns)) > (let ((val-tns (loop for arg in args > collect (continuation-tn node block arg)))) > (vop* known-return node block > (old-fp return-pc (reference-tn-list val-tns nil)) > (nil) > result-tns))))) > > (defknown cl-user::%typed-call (cons &rest *) *) > > ;; %typed-call gets transformed to %%typed-call. We create the the > ;; load-time-value-form and some type declarations. > ;; > ;; FIXME: The :adapt option should probably be a separate argument, > ;; but for now it's encoded in the function name. > ;; > ;; FIXME: why is the truly-the still needed? Shouldn't > ;; load-time-value be able use the declared return type of > ;; find-typed-entry-point? > (deftransform cl-user::%typed-call ((name &rest args) > * * :important t) > (unless (constant-continuation-p name) > (error "Function name must be constant.")) > (destructuring-bind (&key typed-entry-point adapt) (continuation-value name) > (let* ((name typed-entry-point) > (ftype (the function-type > (info function type `(:typed-entry-point ,name)))) > (vars (loop for nil in (function-type-required ftype) > collect (gensym)))) > `(lambda (name ,@vars) > (declare (ignore name) > ,@(loop for type in (function-type-required ftype) > for var in vars > collect `(type ,(type-specifier type) ,var))) > (cl-user::%%typed-call > (locally (declare (optimize speed)) > (truly-the fdefn > (load-time-value (cl-user::find-typed-entry-point > '(:typed-entry-point ,name) > ',(type-specifier ftype) > ',adapt) > t))) > ',ftype ,@vars))))) > > (defknown cl-user::%%typed-call (fdefn function-type &rest *) *) > > (defoptimizer (cl-user::%%typed-call derive-type) ((fdefn type &rest args)) > (let ((ftype (continuation-value type))) > (function-type-returns ftype))) > > (defoptimizer (cl-user::%%typed-call ltn-annotate) > ((fdefn type &rest args) node policy) > (setf (basic-combination-info node) :funny) > (setf (node-tail-p node) nil) > (annotate-ordinary-continuation fdefn policy) > (dolist (arg args) > (annotate-ordinary-continuation arg policy))) > > ;; A typed-call we is similar to a known-local-call. > ;; make-typed-call-tns chooses the argument representation. > ;; > ;; 1. allocate a frame > ;; 2. moving/coercing the arguments and done the CALL-TYPED-NAMED vop > ;; 3. move-continuation-result moves the results in the right place > ;; > ;; FIXME: all return values are in registers move-continuation-result > ;; can probably not handle stack arguments. > ;; > ;; FIXME: the ALLOCATE-FRAME vop isn't quite right for us, see the > ;; comment in call-typed-named. > ;; > ;; FIXME: currently can't make tail calls. We would need to guarantee > ;; that the types match. > (defoptimizer (cl-user::%%typed-call ir2-convert) > ((fdefn type &rest args) node block) > (let ((ftype (the function-type (continuation-value type))) > (cont (node-cont node))) > (multiple-value-bind (arg-tns result-tns > fp stack-frame-size > nfp number-stack-frame-size) > (x86::make-typed-call-tns ftype) > (declare (ignore number-stack-frame-size)) > (let ((fdefn-tn (continuation-tn node block fdefn)) > (cont-tns (loop for arg in args > collect (continuation-tn node block arg)))) > (vop allocate-frame node block nil fp nfp) > (vop* cl-user::call-typed-named node block > (fp nfp fdefn-tn (reference-tn-list cont-tns nil)) > ((reference-tn-list result-tns t)) > arg-tns stack-frame-size) > (move-continuation-result node block result-tns cont))))) > ;; Tests for typed calling convention. > > (in-package :cl-user) > > (eval-when (:compile-toplevel) > (fmakunbound '(:typed-entry-point fid)) > (fmakunbound '(:typed-entry-point sum-prod)) > (fmakunbound '(:typed-entry-point cons-sum)) > (fmakunbound '(:typed-entry-point id))) > > ;; First we create a declaration > (declaim (ftype (function (double-float) double-float) > (:typed-entry-point fid))) > > ;; then the typed entry point > (defun (:typed-entry-point fid) (f) f) > > ;; then the "normal" entry point, which calls the typed version. > (defun fid (f) > (%typed-call '(:typed-entry-point fid :adapt nil) f)) > > ;; and also a compiler macro so that named calls automtomatically call > ;; the typed entry point. > (define-compiler-macro fid (f) > `(%typed-call '(:typed-entry-point fid :adapt t) ,f)) > > (defun test-fid-1 () > (assert (= (fid 1d0) 1d0))) > > ;; (fid 1d0) > ;; (disassemble '(:typed-entry-point fid)) > ;; (disassemble 'fid) > > ;; Let's wrap that up as macro. In a more polished implementation > ;; DEFUN could do all this. > (defmacro defun-typed (name (&rest args) ((&rest arg-types) return-type) > &body body) > `(progn > (declaim (ftype (function ,arg-types ,return-type) > (:typed-entry-point ,name))) > (defun (:typed-entry-point ,name) ,args > (the ,return-type > . ,body)) > (defun ,name ,args > (%typed-call '(:typed-entry-point ,name :adapt nil) . ,args)) > (define-compiler-macro ,name ,args > `(%typed-call '(:typed-entry-point ,',name :adapt t) > . ,(list . ,args))))) > > (defun-typed f+ (x y) > ((double-float double-float) double-float) > (+ x y)) > > ;; (disassemble '(:typed-entry-point f+)) > > (defun-typed sum-prod (x y z u v w) > ((double-float double-float double-float > double-float double-float double-float) > (values double-float double-float)) > (values (+ x y z u v w) > (* x y z u v w))) > > ;; (disassemble '(:typed-entry-point sum-prod)) > ;; (ext:info function linkage '(:typed-entry-point sum-prod)) > > (defun test-sum-prod-1 () > (multiple-value-bind (sum prod) (sum-prod 2d0 3d0 4d0 5d0 6d0 7d0) > (assert (= sum (+ 2d0 3d0 4d0 5d0 6d0 7d0))) > (assert (= prod (* 2d0 3d0 4d0 5d0 6d0 7d0))))) > > (defun test-sum-prod-2 () > (multiple-value-bind (sum) (sum-prod 2d0 3d0 4d0 5d0 6d0 7d0) > (assert (= sum (+ 2d0 3d0 4d0 5d0 6d0 7d0))))) > > (defun test-sum-prod-3-aux (x y z u v w) > (sum-prod x y z u v w)) > > (defun test-sum-prod-3 () > (multiple-value-bind (sum prod) (test-sum-prod-3-aux 2d0 3d0 4d0 5d0 6d0 7d0) > (assert (= sum (+ 2d0 3d0 4d0 5d0 6d0 7d0))) > (assert (= prod (* 2d0 3d0 4d0 5d0 6d0 7d0))))) > > (defun-typed id (x) > ((t) t) > x) > > ;; (id 1) > > (defun test-id-1 () > (assert (eql (id 1) 1))) > > (defun test-id-2 () > (assert (eql (id 1d0) 1d0))) > > ;; This one has both boxed and unboxed arguments. > (defun-typed cons-sum (o1 f1 o2 f2) > ((t double-float t double-float) > (values cons double-float)) > (values (cons o1 o2) (+ f1 f2))) > > (defun test-cons-sum-1 () > (multiple-value-bind (cons sum) (cons-sum 1 2d0 3 4d0) > (assert (equal cons '(1 . 3))) > (assert (= sum (+ 2d0 4d0))))) > > ;; SUM will be redefined with different types to exercise the linker a > ;; bit. > (defun-typed sum (f1 f2) > ((double-float double-float) double-float) > (+ f1 f2)) > > (defun test-sum-1 () > (assert (= (sum 2d0 3d0) 5d0))) > > (defun-typed sum (f1 f2) > ((t t) t) > (+ f1 f2)) > > (defun test-sum-2 () > (assert (= (sum 2d0 3d0) 5d0))) > > (defun test-sum-3 () > (handler-case (progn (sum 2 3) > (assert nil)) > (type-error (c) > (assert (equal (type-error-datum c) 3)) > (assert (eq (type-error-expected-type c) 'double-float))))) > > (defun-typed sum (f1 f2) > ((t double-float) double-float) > (+ f1 f2)) > > (defun test-sum-4 () > (assert (= (sum 2d0 3d0) 5d0))) > > (defun test-sum-5 () > (assert (= (sum 2 3d0) 5d0))) > > (defun test-sum-6 () > (handler-case (progn > (sum #c(0 1) 3d0) > (assert nil)) > (type-error (c) > (assert (equal (type-error-datum c) #c(3d0 1d0))) > (assert (eq (type-error-expected-type c) 'double-float))))) > > #+(or) > (defun foo () > (labels ((sum (x y) (+ x y))) > (declare (ftype (function (double-float double-float) double-float) sum)) > (list (sum 2d0 4d0) > (sum 2 4)))) > > (defun tests () > (test-fid-1) > (test-sum-prod-1) > (test-sum-prod-2) > (test-sum-prod-3) > (test-id-1) > (test-id-2) > (test-cons-sum-1) > (test-sum-1) > (test-sum-2) > (test-sum-3) > (test-sum-4) > (test-sum-5) > (test-sum-6) > ) > > ;; (tests) > _______________________________________________ > cmucl-imp mailing list > [email protected] > http://lists.zs64.net/mailman/listinfo/cmucl-imp -- Marco Antoniotti _______________________________________________ cmucl-imp mailing list [email protected] http://lists.zs64.net/mailman/listinfo/cmucl-imp