source: trunk/abcl/src/org/armedbear/lisp/runtime-class.lisp

Last change on this file was 15796, checked in by Mark Evenson, 9 months ago

JNEW-RUNTIME-CLASS: add support for doubles as arguments and return type in methods

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 20.1 KB
Line 
1(require "JVM")
2
3;;The package is set to :jvm for convenience, since most of the symbols used
4;;here come from that package. However, the functions we're definining belong
5;;to the :java package.
6(in-package :jvm)
7
8(defconstant +abcl-java-object+ (make-jvm-class-name "org.armedbear.lisp.JavaObject"))
9
10(defun java::make-memory-class-loader (&optional (parent (java:get-current-classloader)))
11  (java:jnew "org.armedbear.lisp.MemoryClassLoader" parent))
12
13(defun java:jnew-runtime-class
14    (class-name &rest args &key (superclass "java.lang.Object")
15     interfaces constructors methods fields (access-flags '(:public)) annotations
16     (class-loader (java::make-memory-class-loader)))
17  "Creates and loads a Java class with methods calling Lisp closures
18   as given in METHODS.  CLASS-NAME and SUPER-NAME are strings,
19   INTERFACES is a list of strings, CONSTRUCTORS, METHODS and FIELDS are
20   lists of constructor, method and field definitions.
21
22   Constructor definitions are lists of the form
23   (argument-types function &optional super-invocation-arguments)
24   where argument-types is a list of strings and function is a lisp function of
25   (1+ (length argument-types)) arguments; the instance (`this') is passed in as
26   the last argument. The optional super-invocation-arguments is a list of numbers
27   between 1 and (length argument-types), where the number k stands for the kth argument
28   to the just defined constructor. If present, the constructor of the superclass
29   will be called with the appropriate arguments. E.g., if the constructor definition is
30   ((\"java.lang.String\" \"int\") #'(lambda (string i this) ...) (2 1))
31   then the constructor of the superclass with argument types (int, java.lang.String) will
32   be called with the second and first arguments.
33
34   Method definitions are lists of the form
35
36     (METHOD-NAME RETURN-TYPE ARGUMENT-TYPES FUNCTION &key MODIFIERS ANNOTATIONS)
37
38   where
39      METHOD-NAME is a string
40      RETURN-TYPE denotes the type of the object returned by the method
41      ARGUMENT-TYPES is a list of parameters to the method
42
43        The types are either strings naming fully qualified java classes, Lisp
44        keywords referring to primitive types (:void, :int, etc.), or 2-element
45        lists where the first element is the keyword :array and the second
46        element is a keyword referring to a primitive type, e.g. (:array :byte).
47
48     FUNCTION is a Lisp function of minimum arity (1+ (length
49     argument-types)). The instance (`this') is passed as the first
50     argument.
51
52   Field definitions are lists of the form (field-name type &key modifiers annotations)."
53  (declare (ignorable superclass interfaces constructors methods fields access-flags annotations))
54  (let ((stream (sys::%make-byte-array-output-stream)))
55    (multiple-value-bind (class-file method-implementation-fields)
56        (apply #'java::%jnew-runtime-class class-name stream :allow-other-keys T args)
57      (sys::put-memory-function class-loader
58                                class-name (sys::%get-output-stream-bytes stream))
59      (let ((jclass (java:jcall "loadClass" class-loader class-name)))
60        (dolist (method method-implementation-fields)
61          (setf (java:jfield jclass (car method)) (cdr method)))
62        jclass))))
63
64(defconstant +abcl-lisp-integer-object+ (make-jvm-class-name "org.armedbear.lisp.LispInteger"))
65
66(defconstant +abcl-lisp-double-object+
67  (make-jvm-class-name "org.armedbear.lisp.DoubleFloat"))
68
69(defun arg-size (type)
70  (if (keywordp type)
71      (representation-size type)
72      1))
73
74(defun box-arguments (argument-types offset all-arg-size)
75  "Emits bytecode to box Java method arguments to lisp types.
76
77The boxed arguments end up, in the same order, immediately after the actual
78arguments in the local variable space.
79
80ARGUMENT-TYPES: list of argument types, each as in the JNEW-RUNTIME-CLASS
81    form after being passed to JAVA::CANONICALIZE-JAVA-TYPE.
82OFFSET: Extra space used before the args, currently 1 for 'this' or zero for
83    static method.
84ALL-ARG-SIZE: The number of 'local variables' (per JVMS23 2.6.1) used to
85    hold the arguments to this method.  This would be the number of
86    arguments, except that long and double arguments take up two variables."
87  (loop
88    :for arg-type :in argument-types
89    :for argn :from offset
90    :for arg-offset :from offset
91    :do (progn
92          (cond
93            ((eq arg-type :int)
94             (iload arg-offset)
95             (emit-invokestatic +abcl-lisp-integer-object+ "getInstance"
96                                (list :int) +abcl-lisp-integer-object+))
97            ((eq arg-type :double)
98             (dload arg-offset)
99             (incf arg-offset); doubles take two spots
100             (emit-invokestatic +abcl-lisp-double-object+ "getInstance"
101                                (list :double) +abcl-lisp-double-object+))
102            ((keywordp arg-type)
103             (error "Unsupported arg-type: ~A" arg-type))
104            (t (aload arg-offset)
105               (emit 'iconst_1) ;;true
106               (emit-invokestatic +abcl-java-object+ "getInstance"
107                                  (list +java-object+ :boolean) +lisp-object+)))
108          (astore (+
109                   all-arg-size; passed arguments size
110                   argn; boxed argument offset
111                   )))))
112
113(defun java::%jnew-runtime-class
114    (class-name stream &key (superclass "java.lang.Object")
115     interfaces constructors methods fields (access-flags '(:public)) annotations)
116  "Actual implementation of jnew-runtime-class. Writes the class bytes to a stream. Returns two values: the finalized class-file structure and the alist of method implementation fields."
117  (let* ((jvm-class-name (make-jvm-class-name class-name))
118         (class-file (make-class-file jvm-class-name (make-jvm-class-name superclass) access-flags))
119         method-implementation-fields)
120    (setf (class-file-interfaces class-file)
121          (mapcar #'make-jvm-class-name interfaces))
122    (when annotations
123      (class-add-attribute class-file (make-runtime-visible-annotations-attribute
124                                       :list (mapcar #'parse-annotation annotations))))
125    (setf method-implementation-fields (java::runtime-class-add-methods class-file methods))
126    (java::runtime-class-add-fields class-file fields)
127    (if (null constructors)
128      (let ((ctor (make-jvm-method :constructor :void nil :flags '(:public))))
129        (class-add-method class-file ctor)
130        (with-code-to-method (class-file ctor)
131          (aload 0)
132          (emit-invokespecial-init (class-file-superclass class-file) nil)
133          (emit 'return)))
134      (dolist (constructor constructors)
135        (destructuring-bind (argument-types function &optional super-args)
136            constructor
137          (let* ((argument-types (mapcar #'java::canonicalize-java-type argument-types))
138                 (argc (length argument-types))
139                 (ctor (make-jvm-method :constructor :void argument-types))
140                 (field-name (string (gensym "CONSTRUCTOR")))
141                 (all-argc (1+ argc)))
142            (class-add-method class-file ctor)
143            (let ((field (make-field field-name +lisp-object+ :flags '(:public :static))))
144              (class-add-field class-file field))
145            (push (cons field-name function) method-implementation-fields)
146            (with-code-to-method (class-file ctor)
147              (dotimes (i (* 2 all-argc))
148                (allocate-register nil))
149
150              (aload 0)
151              (dolist (arg super-args)
152                (aload arg))
153              (emit-invokespecial-init
154               (class-file-superclass class-file)
155               (map 'list
156                    (lambda (index) (elt argument-types (1- index)))
157                    super-args))
158                                           
159
160              (aload 0)
161              (emit 'iconst_1) ;;true
162              (emit-invokestatic +abcl-java-object+ "getInstance"
163                                 (list +java-object+ :boolean) +lisp-object+)
164              (astore all-argc)
165
166              (box-arguments argument-types 1 all-argc)
167
168              ;;Load the Lisp function from its static field
169              (emit-getstatic (class-file-class class-file) field-name +lisp-object+)
170              (if (<= all-argc call-registers-limit)
171                  (progn
172                    ;;Load the boxed this
173                    (aload all-argc)
174                    ;;Load each boxed argument
175                    (dotimes (i argc)
176                      (aload (+ i 1 all-argc))))
177                  (error "execute(LispObject[]) is currently not supported"))
178              (emit-call-execute all-argc)
179
180              (emit 'return))))))
181    (finalize-class-file class-file)
182    (write-class-file class-file stream)
183    (finish-output stream)
184    #+test-record-generated-class-file
185    (let ((filename (merge-pathnames (format nil "~A.class" class-name))))
186      (with-open-file (f filename :direction :output :element-type '(signed-byte 8))
187        (write-sequence (java::list-from-jarray (sys::%get-output-stream-bytes stream)) f))
188      (format *standard-output* "~&Wrote class file ~A.~%" filename))
189    (values class-file method-implementation-fields)))
190
191(defun java::make-accessor-name (prefix name)
192  (let ((initial (char-upcase (aref name 0)))
193        (rest (subseq name 1)))
194    (format nil "~A~A~A" prefix initial rest)))
195
196;;This is missing from compiler-pass2.lisp. Probably this and similar functions should reside
197;;in a dedicated file, independent from both runtime-class and compiler-pass2.
198(defun emit-invokespecial (class-name method-name arg-types return-type)
199  (let* ((stack-effect (apply #'descriptor-stack-effect return-type arg-types))
200         (index (pool-add-method-ref *pool* class-name
201                                     method-name (cons return-type arg-types)))
202         (instruction (apply #'%emit 'invokespecial (u2 index))))
203    (declare (type (signed-byte 8) stack-effect))
204    (setf (instruction-stack instruction) (1- stack-effect))))
205
206(defun java::canonicalize-java-type (type)
207  (cond
208    ((stringp type) (make-jvm-class-name type))
209    ((keywordp type) type)
210    ((consp type) type)
211    (t (error "Unrecognized Java type: ~A" type))))
212
213(defun java::emit-unbox-and-return (return-type)
214  (cond
215    ((eq return-type :void)
216     (emit 'pop)
217     (emit 'return))
218    ((eq return-type :int)
219     (emit-invokevirtual +lisp-object+ "intValue" nil :int)
220     (emit 'ireturn))
221    ((eq return-type :boolean)
222     (emit-invokevirtual +lisp-object+ "getBooleanValue" nil :boolean)
223     (emit 'ireturn))
224    ((eq return-type :double)
225     (emit-invokestatic +abcl-lisp-double-object+ "getValue" (list +lisp-object+) :double)
226     (emit 'dreturn))
227    ((jvm-class-name-p return-type)
228     (emit 'ldc_w (pool-class return-type))
229     (emit-invokevirtual +lisp-object+ "javaInstance" (list +java-class+) +java-object+)
230     (emit-checkcast return-type)
231     (emit 'areturn))
232    (t
233     (error "Unsupported return type: ~A" return-type))))
234
235(defun java::runtime-class-add-methods (class-file methods)
236  (mapcan (lambda (method) (java::runtime-class-add-method class-file method))
237          methods))
238
239(defun java::runtime-class-add-method (class-file method)
240   "Compute METHOD definition and add it to CLASS-FILE.
241
242Returns method implementation fields."
243  (let (method-implementation-fields)
244      (destructuring-bind (name return-type argument-types function
245                           &key (modifiers '(:public)) annotations override)
246          method
247        (let* ((argument-types (mapcar #'java::canonicalize-java-type argument-types))
248               (argc (length argument-types))
249               (args-size (reduce #'+ (mapcar #'arg-size argument-types)))
250               (return-type (java::canonicalize-java-type return-type))
251               (jmethod (make-jvm-method name return-type argument-types :flags modifiers))
252               (field-name (string (gensym name)))
253               (staticp (member :static modifiers))
254               (this-offset (if staticp 0 1))
255               (all-argc (+ argc this-offset))
256               (all-args-size (+ args-size this-offset)))
257          (class-add-method class-file jmethod)
258          (let ((field (make-field field-name +lisp-object+ :flags '(:public :static))))
259            (class-add-field class-file field)
260            (push (cons field-name function) method-implementation-fields))
261          (when annotations
262            (method-add-attribute jmethod (make-runtime-visible-annotations-attribute
263                                           :list (mapcar #'parse-annotation annotations))))
264          (with-code-to-method (class-file jmethod)
265            ;;Allocate registers
266            (dolist (type argument-types)
267              ;; allocate register(s) to store raw argument
268              (allocate-register (if (keywordp type) type nil))
269              ;; allocate register to store boxed argument
270              (allocate-register nil))
271            (unless staticp
272              (allocate-register nil); raw 'this'
273              (allocate-register nil); boxed 'this'
274              ;;Box "this" (to be passed as the first argument to the Lisp function)
275              (aload 0)
276              (emit 'iconst_1) ;;true
277              (emit-invokestatic +abcl-java-object+ "getInstance"
278                                 (list +java-object+ :boolean) +lisp-object+)
279              (astore all-args-size))
280            (box-arguments argument-types this-offset all-args-size)
281            ;;Load the Lisp function from its static field
282            (emit-getstatic (class-file-class class-file) field-name +lisp-object+)
283            (if (<= all-args-size call-registers-limit)
284                (progn
285                  ;;Load the boxed this
286                  (unless staticp
287                    (aload all-args-size))
288                  ;;Load each boxed argument
289                  (dotimes (i argc)
290                    (aload (+ i 1 all-args-size))))
291                (error "execute(LispObject[]) is currently not supported"))
292            (emit-call-execute all-argc)
293            (java::emit-unbox-and-return return-type))
294          (cond
295            ((eq override t)
296             (let ((super-method
297                    (make-jvm-method (format nil "super$~A" name)
298                                     return-type argument-types :flags modifiers)))
299               (class-add-method class-file super-method)
300               (with-code-to-method (class-file super-method)
301                 (dotimes (i (1+ (length argument-types)))
302                   (allocate-register nil))
303                 (aload 0)
304                 (loop
305                    :for arg-type :in argument-types
306                    :for i :from 1
307                    :do (progn
308                          (cond
309                            ((keywordp arg-type)
310                             (error "Unsupported arg-type: ~A" arg-type))
311                            ((eq arg-type :int) :todo)
312                            (t (aload i)))))
313                 (emit-invokespecial (class-file-superclass class-file) name
314                                     argument-types return-type)
315                 ;(emit 'pop)
316                 (cond
317                   ((eq return-type :void)
318                    (emit 'return))
319                   ((eq return-type :int)
320                    (emit 'ireturn))
321                   ((eq return-type :boolean)
322                    (emit 'ireturn))
323                   ((jvm-class-name-p return-type)
324                    (emit 'areturn))
325                   (t
326                    (error "Unsupported return type: ~A" return-type)))))))))
327    method-implementation-fields))
328
329(defun java::runtime-class-add-fields (class-file fields)
330  (dolist (field-spec fields)
331    (destructuring-bind (name type &key (modifiers '(:public)) annotations
332                              (getter nil getter-p) (setter nil setter-p)
333                              (property (and (not getter-p) (not setter-p))))
334        field-spec
335      (let* ((type (if (keywordp type) type (make-jvm-class-name type)))
336             (field (make-field name type :flags modifiers)))
337        (when (member :static modifiers)
338          (setf property nil getter nil setter nil))
339        (when annotations
340          (field-add-attribute field (make-runtime-visible-annotations-attribute
341                                      :list (mapcar #'parse-annotation annotations))))
342        (class-add-field class-file field)
343        (when (or getter property)
344          (unless (stringp getter)
345            (setf getter (java::make-accessor-name "get" (if (stringp property) property name))))
346          (let ((jmethod (make-jvm-method getter type nil :flags '(:public))))
347            (class-add-method class-file jmethod)
348            (with-code-to-method (class-file jmethod)
349              (aload 0)
350              (emit-getfield (class-file-class class-file) name type)
351              (cond
352                ((jvm-class-name-p type) (emit 'areturn))
353                ((eq type :int) (emit 'ireturn))
354                (t (error "Unsupported getter return type: ~A" type))))))
355        (when (or setter property)
356          (unless (stringp setter)
357            (setf setter (java::make-accessor-name "set" (if (stringp property) property name))))
358          (let ((jmethod (make-jvm-method setter :void (list type) :flags '(:public))))
359            (class-add-method class-file jmethod)
360            (with-code-to-method (class-file jmethod)
361              (aload 0)
362              (cond
363                ((jvm-class-name-p type) (aload 1))
364                ((eq type :int) (emit 'iload 1))
365                (t (error "Unsupported setter parameter type: ~A" type)))
366              (emit-putfield (class-file-class class-file) name type)
367              (emit 'return))))))))
368
369(defmacro java:define-java-class () :todo)
370
371(defun parse-annotation (annotation)
372  (when (annotation-p annotation)
373    (return-from parse-annotation annotation))
374  (destructuring-bind (class &rest elements) (if (listp annotation) annotation (list annotation))
375    (let (actual-elements)
376      (dolist (elem elements)
377        (push (parse-annotation-element elem) actual-elements))
378      (make-annotation :type class :elements (nreverse actual-elements)))))
379
380(defun parse-annotation-element (elem)
381  (cond
382    ((annotation-element-p elem) elem)
383    ((atom elem) (make-primitive-or-string-annotation-element :name nil :value elem))
384    ((keywordp (car elem)) (parse-annotation-element `("value" ,@elem)))
385    (t
386     (destructuring-bind (name &key value enum annotation) elem
387       (cond
388         (enum (make-enum-value-annotation-element :name name :type enum :value value))
389         (annotation
390          (make-annotation-value-annotation-element :name name :value (parse-annotation annotation)))
391         ((listp value)
392          (make-array-annotation-element :name name :values (mapcar #'parse-annotation-element value)))
393         (t (make-primitive-or-string-annotation-element :name name :value value)))))))
394
395;;TODO:
396;; - Returning nil as null is broken
397;; - Function calls with 8+ args
398;; - super method invocation. Idea: generate companion methods super_... to use with plain jcall. Add a flag per method to optionally disable this when not needed.
399;; - Constructors
400;; - optional accessors (CLOS methods) for properties?
401
402#+example
403(java:jnew-runtime-class
404 "Foo"
405 :interfaces (list "java.lang.Comparable")
406 :fields (list '("someField" "java.lang.String") '("anotherField" "java.lang.Object" :getter t))
407 :methods (list
408           (list "foo" :void '("java.lang.Object")
409                 (lambda (this that) (print (list this that)))
410                 :annotations (list "java.lang.Deprecated"
411                                    '("java.lang.annotation.Retention"
412                                      (:enum "java.lang.annotation.RetentionPolicy" :value "RUNTIME"))
413                                    '("javax.xml.bind.annotation.XmlAttribute" ("required" :value t))
414                                    '("com.manydesigns.portofino.system.model.users.annotations.RequiresPermissions"
415                                      ("level"
416                                       :enum "com.manydesigns.portofino.model.pages.AccessLevel"
417                                       :value "EDIT")
418                                      ("permissions" :value ("foo" "bar")))))
419           (list "bar" :int '("java.lang.Object")
420                 (lambda (this that) (print (list this that)) 23))))
421
422(provide "RUNTIME-CLASS")
Note: See TracBrowser for help on using the repository browser.