     :entry is a symbol, presumably a namespace qualified -main var,
     which is the entry point of the prorgam. It is with respect to this
     function that all other defs will be considered for elimination."
-  [ast {:keys [entry] :as options}]
-  (-> ast
-      (defs/locate-defs options)
-      (util/map-vals (partial tree-shake-module options))))
+  [{:keys [modules] :as ast} {:keys [entry] :as options}]
+  (let [;; First find and annotate all the defs in the program
+        ast (-> ast (defs/locate-defs options))
+
+        ;; Then compute the {Var → #{Var}} form of the whole program
+        symbol-sets (->> (for [m   modules
+                               sym (keys (get ast m))
+                               :let [form (get-in ast [m sym])]]
+                           [(:var form) (reach-set form)])
+                         (into {})
+                         global-reach-set)
+
+        ;; The emit set is now trivially
+        emit-set (get symbol-sets (resolve entry))]
+    (trim-with-emit-set ast emit-set)))



(defn global-reach-set
  "λ {Symbol → AST} → {Symbol → #{Symbol}}

  Computes the reach set for an entire program"
  [whole-program-ast]

                (util/map-vals reach-set))))


(defn trim-with-emit-set
  [{:keys [modules] :as whole-program-ast} reach-set]
  (let [new-ast (atom {:modules modules})]

    (doseq [m modules]
      (doseq [[sym ast] (get whole-program-ast m)]
        (if (pattern/def? ast)
          (if (contains? reach-set (:var ast))
            (swap! new-ast assoc-in [m sym] ast)
            (info "Discarding unused def form,"
                  (util/line ast)))
          (warn "Discarding non-def top level form,"
                (util/line ast)))))

    @new-ast))


(defn tree-shake
  "λ Whole-AST → options → Whole-AST
