Skip to content

General inliner #29

Open
Open
@bsless

Description

@bsless

All of my inlining implementations, although close to the original implementation they're mimicking, are ad-hoc. They require prior knowledge of the inlined form.
Ideally, I would want to expose a macro like definline which will include a sophisticated :inline function. This inliner will examine the call site and try to inline what it can.

Prior works on this include F expressions
https://github.com/halgari/heliotrope
https://web.wpi.edu/Pubs/ETD/Available/etd-090110-124904/unrestricted/jshutt.pdf

And Haskell's Core compiler
https://gitlab.haskell.org/ghc/ghc/-/wikis/commentary/compiler/core-to-core-pipeline

I started trying to generalize an implementation, starting out with replacing function application with a rewrite rule which would replace application by the instructions to write an application:

(defn abstract-call
  [sym]
  (fn [& args]
    `(~sym ~@args)))

(defmacro ac
  [sym]
  `(abstract-call ~sym))

This allows to quite trivially port an existing definition:

(defn a-get-in
  [m ks]
  (reduce (ac `get) m ks))

(a-get-in 'm '[a b c])
;=>
(clojure.core/get (clojure.core/get (clojure.core/get m a) b) c)

It requires some massaging of more complex definitions but essentially works:

(defn a-assoc-in
  [m [k & ks] v]
  (let [g (gensym)]
    `(let [~g ~m]
       ~(if ks
          ((ac `assoc) g k (a-assoc-in ((ac `get) g k) ks v))
          ((ac `assoc) g k v)))))

(a-assoc-in 'm '[a b c] 'v)
;=>
(clojure.core/let
 [G__11548 m]
 (clojure.core/assoc
  G__11548
  a
  (clojure.core/let
   [G__11549 (clojure.core/get G__11548 a)]
   (clojure.core/assoc
    G__11549
    b
    (clojure.core/let
     [G__11550 (clojure.core/get G__11549 b)]
     (clojure.core/assoc G__11550 c v))))))

Things get more complicated when trying to generalize this

This is my initial abortive attempt:

(defn replace-args
  [args form]
  (let [argm (zipmap args (repeatedly gensym))
        imap (interleave (vals argm) (keys argm))]
    `(let [~@imap]
       ~(walk/postwalk-replace argm form))))

(defn fnsym
  [sym]
  (when-let [v (resolve sym)]
    (when (and (ifn? (deref v)) (not (:macro (meta v))))
      (symbol v))))

(defn abstract-fn
  [sym]
  (if-let [sym (fnsym sym)]
    `(abstract-call ~sym)
    sym))

(comment
  ((abstract-fn 'get) 'm 'k))

(defn abstract-form
  [name form]
  (walk/postwalk
   (fn [expr]
     (if-let [expr (and (symbol? expr)
                        (not= name expr)
                        (abstract-fn expr))]
       expr
       expr))
   form))

(defn regenerate-form
  [name args form]
  (fn [& args']
    (let [gnosis (map (fn [argn arg] (if (known-at-callsite? arg)
                                       (with-meta argn (assoc (meta argn) :known true))
                                       argn)) args args')
          known (filter (comp :known meta) gnosis)
          unknown (remove (comp :known meta) gnosis)]
      (if (seq known)
        (->> form
             (replace-args unknown)
             (abstract-form name))
        'boring))))

(defn emit-inliner
  [name args form]
  (let [generator (regenerate-form name args form)]
    (fn [& callsite]
      (let [emitter (apply generator callsite)
            emitter (eval `(fn [~@args] ~emitter))]
        (apply emitter callsite)))))

(defmacro definline+
  [name & decl]
  (let [[pre-args [args expr]] (split-with (comp not vector?) decl)]
    `(do
       (defn ~name ~@pre-args ~args ~expr)
       (alter-meta! (var ~name) assoc :inline (emit-inliner (quote ~name) (quote ~args) (quote ~expr)))
       (var ~name))))

(definline+ my-assoc-in
  [m [k & ks] v]
  (if ks
    (assoc m k (my-assoc-in (get m k) ks v))
    (assoc m k v)))

(defn foo
  [m v]
  (my-assoc-in m [1 2 3] v))

There are heuristics to be considered when attempting to inline, some of which are discussed in the Haskell Core compiler literature, such as the number of occurrences of a variable in a form determining if it needs to be bound (see the input map in assoc-in).

cc: @joinr, what do you think, does a solution present itself readily here, or is it completely inscrutable?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions