Changeset 15087


Ignore:
Timestamp:
06/17/17 06:52:40 (6 years ago)
Author:
Mark Evenson
Message:

jss: optimizations for jss field accessors
(Alan Ruttenberg)

Including '==' scope for one-only lookup of fields.

Revised doc at top of transform-to-field.lisp.

From <https://github.com/armedbear/abcl/pull/56>.

Merges
<https://github.com/armedbear/abcl/pull/56/commits/382a6db1ad5ac6685e97ad3677cc1681a12bc0f7>.

Location:
trunk/abcl/contrib/jss
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/abcl/contrib/jss/invoke.lisp

    r15086 r15087  
    209209    (let ((name (read stream)))
    210210      (if (or (find #\. name) (find #\{ name))
    211           (jss-transform-to-field name)
     211          (jss-transform-to-field name arg)
    212212          (let ((object-var (gensym))
    213213                (args-var (gensym)))
  • trunk/abcl/contrib/jss/transform-to-field.lisp

    r14947 r15087  
    22
    33;; JSS syntax for fields
    4 ;; #"<thing>.<field>"
     4;; #"[<scope>]<thing>.<field>"
    55;;
     6;; <scope> is empty or "==". scope is only paid attention to when <field> is a literal string
     7;;
    68;; <thing> is either {<lisp expression>} or a class name or abbreviation that find-java-class can use
    79;;   If <thing> is a lisp expression, then it is evaluated (in the lexical environment) and used as an instance
     10;;     when <scope> is "==" you promise that instance will always be of the same class, and so field lookup
     11;;     is done once and cached.
    812;;   If <thing> is a class name the result of find-java-class is used and a static field access is done.
     13;;     when <scope> is "==" you promise the static field is final and so the result is wrapped in (load-time-value  ...)
    914;;
    1015;; <field> is either {<lisp expression} or string
     
    1419;; eg. #"foo.bar.baz" -> (get-java-field (find-java-class 'foo.bar) "baz" t)
    1520;;     #"{foo}.baz" -> (get-java-field (find-java-class foo) "baz" t)
     21;;     #"==foo.baz" -> (load-time-value (get-java-field (find-java-class "foo") "bar" t))
     22;;     #"=={foo}.baz" -> TL;DR (only look up baz field once based on class of foo, and cache)
    1623
    17 
    18 (defun jss-transform-to-field (string)
    19   (let* ((pattern (#"compile" 'java.util.regex.Pattern "(.*)\\.([^.]+)$"))
     24(defun jss-transform-to-field (string sharp-arg)
     25  (let* ((pattern (#"compile" 'java.util.regex.Pattern "((==){0,1})(.*)\\.([^.]+)$"))
    2026   (matcher (#"matcher" pattern string)))
    2127    (#"find" matcher)
    22     (let ((parts (list (#"group" matcher 1) (#"group" matcher 2))))
     28    (let ((parts (list (#"group" matcher 3) (#"group" matcher 4)))
     29    (scope (#"group" matcher 1)))
    2330      (check-class-or-eval (first parts))
    2431      (check-field-or-eval (second parts))
    25       (apply 'field-access-expression parts))))
     32      (apply 'field-access-expression sharp-arg scope parts ))))
    2633
    2734;; http://stackoverflow.com/questions/5205339/regular-expression-matching-fully-qualified-class-names
     
    3845    "inside #\"..\" expected either a field name or an expression surrounded by {}. Found: #~s" string))
    3946
    40 (defun field-access-expression (thing field)
    41   `(get-java-field ,(if (char= (char thing 0) #\{)
    42       (intern (string-upcase (subseq thing 1 (- (length thing) 1))))
    43       `(load-time-value (find-java-class ,thing)))
    44        ,(if (char= (char field 0) #\{)
    45       (intern (string-upcase (subseq field 1 (- (length field) 1))))
    46       field)
    47        t))
     47(defun field-access-expression (sharp-arg scope thing field )
     48  (if (and (not (char= (char thing 0) #\{)) (not (char= (char field 0) #\{)))
     49      (static-field-ref-transform thing field sharp-arg scope)
     50      (if (and (equal scope "==") (char= (char thing 0) #\{) (not (char= (char field 0) #\{)))
     51    (always-same-signature-field-ref-transform sharp-arg thing field)
     52    `(get-java-field ,(if (char= (char thing 0) #\{)
     53        (read-from-string (subseq thing 1 (- (length thing) 1)))
     54        `(load-time-value (find-java-class ,thing)))
     55         ,(if (char= (char field 0) #\{)
     56        (read-from-string (subseq field 1 (- (length field) 1)))
     57        field)
     58         t))))
     59
     60;; If a class name and explicit field name we can look everything up at load time
     61(defun static-field-ref-transform (class field sharp-arg scope)
     62  (if (equal scope "==")
     63      `(load-time-value (get-java-field (find-java-class ,class) ,field t))
     64      `(,(if (eql sharp-arg 0) 'jcall-raw 'jcall)
     65  (load-time-value (jmethod "java.lang.reflect.Field" "get" "java.lang.Object"))
     66  (load-time-value
     67   (let ((jfield (find-declared-field ,field (find-java-class ,class))))
     68     (#"setAccessible" jfield t)
     69     jfield))
     70  (load-time-value (find-java-class ',class)))))
     71
     72;; 1 case: =={var}.foo
     73;; Globally cache the field accessor for the first value of {var}. Subsequent calls ignore the class of var.
     74(defun always-same-signature-field-ref-transform (sharp-arg object field)
     75  (let ((cached (make-symbol (format nil "CACHED-FIELD-field")))
     76  (object (intern (string-upcase (subseq object 1 (- (length object) 1))))))
     77    `(,(if (eql sharp-arg 0) 'jcall-raw 'jcall)
     78      (load-time-value (jmethod "java.lang.reflect.Field" "get" "java.lang.Object"))
     79      (locally (declare (special ,cached))
     80  (if (boundp ',cached)
     81      ,cached
     82      (progn (setq ,cached
     83       (find-declared-field ,field (jcall (load-time-value (jmethod "java.lang.Object" "getClass")) ,object)))
     84       (jcall (load-time-value (jmethod "java.lang.reflect.Field" "setAccessible" "boolean")) ,cached t)
     85       ,cached)))
     86      ,object)))
     87
     88
     89
     90
     91
     92 
Note: See TracChangeset for help on using the changeset viewer.