From d30af6e9721b0173646478260211c34ed4350fe0 Mon Sep 17 00:00:00 2001 From: Nathan Marz Date: Mon, 23 May 2016 08:18:49 -0400 Subject: [PATCH] cljs inline caching impl almost working --- src/clj/com/rpl/specter/impl.cljx | 65 +++++++++++++++++++++--------- src/clj/com/rpl/specter/macros.clj | 61 ++++++++++++++++++---------- 2 files changed, 84 insertions(+), 42 deletions(-) diff --git a/src/clj/com/rpl/specter/impl.cljx b/src/clj/com/rpl/specter/impl.cljx index 9baecdd..7ea1f21 100644 --- a/src/clj/com/rpl/specter/impl.cljx +++ b/src/clj/com/rpl/specter/impl.cljx @@ -619,7 +619,7 @@ [val sym]) (defrecord VarUse - [var sym]) + [val var sym]) (defrecord SpecialFormUse [val code]) @@ -635,6 +635,8 @@ (defonce PATH-CACHE #+clj (java.util.concurrent.ConcurrentHashMap.) + ;;TODO: according to @dnolen, can forgo this for instead doing + ;;inline defs at runtime #+cljs (atom {}) ) @@ -674,9 +676,20 @@ (satisfies? p/Collector v) (instance? CompiledPath v))) -(defn magic-fail! [failed-atom] - (reset! failed-atom true) - nil) +#+clj +(def ^:dynamic *used-locals*) + +#+clj +(defmacro handle-params [precompiled params-maker possible-params] + `(bind-params* ~precompiled (~params-maker ~@*used-locals*) 0)) + +#+cljs +(defn handle-params [precompiled params-maker possible-params] + (let [params (fast-object-array (count params-maker))] + (doseq [i params-maker] + (aset params i ((get possible-params i)))) + (bind-params* precompiled params 0) + )) (def pred* (->ParamsNeededPath @@ -713,7 +726,7 @@ (instance? VarUse p) (let [v (:var p) - vv (var-get v)] + vv (:val p)] (cond (-> v meta :dynamic) (magic-fail! "Var " (:sym p) " is dynamic") (valid-navigator? vv) vv :else (magic-fail! "Var " (:sym p) " is not a navigator") @@ -733,7 +746,7 @@ ps (:params p)] (if (instance? VarUse op) (let [v (:var op) - vv (var-get v)] + vv (:val op)] (if (-> v meta :dynamic) (magic-fail! "Var " (:sym op) " is dynamic") (cond @@ -782,7 +795,30 @@ ) ))) -(defn magic-precompilation [prepared-path used-locals] +#+clj +(defn mk-params-maker [params-code possible-params-code used-locals] + (let [array-sym (gensym "array")] + (eval + `(fn [~@used-locals] + (let [~array-sym (fast-object-array ~(count params-code))] + ~@(map-indexed + (fn [i c] + `(aset ~array-sym ~i ~c)) + params-code + ) + ~array-sym + ))))) + +#+cljs +(defn mk-params-maker [params-code possible-params-code used-locals] + (let [indexed (->> possible-params-code + (map-indexed (comp vec reverse vector)) + (into {}))] + ;;TODO: may be more efficient as an array + (mapv (fn [c] (get indexed c)) params-code))) + +;; possible-params-code is for cljs impl that can't use eval +(defn magic-precompilation [prepared-path used-locals possible-params-code] (let [params-atom (atom []) failed-atom (atom false) path (magic-precompilation* prepared-path params-atom failed-atom) @@ -793,19 +829,8 @@ (->CachedPathInfo nil nil)) (let [precompiled (comp-paths* path) params-code (mapv extract-original-code @params-atom) - array-sym (gensym "array") - params-maker - (if-not (empty? params-code) - (eval - `(fn [~@used-locals] - (let [~array-sym (fast-object-array ~(count params-code))] - ~@(map-indexed - (fn [i c] - `(aset ~array-sym ~i ~c)) - params-code - ) - ~array-sym - )))) + params-maker (if-not (empty? params-code) + (mk-params-maker params-code possible-params-code used-locals)) ] ;; TODO: error if precompiled is compiledpath and there are params or ;; precompiled is paramsneededpath and there are no params... diff --git a/src/clj/com/rpl/specter/macros.clj b/src/clj/com/rpl/specter/macros.clj index b891ed2..3a0df81 100644 --- a/src/clj/com/rpl/specter/macros.clj +++ b/src/clj/com/rpl/specter/macros.clj @@ -327,7 +327,8 @@ (symbol? path) (if (contains? locals-set path) `(com.rpl.specter.impl/->LocalSym ~path (quote ~path)) - `(com.rpl.specter.impl/->VarUse (var ~path) (quote ~path)) + ;; var-get doesn't work in cljs, so capture the val in the macro instead + `(com.rpl.specter.impl/->VarUse ~path (var ~path) (quote ~path)) ) (i/fn-invocation? path) @@ -344,11 +345,20 @@ `(quote ~path) )) +(defn ic-possible-params [path] + (do + (mapcat + (fn [e] + (if (i/fn-invocation? e) + (concat (-> e rest) (ic-possible-params e)))) + path))) + ;; still possible to mess this up with alter-var-root (defmacro path [& path] ; "inline cache" (let [local-syms (-> &env keys set) used-locals (vec (i/walk-select local-syms vector path)) prepared-path (ic-prepare-path local-syms (walk/macroexpand-all (vec path))) + possible-params (vec (ic-possible-params path)) ;; TODO: unclear if using long here versus string makes ;; a significant difference ;; - but using random longs creates possibility of collisions @@ -360,28 +370,35 @@ ;; a bunch of checks beforehand cache-id (str (java.util.UUID/randomUUID)) ] - `(let [info# (i/get-path-cache ~cache-id) - - ^com.rpl.specter.impl.CachedPathInfo info# - (if (some? info#) - info# - (let [info# (i/magic-precompilation - ~prepared-path - ~(mapv (fn [e] `(quote ~e)) used-locals) - )] - (i/add-path-cache! ~cache-id info#) - info# - )) + (binding [i/*used-locals* used-locals] + ;; in order to pass the used locals to the clj handle-params macro + (walk/macroexpand-all + `(let [info# (i/get-path-cache ~cache-id) + + ^com.rpl.specter.impl.CachedPathInfo info# + (if (some? info#) + info# + (let [info# (i/magic-precompilation + ~prepared-path + (quote ~used-locals) + (quote ~possible-params) + )] + (i/add-path-cache! ~cache-id info#) + info# + )) - precompiled# (.-precompiled info#) - params-maker# (.-params-maker info#)] - (if (some? precompiled#) - (if (nil? params-maker#) - precompiled# - (i/bind-params* precompiled# (params-maker# ~@used-locals) 0) - ) - (i/comp-paths* ~(vec path)) - )) + precompiled# (.-precompiled info#) + params-maker# (.-params-maker info#)] + (if (some? precompiled#) + (if (nil? params-maker#) + precompiled# + (i/handle-params + precompiled# + params-maker# + ~(mapv (fn [p] `(fn [] ~p)) possible-params) + )) + (i/comp-paths* ~(vec path)) + )))) )) (defmacro select [apath structure]