| author | Alan Dipert
<alan@dipert.org> 2026-11-06 21:24:12 UTC |
| committer | Alan Dipert
<alan@dipert.org> 2026-11-06 21:24:12 UTC |
| parent | dfe76d51482f7833f2b695e927c2fc3aa7d0509b |
| doc/compiler.md | +11 | -2 |
| doc/nanopass.md | +8 | -2 |
| src/com/tailrecursion/blacktide/compiler.clj | +54 | -23 |
| src/com/tailrecursion/blacktide/compiler/analysis_languages.clj | +0 | -23 |
| src/com/tailrecursion/blacktide/compiler/assignment.clj | +0 | -123 |
| src/com/tailrecursion/blacktide/compiler/backend.clj | +0 | -26 |
| src/com/tailrecursion/blacktide/compiler/boundaries.clj | +0 | -70 |
| src/com/tailrecursion/blacktide/compiler/cases.clj | +0 | -24 |
| src/com/tailrecursion/blacktide/compiler/collections.clj | +0 | -71 |
| src/com/tailrecursion/blacktide/compiler/compact_languages.clj | +0 | -24 |
| src/com/tailrecursion/blacktide/compiler/effects.clj | +0 | -28 |
| src/com/tailrecursion/blacktide/compiler/errors.clj | +5 | -5 |
| src/com/tailrecursion/blacktide/compiler/jvm.clj | +1 | -1 |
| src/com/tailrecursion/blacktide/compiler/jvm_languages.clj | +0 | -35 |
| src/com/tailrecursion/blacktide/compiler/languages.clj | +200 | -14 |
| src/com/tailrecursion/blacktide/compiler/lower.clj | +0 | -141 |
| src/com/tailrecursion/blacktide/compiler/native.clj | +0 | -108 |
| src/com/tailrecursion/blacktide/compiler/order.clj | +0 | -116 |
| src/com/tailrecursion/blacktide/compiler/passes/assignment.clj | +123 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj | +76 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/box_origins.clj | +3 | -4 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/case_emit.clj | +2 | -3 |
| src/com/tailrecursion/blacktide/compiler/passes/cases.clj | +25 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/collections.clj | +69 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/compact.clj | +73 | -74 |
| src/com/tailrecursion/blacktide/compiler/passes/effects.clj | +27 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/emit.clj | +5 | -5 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/feasible.clj | +1 | -1 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/graph.clj | +2 | -2 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/host_emit.clj | +4 | -5 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/invariants.clj | +4 | -4 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/joins.clj | +12 | -13 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/loop_facts.clj | +77 | -78 |
| src/com/tailrecursion/blacktide/compiler/passes/lower.clj | +139 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/native.clj | +104 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/order.clj | +115 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/prepare.clj | +44 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/source.clj | +410 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/source_case.clj | +6 | -6 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/source_host.clj | +3 | -4 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/source_jvm.clj | +6 | -7 |
| src/com/tailrecursion/blacktide/compiler/passes/source_values.clj | +38 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/storage.clj | +13 | -15 |
| src/com/tailrecursion/blacktide/compiler/passes/structure.clj | +160 | -0 |
| src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj | +108 | -0 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/transfers.clj | +1 | -1 |
| src/com/tailrecursion/blacktide/compiler/{ => passes}/value_facts.clj | +13 | -15 |
| src/com/tailrecursion/blacktide/compiler/prepare.clj | +0 | -46 |
| src/com/tailrecursion/blacktide/compiler/source.clj | +0 | -412 |
| src/com/tailrecursion/blacktide/compiler/source_languages.clj | +0 | -93 |
| src/com/tailrecursion/blacktide/compiler/source_values.clj | +0 | -36 |
| src/com/tailrecursion/blacktide/compiler/structure.clj | +0 | -163 |
| src/com/tailrecursion/blacktide/compiler/structured_emit.clj | +0 | -110 |
| src/com/tailrecursion/blacktide/compiler/structured_languages.clj | +0 | -34 |
| src/com/tailrecursion/nanopass.clj | +37 | -10 |
| src/com/tailrecursion/nanopass/pass.clj | +107 | -110 |
| test/com/tailrecursion/blacktide/annotations.clj | +25 | -121 |
| test/com/tailrecursion/blacktide/backend_test.clj | +12 | -12 |
| test/com/tailrecursion/blacktide/check.clj | +20 | -50 |
| test/com/tailrecursion/blacktide/collections_test.clj | +4 | -4 |
| test/com/tailrecursion/blacktide/generated_test.clj | +2 | -2 |
| test/com/tailrecursion/blacktide/graph_test.clj | +1 | -1 |
| test/com/tailrecursion/blacktide/machine_test.clj | +1 | -1 |
| test/com/tailrecursion/blacktide/source_loops_test.clj | +4 | -4 |
| test/com/tailrecursion/nanopass/check.clj | +22 | -0 |
| test/com/tailrecursion/nanopass/clients/assignment.clj | +20 | -21 |
| test/com/tailrecursion/nanopass/clients/flow.clj | +74 | -74 |
| test/com/tailrecursion/nanopass/pass_test.clj | +4 | -1 |
| test/com/tailrecursion/nanopass/passes.clj | +23 | -25 |
diff --git a/doc/compiler.md b/doc/compiler.md index 19b48b1..dea52a6 100644 --- a/doc/compiler.md +++ b/doc/compiler.md @@ -5,6 +5,11 @@ Blacktide transforms immutable Clojure data through languages defined with checker is absent from ordinary library use. Namespace docstrings state pass contracts. Compiler namespaces and intermediate representations are internal. +All grammars live in [`compiler/languages.clj`](../src/com/tailrecursion/blacktide/compiler/languages.clj). +Transformations and their helpers live under [`compiler/passes/`](../src/com/tailrecursion/blacktide/compiler/passes/). +The executable pipeline, including the backend choice, lives in +[`compiler.clj`](../src/com/tailrecursion/blacktide/compiler.clj). + | Stage | Responsibility | | --- | --- | | Source | Expand macros in order, resolve unique lexical identities and source JVM facts, enforce the subset, solve loop representations without repeating expansion. | @@ -20,8 +25,10 @@ contracts. Compiler namespaces and intermediate representations are internal. The outer bindings enter as one lexical scope; emission seals the result with an Object type boundary. `Native` contains analyzed subtrees without DSL operations. -The structured backend uses `Machine → Planned → Joined → Stored → Compact → Code` -and shares graph validation with the dispatcher. +The structured backend uses `Machine → Analysis → Planned → Joined → Stored → Compact → Code` +and shares graph validation with the dispatcher. Grammar derivation expresses +structural relationships, not execution order: `Symbolic` extends `CFG`, which +extends `Machine`, though lowering visits them in the opposite order. Assignments, normalization and graph construction are pure. Locals become lexical bindings and loop parameters, never mutable cells. A generated @@ -36,6 +43,8 @@ Clojure analysis. The Java helper targets JDK 17 and is bundled in JARs. To extend the compiler, change the language and affected passes, then run `bin/check`. Prefer a compiler-local transformation over new nanopass machinery. +Checking discovers compiler source namespaces and registers language and pass +contracts from their metadata; new files need no separate registration list. Tests should establish source semantics or a necessary invariant. Generators use independent models or ordinary Clojure, deterministic seeds and shrinking; backend comparisons supplement those independent references. diff --git a/doc/nanopass.md b/doc/nanopass.md index bb1e150..1120b5b 100644 --- a/doc/nanopass.md +++ b/doc/nanopass.md @@ -46,8 +46,14 @@ Register `(np/ann-passes lower)` in the checking-only namespace, then check the implementation namespace with `typed.clojure/check-ns-clj`. Generated signatures are contracts, not a replacement for checking function bodies. -`:processors {Binding lower-binding}` names other nonterminal processors; use -ordinary `declare` for mutual recursion. `:args [ContextType]` types leading +`:processors {Binding lower-binding}` names other nonterminal processors. Group +mutually recursive processors with `np/defpasses`; each entry uses `defpass` +syntax without the macro name. See the [recursive traversal example](../test/com/tailrecursion/nanopass/passes.clj). +Expansion produces one +lexical `letfn` group and exports its functions with their individual signatures. +The checker checks every body; the group is redefined as a unit. + +`:args [ContextType]` types leading arguments, which generated traversal forwards unchanged. Write explicit clauses when lexical context changes. With `:body true`, supply one ordinary expression and any result type in `:to`, including state/result tuples. Automatic clauses diff --git a/src/com/tailrecursion/blacktide/compiler.clj b/src/com/tailrecursion/blacktide/compiler.clj index 21507eb..c4e3f66 100644 --- a/src/com/tailrecursion/blacktide/compiler.clj +++ b/src/com/tailrecursion/blacktide/compiler.clj @@ -3,41 +3,72 @@ macro supplies marker Vars and the caller's lexical environment." (:require [com.tailrecursion.blacktide.compiler.errors :as errors] [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.source :as source] - [com.tailrecursion.blacktide.compiler.source-jvm :as facts] - [com.tailrecursion.blacktide.compiler.boundaries :as boundaries] - [com.tailrecursion.blacktide.compiler.cases :as cases] - [com.tailrecursion.blacktide.compiler.assignment :as assignment] - [com.tailrecursion.blacktide.compiler.source-values :as source-values] - [com.tailrecursion.blacktide.compiler.collections :as collections] - [com.tailrecursion.blacktide.compiler.order :as order] - [com.tailrecursion.blacktide.compiler.prepare :as prepare] - [com.tailrecursion.blacktide.compiler.lower :as lower] - [com.tailrecursion.blacktide.compiler.transfers :as transfers] - [com.tailrecursion.blacktide.compiler.graph :as graph] - [com.tailrecursion.blacktide.compiler.backend :as backend] - [com.tailrecursion.blacktide.compiler.host-emit :as host] + [com.tailrecursion.blacktide.compiler.passes.source :as source] + [com.tailrecursion.blacktide.compiler.passes.source-jvm :as facts] + [com.tailrecursion.blacktide.compiler.passes.boundaries :as boundaries] + [com.tailrecursion.blacktide.compiler.passes.cases :as cases] + [com.tailrecursion.blacktide.compiler.passes.assignment :as assignment] + [com.tailrecursion.blacktide.compiler.passes.source-values :as source-values] + [com.tailrecursion.blacktide.compiler.passes.collections :as collections] + [com.tailrecursion.blacktide.compiler.passes.order :as order] + [com.tailrecursion.blacktide.compiler.passes.prepare :as prepare] + [com.tailrecursion.blacktide.compiler.passes.lower :as lower] + [com.tailrecursion.blacktide.compiler.passes.transfers :as transfers] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] + [com.tailrecursion.blacktide.compiler.passes.feasible :as feasible] + [com.tailrecursion.blacktide.compiler.passes.value-facts :as value-facts] + [com.tailrecursion.blacktide.compiler.passes.joins :as joins] + [com.tailrecursion.blacktide.compiler.passes.structure :as structure] + [com.tailrecursion.blacktide.compiler.passes.invariants :as invariants] + [com.tailrecursion.blacktide.compiler.passes.storage :as storage] + [com.tailrecursion.blacktide.compiler.passes.compact :as compact] + [com.tailrecursion.blacktide.compiler.passes.structured-emit :as structured] + [com.tailrecursion.blacktide.compiler.passes.emit :as emit] + [com.tailrecursion.blacktide.compiler.passes.host-emit :as host] [typed.clojure :as-alias t])) -(np/defpass compile-tree {:from [s/Bound Expr] :to [l/Machine Code] :args [s/Bound-Supply] :body true} +(np/defpass compile-program + {:from [l/Machine Program] :to [l/Machine Code] :body true} + [machine] + (let [cfg (feasible/program {:entry (:entry machine) :blocks (:blocks machine)}) + reduced (graph/analyze cfg) + analysis (value-facts/analyze reduced) + planned (joins/plan analysis) + decision (structure/recover planned)] + (if (= :structured (:op decision)) + (let [joined (invariants/expression #{} (:body decision)) + stored (storage/select-storage analysis joined) + compact (compact/program stored)] + (structured/expression (:types compact) (:body compact))) + (emit/program-form machine)))) + +(np/defpass compile-tree + {:from [l/Bound Expr] :to [l/Machine Code] :args [l/Bound-Supply] :body true} [supply expr] - (let [[supply expr] (assignment/parallel supply (assignment/sequential (source-values/expression (boundaries/classify expr)))) - [supply expr] (order/normalize supply (prepare/prepare (collections/normalize (cases/normalize expr)))) - program (graph/analyze (transfers/resolve-program (lower/compile-graph supply expr)))] - (host/box-form (backend/compile-program program)))) + (let [classified (boundaries/classify expr) + values (source-values/expression classified) + sequential (assignment/sequential values) + [supply assigned] (assignment/parallel supply sequential) + selected (cases/normalize assigned) + constructed (collections/normalize selected) + prepared (prepare/prepare constructed) + [supply ordered] (order/normalize supply prepared) + symbolic (lower/compile-graph supply ordered) + cfg (transfers/resolve-program symbolic) + machine (graph/analyze cfg)] + (host/box-form (compile-program machine)))) (np/defpass caller-locals - {:from [s/Bound Context] :to [s/Bound Context] :body true} + {:from [l/Bound Context] :to [l/Bound Context] :body true} [ctx] (assoc ctx :locals - (reduce-kv (fn [^{::t/- (t/Map t/Sym s/Bound-Local)} out ^{::t/- t/Sym} name ^{::t/- t/Any} binding] + (reduce-kv (fn [^{::t/- (t/Map t/Sym l/Bound-Local)} out ^{::t/- t/Sym} name ^{::t/- t/Any} binding] (assoc out name {:id name :name name :mutable? false :external? true :jvm (facts/caller-facts name binding)})) {} (:macro-env ctx)))) (np/defpass compile-form - {:from [s/Bound Raw] :to [l/Machine Code] + {:from [l/Bound Raw] :to [l/Machine Code] :args [(t/Map t/Sym t/Any) (t/Map t/AnyVar t/Kw)] :body true} [env markers form] (let [_ (assert (seq? form) "Expected a loophole macro form") diff --git a/src/com/tailrecursion/blacktide/compiler/analysis_languages.clj b/src/com/tailrecursion/blacktide/compiler/analysis_languages.clj deleted file mode 100644 index 9fd8408..0000000 --- a/src/com/tailrecursion/blacktide/compiler/analysis_languages.clj +++ /dev/null @@ -1,23 +0,0 @@ -(ns com.tailrecursion.blacktide.compiler.analysis-languages - "Value proofs and join plans for checked control-flow graphs. Bottom is an - unresolved copy cycle; boxed values have no primitive storage proof." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [typed.clojure :as-alias t])) - -(np/deflanguage Analysis - {Type (t/U ':bottom ':boxed java.lang.Class) - Types (t/Map t/Sym Type) - Definitions (t/Map t/Sym (t/Vec l/Machine-Rhs)) - Program (t/HMap :mandatory {:machine l/Machine-Program :types Types - :reachable (t/Set l/Machine-Label)} :complete? true)}) - -(np/derive-language Planned Analysis - {:replace - {Postdominators (t/Map l/Machine-Label (t/Set l/Machine-Label)) - Region (t/HMap :mandatory {:definite (t/Set t/Sym) :changed (t/Set t/Sym)} :complete? true) - Regions (t/Map l/Machine-Label Region) - Join (t/HMap :mandatory {:target l/Machine-Label :result t/Sym} :complete? true) - Joins (t/Map l/Machine-Label Join) - Program (t/HMap :mandatory {:machine l/Machine-Program :types Types - :reachable (t/Set l/Machine-Label) :joins Joins} :complete? true)}}) diff --git a/src/com/tailrecursion/blacktide/compiler/assignment.clj b/src/com/tailrecursion/blacktide/compiler/assignment.clj deleted file mode 100644 index f79276e..0000000 --- a/src/com/tailrecursion/blacktide/compiler/assignment.clj +++ /dev/null @@ -1,123 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.assignment - "Lower sequential batches to writes, then parallel batches to saved RHSs and - writes. Pending parallel writes do not execute if an RHS abandons evaluation." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as l] - [com.tailrecursion.blacktide.compiler.source :as source] - [typed.clojure :as-alias t])) - -(declare sequential sequential-binding sequential-pair sequential-forms sequential-pairs parallel) - -(np/defpass sequential-pair - {:from [l/Classified Assignment] :to [l/Sequential Assignment] - :processors {Local clojure.core/identity Expr sequential}} - [pair]) - -(np/defpass sequential-pairs - {:from [l/Classified Assignments] :to [l/Sequential Assignments] :processors {Assignment sequential-pair}} - [pairs]) - -(np/defpass sequential-forms - {:from [l/Classified Exprs] :to [l/Sequential Exprs] :processors {Expr sequential}} - [forms]) - -(np/defpass sequential-node - {:from [l/Classified Node] :to [l/Sequential Node] - :processors {Local clojure.core/identity Raw clojure.core/identity CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity - Expr sequential Exprs sequential-forms Binding sequential-binding Assignments sequential-pairs}} - [node] - :setq {:op :do :forms - (mapv (fn [pair] - (let [value (sequential (:value pair))] - (assoc value :node {:op :write :target (:target pair) :value value}))) (:pairs node))}) - -(np/defpass sequential-binding - {:from [l/Classified Binding] :to [l/Sequential Binding] - :processors {Local clojure.core/identity Expr sequential}} - [binding]) - -(np/defpass sequential - {:from [l/Classified Expr] :to [l/Sequential Expr] :body true} - [expr] - (assoc expr :node (sequential-node (:node expr)))) - -(np/defpass parallel-forms - {:from [l/Sequential Exprs] :to (t/HVec [l/Bound-Supply l/Assigned-Exprs]) - :args [l/Bound-Supply] :body true} - [supply forms] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Exprs])} acc ^{::t/- l/Sequential-Expr} expr] - (let [[supply out] acc [supply expr] (parallel supply expr)] [supply (conj out expr)])) - [supply []] forms)) - -(np/defpass parallel-bindings - {:from [l/Sequential Bindings] :to (t/HVec [l/Bound-Supply l/Assigned-Bindings]) - :args [l/Bound-Supply] :body true} - [supply bindings] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings])} acc ^{::t/- l/Sequential-Binding} binding] - (let [[supply out] acc [supply init] (parallel supply (:init binding))] - [supply (conj out (assoc binding :init init))])) [supply []] bindings)) - -(np/defpass save-parallel-values - {:from [l/Sequential Assignments] - :to (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs]) :args [l/Bound-Supply] :body true} - [supply pairs] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs])} acc - ^{::t/- l/Sequential-Assignment} pair] - (let [[supply bindings writes] acc [supply value] (parallel supply (:value pair)) - [supply id] (source/fresh "parallel" supply) - local {:id id :name id :mutable? false :external? false :jvm (:jvm value)} - read (assoc value :node {:op :local :local local}) - write (assoc value :node {:op :write :target (:target pair) :value read})] - [supply (conj bindings {:local local :init value}) (conj writes write)])) [supply [] []] pairs)) - -(np/defpass parallel-node - {:from [l/Sequential Node] :to (t/HVec [l/Bound-Supply l/Assigned-Node]) :args [l/Bound-Supply]} - [supply node] - :literal [supply node] - :local [supply node] - :global [supply node] - :var [supply node] - :label [supply node] - :goto [supply node] - :native [supply node] - :dispatch (let [[supply value] (parallel supply (:value node))] - [supply {:op :dispatch :value value :spec (:spec node)}]) - :case (let [[supply selector] (parallel supply (:selector node)) - [supply arms] (parallel-forms supply (:arms node))] - [supply {:op :case :selector selector :arms arms}]) - :recur (let [[supply args] (parallel-forms supply (:args node))] - [supply {:op :recur :target (:target node) :args args}]) - :loop (let [[supply bindings] (parallel-bindings supply (:bindings node)) - [supply body] (parallel supply (:body node))] - [supply {:op :loop :id (:id node) :bindings bindings :body body}]) - :if (let [[supply test] (parallel supply (:test node)) - [supply yes] (parallel supply (:then node)) - [supply no] (parallel supply (:else node))] - [supply {:op :if :test test :then yes :else no}]) - :throw (let [[supply value] (parallel supply (:value node))] [supply {:op :throw :value value}]) - :write (let [[supply value] (parallel supply (:value node))] - [supply {:op :write :target (:target node) :value value}]) - :do (let [[supply forms] (parallel-forms supply (:forms node))] [supply {:op :do :forms forms}]) - :host (let [[supply args] (parallel-forms supply (:args node))] - [supply {:op :host :operation (:operation node) :args args}]) - :invoke (let [[supply function] (parallel supply (:function node)) - [supply args] (parallel-forms supply (:args node))] - [supply {:op :invoke :function function :args args}]) - :collection (let [[supply items] (parallel-forms supply (:items node)) - [supply metadata] (if-let [metadata (:metadata node)] (parallel supply metadata) [supply nil])] - [supply {:op :collection :kind (:kind node) :items items :metadata metadata}]) - :let (let [[supply bindings] (parallel-bindings supply (:bindings node)) - [supply body] (parallel supply (:body node))] - [supply {:op :let :bindings bindings :body body}]) - :psetq (let [[supply bindings writes] (save-parallel-values supply (:pairs node)) - template (first writes)] - (if template - [supply {:op :let :bindings bindings - :body (assoc template :node {:op :do :forms (conj writes (assoc template :node {:op :literal :value nil}))})}] - [supply {:op :literal :value nil}]))) - -(np/defpass parallel - {:from [l/Sequential Expr] :to (t/HVec [l/Bound-Supply l/Assigned-Expr]) - :args [l/Bound-Supply] :body true} - [supply expr] - (let [[supply node] (parallel-node supply (:node expr))] [supply (assoc expr :node node)])) diff --git a/src/com/tailrecursion/blacktide/compiler/backend.clj b/src/com/tailrecursion/blacktide/compiler/backend.clj deleted file mode 100644 index 279f53a..0000000 --- a/src/com/tailrecursion/blacktide/compiler/backend.clj +++ /dev/null @@ -1,26 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.backend - "Select checked structured code when eligible, otherwise the general machine. - Independent models and both emitters exercise the same source semantics." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.feasible :as feasible] - [com.tailrecursion.blacktide.compiler.graph :as graph] - [com.tailrecursion.blacktide.compiler.invariants :as invariants] - [com.tailrecursion.blacktide.compiler.value-facts :as facts] - [com.tailrecursion.blacktide.compiler.joins :as joins] - [com.tailrecursion.blacktide.compiler.storage :as storage] - [com.tailrecursion.blacktide.compiler.compact :as compact] - [com.tailrecursion.blacktide.compiler.structure :as structure] - [com.tailrecursion.blacktide.compiler.structured-emit :as structured] - [com.tailrecursion.blacktide.compiler.emit :as emit])) - -(np/defpass compile-program {:from [l/Machine Program] :to [l/Machine Code] :body true} - [machine] - (let [program (-> {:entry (:entry machine) :blocks (:blocks machine)} - feasible/program graph/analyze) - analysis (facts/analyze program) - decision (structure/recover (joins/plan analysis))] - (if (= :structured (:op decision)) - (let [program (compact/program (storage/select-storage analysis (invariants/expression #{} (:body decision))))] - (structured/expression (:types program) (:body program))) - (emit/program-form machine)))) diff --git a/src/com/tailrecursion/blacktide/compiler/boundaries.clj b/src/com/tailrecursion/blacktide/compiler/boundaries.clj deleted file mode 100644 index d7e1d5d..0000000 --- a/src/com/tailrecursion/blacktide/compiler/boundaries.clj +++ /dev/null @@ -1,70 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.boundaries - "Classify analyzed native bodies before expression lowering. Native subtrees - exclude DSL effects; lowered loops materialize their source representations." - (:require [com.tailrecursion.blacktide.compiler.effects :refer [effects]] - [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.loop-facts :as loops] - [typed.clojure :as-alias t])) - -(declare native native-binding native-forms classify classify-binding classify-forms classify-pair classify-pairs) - -(np/defpass native-node - {:from [s/Bound Node] :to [s/Native Node] - :processors {Expr native Exprs native-forms Binding native-binding}} - [node] - :literal node :local node :global node :var node - :case {:op :case :selector (native (:selector node)) - :arms (native-forms (:arms node))} - :dispatch {:op :dispatch :value (native (:value node)) :spec (:spec node)} - :collection {:op :collection :kind (:kind node) :items (native-forms (:items node)) - :metadata (when-let [metadata (:metadata node)] (native metadata))} - :recur {:op :recur :target (:target node) :args (mapv native (:args node))} - :loop {:op :loop :id (:id node) :bindings (mapv native-binding (:bindings node)) - :body (native (:body node))} - :setq (if (empty? (:pairs node)) {:op :literal :value nil} (throw (IllegalStateException. "Native local write"))) - :psetq (if (empty? (:pairs node)) {:op :literal :value nil} (throw (IllegalStateException. "Native parallel write"))) - :label (throw (IllegalStateException. "Native label")) - :goto (throw (IllegalStateException. "Native transfer"))) - -(np/defpass native-binding {:from [s/Bound Binding] :to [s/Native Binding] :body true} - [binding] (assoc binding :init (native (:init binding)))) -(np/defpass native-forms {:from [s/Bound Exprs] :to [s/Native Exprs] :processors {Expr native}} - [forms]) -(np/defpass native {:from [s/Bound Expr] :to [s/Native Expr] :body true} - [expr] - (assoc expr :node (native-node (:node expr)))) - -(np/defpass classify-node - {:from [s/Bound Node] :to [s/Classified Node] - :processors {Expr classify Exprs classify-forms Binding classify-binding Assignments classify-pairs - Local clojure.core/identity RecurTarget clojure.core/identity - Raw clojure.core/identity CollectionKind clojure.core/identity CaseSpec clojure.core/identity}} - [node] - :loop {:op :loop :id (:id node) :bindings (mapv classify-binding (:bindings node)) :body (classify (:body node))}) - -(np/defpass classify-pair {:from [s/Bound Assignment] :to [s/Classified Assignment] - :processors {Expr classify Local clojure.core/identity}} - [pair]) -(np/defpass classify-pairs {:from [s/Bound Assignments] :to [s/Classified Assignments] :processors {Assignment classify-pair}} - [pairs]) -(np/defpass classify-binding {:from [s/Bound Binding] :to [s/Classified Binding] - :processors {Expr classify Local clojure.core/identity}} - [binding]) -(np/defpass classify-forms {:from [s/Bound Exprs] :to [s/Classified Exprs] :processors {Expr classify}} - [forms]) -(np/defpass classify {:from [s/Bound Expr] :to [s/Classified Expr] :body true} - [expr] - (let [node (:node expr)] - (cond - ;; KeywordInvokeExpr snapshots its cache before evaluating the target. - ;; Retain the whole call when the target needs no DSL control or writes; - ;; saving the target first would change reentrant lookup behavior. - (and (= :invoke (:op node)) (= 1 (count (:args node))) - (= :literal (:op (:node (:function node)))) - (keyword? (:form (:site (:function node)))) (not (effects expr))) - (assoc expr :node {:op :native :value (native expr)}) - (and (= :loop (:op node)) (not (effects expr))) - (assoc expr :node {:op :native :value (native expr)}) - (= :loop (:op node)) (let [expr (loops/lower expr)] (assoc expr :node (classify-node (:node expr)))) - :else (assoc expr :node (classify-node node))))) diff --git a/src/com/tailrecursion/blacktide/compiler/cases.clj b/src/com/tailrecursion/blacktide/compiler/cases.clj deleted file mode 100644 index 422d17e..0000000 --- a/src/com/tailrecursion/blacktide/compiler/cases.clj +++ /dev/null @@ -1,24 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.cases - "Separate source case from ordinal arm selection after native constant dispatch. - Each source arm occurs once, including grouped and hash-collision alternatives." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s])) - -(declare normalize normalize-binding normalize-forms) - -(np/defpass normalize-node - {:from [s/Assigned Node] :to [s/Cases Node] - :processors {Expr normalize Exprs normalize-forms Binding normalize-binding - Raw clojure.core/identity Local clojure.core/identity CollectionKind clojure.core/identity - RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} - [node] - :case - {:op :select :selector (normalize (:selector node)) :arms (normalize-forms (:arms node))}) - -(np/defpass normalize-binding {:from [s/Assigned Binding] :to [s/Cases Binding] - :processors {Expr normalize Local clojure.core/identity}} - [binding]) -(np/defpass normalize-forms {:from [s/Assigned Exprs] :to [s/Cases Exprs] :processors {Expr normalize}} - [forms]) -(np/defpass normalize {:from [s/Assigned Expr] :to [s/Cases Expr] :body true} - [expr] (assoc expr :node (normalize-node (:node expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/collections.clj b/src/com/tailrecursion/blacktide/compiler/collections.clj deleted file mode 100644 index 02bd5d0..0000000 --- a/src/com/tailrecursion/blacktide/compiler/collections.clj +++ /dev/null @@ -1,71 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.collections - "Preserve native constant collections and construction checks. Metadata keeps - construction dynamic and is evaluated only after that construction succeeds." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as l] - [com.tailrecursion.blacktide.compiler.languages :as m] - [com.tailrecursion.blacktide.compiler.host-emit :as host] - [typed.clojure :as-alias t])) - -(declare normalize normalize-binding) - -(np/defpass form - {:from [l/Collections CollectionKind] :to [m/Machine Code] - :args [(t/Vec m/Machine-Code) t/Bool] :body true} - [items unique? kind] - (if (= :vector kind) - (if (<= (count items) 6) - (list* 'clojure.lang.Tuple/create items) - ;; An Object expression prevents constant folding without unboxing the - ;; first literal, which would lose its original numeric width or identity. - (list 'do (assoc items 0 (host/box-form (nth items 0))))) - (list (if (= :set kind) 'clojure.lang.RT/set - (if unique? 'clojure.lang.RT/mapUniqueKeys 'clojure.lang.RT/map)) - (list 'clojure.lang.RT/object_array items)))) - -(np/defpass normalize-forms - {:from [l/Cases Exprs] :to [l/Collections Exprs] :processors {Expr normalize}} - [forms]) - -(np/defpass literal-values - {:from [l/Collections Exprs] :to (t/Option (t/Vec t/Any)) :body true} - [items] - (reduce (fn [^{::t/- (t/Option (t/Vec t/Any))} values ^{::t/- l/Collections-Expr} expr] - (let [node (:node expr)] - (if (and values (= :literal (:op node))) (conj values (:value node)) (reduced nil)))) - [] items)) - -(np/defpass constant - {:from [l/Collections CollectionKind] :to t/Any :args [(t/Vec t/Any)] :body true} - [values kind] - (case kind - :vector (vec values) - :map (reduce (fn [^{::t/- (t/Map t/Any t/Any)} out ^{::t/- (t/Seq t/Any)} pair] - (assoc out (first pair) (second pair))) {} (partition 2 values)) - :set (set values))) - -(np/defpass normalize-node - {:from [l/Cases Node] :to [l/Collections Node] - :processors {Expr normalize Exprs normalize-forms Binding normalize-binding Raw clojure.core/identity - Local clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} - [node] - :collection - (let [items (mapv normalize (:items node)) metadata (some-> (:metadata node) normalize) - values (literal-values items) - keys (when (= :map (:kind node)) (literal-values (vec (take-nth 2 items)))) - unique? (boolean (and keys (= (count keys) (count (set keys)))))] - (when (and (nil? metadata) keys (not unique?)) - (throw (IllegalArgumentException. "Duplicate constant keys in map"))) - (if (and (nil? metadata) values) - {:op :literal :value (constant values (:kind node))} - {:op :build :kind (:kind node) :items items :metadata metadata :unique? unique?}))) - -(np/defpass normalize-binding - {:from [l/Cases Binding] :to [l/Collections Binding] - :processors {Expr normalize Local clojure.core/identity}} - [binding]) - -(np/defpass normalize - {:from [l/Cases Expr] :to [l/Collections Expr] :body true} - [expr] - (assoc expr :node (normalize-node (:node expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/compact_languages.clj b/src/com/tailrecursion/blacktide/compiler/compact_languages.clj deleted file mode 100644 index aa881cd..0000000 --- a/src/com/tailrecursion/blacktide/compiler/compact_languages.clj +++ /dev/null @@ -1,24 +0,0 @@ -(ns com.tailrecursion.blacktide.compiler.compact-languages - "Lexical expressions after storage selection. Discarded results have an effect - destination; total primitive computations may occupy operand positions. - Native results may remain in tail position. Loop initializers and recur - arguments are explicit, separate from binders." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [typed.clojure :as-alias t])) - -(np/derive-language Compact l/Machine - {:add {Atom [(t/HMap :mandatory {:op ':inline :value Rhs :type (t/Option java.lang.Class)} :complete? true)]} - :replace - {Binding (t/HMap :mandatory {:target t/Sym :value Atom} :complete? true) - Expr (t/U (t/HMap :mandatory {:op ':bind :binding Operation :body Expr} :complete? true) - (t/HMap :mandatory {:op ':effect :binding Operation :body Expr} :complete? true) - (t/HMap :mandatory {:op ':join :target t/Sym :value Expr :body Expr} :complete? true) - (t/HMap :mandatory {:op ':branch :test Atom :yes Expr :no Expr} :complete? true) - (t/HMap :mandatory {:op ':switch :test Atom :arms (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':loop :bindings (t/Vec Binding) :body Expr} :complete? true) - (t/HMap :mandatory {:op ':recur :args (t/Vec Atom)} :complete? true) - (t/HMap :mandatory {:op ':return :value Atom} :complete? true) - (t/HMap :mandatory {:op ':throw :value Atom} :complete? true)) - Types (t/Map t/Sym java.lang.Class) - Program (t/HMap :mandatory {:body Expr :types Types} :complete? true)}}) diff --git a/src/com/tailrecursion/blacktide/compiler/effects.clj b/src/com/tailrecursion/blacktide/compiler/effects.clj deleted file mode 100644 index 8562b1b..0000000 --- a/src/com/tailrecursion/blacktide/compiler/effects.clj +++ /dev/null @@ -1,28 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.effects - "Identify local writes and transfers that prevent native subtree emission." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [typed.clojure :as-alias t])) - -(declare effects) - -(np/defpass effect-node {:from [s/Bound Node] :to t/Bool} - [node] - :literal false :local false :global false :var false - :setq (boolean (seq (:pairs node))) - :psetq (boolean (seq (:pairs node))) - :label true :goto true - :if (or (effects (:test node)) (effects (:then node)) (effects (:else node))) - :case (or (effects (:selector node)) (boolean (some effects (:arms node)))) - :dispatch (effects (:value node)) - :throw (effects (:value node)) - :host (boolean (some effects (:args node))) - :invoke (or (effects (:function node)) (boolean (some effects (:args node)))) - :collection (or (boolean (some effects (:items node))) (boolean (some-> (:metadata node) effects))) - :do (boolean (some effects (:forms node))) - :let (or (boolean (some #(effects (:init %)) (:bindings node))) (effects (:body node))) - :loop (or (boolean (some #(effects (:init %)) (:bindings node))) (effects (:body node))) - :recur (boolean (some effects (:args node)))) - -(np/defpass effects {:from [s/Bound Expr] :to t/Bool :body true} - [expr] (effect-node (:node expr))) diff --git a/src/com/tailrecursion/blacktide/compiler/errors.clj b/src/com/tailrecursion/blacktide/compiler/errors.clj index caef819..b07e727 100644 --- a/src/com/tailrecursion/blacktide/compiler/errors.clj +++ b/src/com/tailrecursion/blacktide/compiler/errors.clj @@ -2,24 +2,24 @@ "Source locations and structured errors shared by syntax analysis and lowering. DSL markers obey the same Var visibility rules as other source forms." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] + [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) -(np/defpass site {:from [s/Bound Raw] :to [s/Bound Site] :args [s/Bound-Site] :body true} +(np/defpass site {:from [l/Bound Raw] :to [l/Bound Site] :args [l/Bound-Site] :body true} [parent form] - ((fn ^{::t/- [t/Any t/Any t/Any :-> s/Bound-Site]} location [file line column] + ((fn ^{::t/- [t/Any t/Any t/Any :-> l/Bound-Site]} location [file line column] {:form form :file (if (string? file) file (:file parent)) :line (if (integer? line) line (:line parent)) :column (if (integer? column) column (:column parent))}) (:file (meta form)) (:line (meta form)) (:column (meta form)))) (np/defpass fail - {:from [s/Bound Site] :to t/Nothing :args [t/Kw t/Str (t/Map t/Kw t/Any)] :body true} + {:from [l/Bound Site] :to t/Nothing :args [t/Kw t/Str (t/Map t/Kw t/Any)] :body true} [kind message details site] (throw (ex-info message (merge site details {:kind kind})))) (np/defpass check-visibility - {:from [s/Bound Site] :to nil :args [t/AnyVar] :body true} + {:from [l/Bound Site] :to nil :args [t/AnyVar] :body true} [v site] ;; The public Var field is authoritative; :ns metadata can be changed. (when (and (not (.isPublic ^clojure.lang.Var v)) diff --git a/src/com/tailrecursion/blacktide/compiler/jvm.clj b/src/com/tailrecursion/blacktide/compiler/jvm.clj index 14614fc..ef77400 100644 --- a/src/com/tailrecursion/blacktide/compiler/jvm.clj +++ b/src/com/tailrecursion/blacktide/compiler/jvm.clj @@ -3,7 +3,7 @@ analyzer. Method inference follows the pinned Clojure overload rules." (:refer-clojure :exclude [methods]) (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.jvm-languages :as l] + [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) (np/defpass resolved-class diff --git a/src/com/tailrecursion/blacktide/compiler/jvm_languages.clj b/src/com/tailrecursion/blacktide/compiler/jvm_languages.clj deleted file mode 100644 index 7a04a91..0000000 --- a/src/com/tailrecursion/blacktide/compiler/jvm_languages.clj +++ /dev/null @@ -1,35 +0,0 @@ -(ns com.tailrecursion.blacktide.compiler.jvm-languages - "JVM facts and host operation descriptors contain no executable source children. - Unknown type, known nil and a known JVM class are distinct facts." - (:require [com.tailrecursion.nanopass :as np] - [typed.clojure :as-alias t])) - -(np/deflanguage JVM - {Raw t/Any - Type (t/U ':unknown nil java.lang.Class) - Seed (t/HMap :mandatory {:hint Raw :var-meta (t/Map t/Any t/Any)} :complete? true) - Fact (t/HMap :mandatory {:hint Raw :var-meta (t/Map t/Any t/Any) - :type Type :primitive? t/Bool :hint-error (t/Option t/Str)} :complete? true) - Types (t/Vec Type) - Tags (t/Option (t/Vec Raw)) - Mode (t/U ':method ':member ':field) - Protocol (t/HMap :mandatory {:class (t/Option java.lang.Class) :member (t/Option t/Sym) - :parameters (t/Vec java.lang.Class)} :complete? true) - Operation (t/U (t/HMap :mandatory {:op ':keyword-site :key t/Kw} :complete? true) - (t/HMap :mandatory {:op ':protocol :method Protocol} :complete? true) - (t/HMap :mandatory {:op ':direct :class java.lang.Class :parameters (t/Vec java.lang.Class) - :variadic? t/Bool :return java.lang.Class} :complete? true) - (t/HMap :mandatory {:op ':instanceof :class java.lang.Class} :complete? true) - (t/HMap :mandatory {:op ':adapt :class java.lang.Class} :complete? true) - (t/HMap :mandatory {:op ':constructor :class java.lang.Class :tags Tags} :complete? true) - (t/HMap :mandatory {:op ':static :class java.lang.Class :member t/Sym :tags Tags} :complete? true) - (t/HMap :mandatory {:op ':instance :class (t/Option java.lang.Class) :member t/Sym - :mode Mode :tags Tags} :complete? true) - (t/HMap :mandatory {:op ':static-field :class java.lang.Class :member t/Sym} :complete? true) - (t/HMap :mandatory {:op ':method-value :name t/Sym} :complete? true)) - Syntax (t/HMap :mandatory {:operation Operation :operands (t/Vec Raw)} :complete? true) - Method (t/U java.lang.reflect.Method java.lang.reflect.Constructor) - Methods (t/Vec Method) - Conversion (t/HMap :mandatory {:type java.lang.Class :parameter? t/Bool} :complete? true) - Plan (t/HMap :mandatory {:operation Operation :conversions (t/Vec (t/Option Conversion))} :complete? true) - Selection (t/HMap :mandatory {:method (t/Option Method) :exact? t/Bool :tied? t/Bool} :complete? true)}) diff --git a/src/com/tailrecursion/blacktide/compiler/languages.clj b/src/com/tailrecursion/blacktide/compiler/languages.clj index 92f9341..75fe9fb 100644 --- a/src/com/tailrecursion/blacktide/compiler/languages.clj +++ b/src/com/tailrecursion/blacktide/compiler/languages.clj @@ -1,24 +1,147 @@ (ns com.tailrecursion.blacktide.compiler.languages - "Loophole's backend languages. Stable operands are separate from evaluations; - allocated machines explicitly name the values live at every block entry." + "All compiler languages, in dependency order: JVM facts, source trees, graphs, + analyses and lexical control. Local nonterminals rebind during derivation; + qualified l/* types refer to another language here. Execution order lives in + com.tailrecursion.blacktide.compiler." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [typed.clojure :as-alias t] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j])) + [com.tailrecursion.blacktide.compiler.languages :as-alias l] + [typed.clojure :as-alias t])) +;; Shared JVM facts and operations. +(np/deflanguage JVM + {Raw t/Any + Type (t/U ':unknown nil java.lang.Class) + Seed (t/HMap :mandatory {:hint Raw :var-meta (t/Map t/Any t/Any)} :complete? true) + Fact (t/HMap :mandatory {:hint Raw :var-meta (t/Map t/Any t/Any) + :type Type :primitive? t/Bool :hint-error (t/Option t/Str)} :complete? true) + Types (t/Vec Type) + Tags (t/Option (t/Vec Raw)) + Mode (t/U ':method ':member ':field) + Protocol (t/HMap :mandatory {:class (t/Option java.lang.Class) :member (t/Option t/Sym) + :parameters (t/Vec java.lang.Class)} :complete? true) + Operation (t/U (t/HMap :mandatory {:op ':keyword-site :key t/Kw} :complete? true) + (t/HMap :mandatory {:op ':protocol :method Protocol} :complete? true) + (t/HMap :mandatory {:op ':direct :class java.lang.Class :parameters (t/Vec java.lang.Class) + :variadic? t/Bool :return java.lang.Class} :complete? true) + (t/HMap :mandatory {:op ':instanceof :class java.lang.Class} :complete? true) + (t/HMap :mandatory {:op ':adapt :class java.lang.Class} :complete? true) + (t/HMap :mandatory {:op ':constructor :class java.lang.Class :tags Tags} :complete? true) + (t/HMap :mandatory {:op ':static :class java.lang.Class :member t/Sym :tags Tags} :complete? true) + (t/HMap :mandatory {:op ':instance :class (t/Option java.lang.Class) :member t/Sym + :mode Mode :tags Tags} :complete? true) + (t/HMap :mandatory {:op ':static-field :class java.lang.Class :member t/Sym} :complete? true) + (t/HMap :mandatory {:op ':method-value :name t/Sym} :complete? true)) + Syntax (t/HMap :mandatory {:operation Operation :operands (t/Vec Raw)} :complete? true) + Method (t/U java.lang.reflect.Method java.lang.reflect.Constructor) + Methods (t/Vec Method) + Conversion (t/HMap :mandatory {:type java.lang.Class :parameter? t/Bool} :complete? true) + Plan (t/HMap :mandatory {:operation Operation :conversions (t/Vec (t/Option Conversion))} :complete? true) + Selection (t/HMap :mandatory {:method (t/Option Method) :exact? t/Bool :tied? t/Bool} :complete? true)}) + +;; Source normalization: Bound -> Classified -> Sequential -> Assigned -> +;; Cases -> Collections -> Prepared -> Ordered. Native is a restricted subtree. +(np/deflanguage Bound + {Raw t/Any + Forms (t/Vec Raw) + Site (t/HMap :mandatory {:form Raw :file (t/Option t/Str) + :line (t/Option t/Int) :column (t/Option t/Int)} :complete? true) + Jvm l/JVM-Fact + Local (t/HMap :mandatory {:id t/Sym :name t/Sym :mutable? t/Bool :external? t/Bool + :jvm Jvm} :complete? true) + Binding (t/HMap :mandatory {:local Local :init Expr} :complete? true) + Bindings (t/Vec Binding) + Assignment (t/HMap :mandatory {:target Local :value Expr} :complete? true) + Assignments (t/Vec Assignment) + RecurTarget (t/HMap :mandatory {:id t/Sym :bindings (t/Vec Local)} :complete? true) + CaseSpec (t/HMap :mandatory {:shift t/Int :mask t/Int :default t/Int + :tests (t/Map t/Int (t/HVec [Raw t/Int])) + :switch (t/U ':compact ':sparse) + :test (t/U ':int ':hash-equiv ':hash-identity) + :skip (t/Option (t/Set t/Int))} :complete? true) + CaseSyntax (t/HMap :mandatory {:selector Raw :arms Forms} :complete? true) + DispatchSyntax (t/HMap :mandatory {:value Raw :spec CaseSpec :arms Forms} :complete? true) + Exprs (t/Vec Expr) + CollectionKind (t/U ':vector ':map ':set) + Expr (t/HMap :mandatory {:site Site :jvm Jvm :node Node} :complete? true) + Node (t/U (t/HMap :mandatory {:op ':literal :value Raw} :complete? true) + (t/HMap :mandatory {:op ':local :local Local} :complete? true) + (t/HMap :mandatory {:op ':global :name t/Sym} :complete? true) + (t/HMap :mandatory {:op ':var :name t/Sym} :complete? true) + (t/HMap :mandatory {:op ':host :operation l/JVM-Operation :args (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':invoke :function Expr :args (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':collection :kind CollectionKind :items Exprs + :metadata (t/Option Expr)} :complete? true) + (t/HMap :mandatory {:op ':setq :pairs Assignments} :complete? true) + (t/HMap :mandatory {:op ':psetq :pairs Assignments} :complete? true) + (t/HMap :mandatory {:op ':if :test Expr :then Expr :else Expr} :complete? true) + (t/HMap :mandatory {:op ':case :selector Expr :arms Exprs} :complete? true) + (t/HMap :mandatory {:op ':dispatch :value Expr :spec CaseSpec} :complete? true) + (t/HMap :mandatory {:op ':throw :value Expr} :complete? true) + (t/HMap :mandatory {:op ':label :name t/Sym} :complete? true) + (t/HMap :mandatory {:op ':goto :name t/Sym} :complete? true) + (t/HMap :mandatory {:op ':do :forms (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':let :bindings (t/Vec Binding) :body Expr} :complete? true) + (t/HMap :mandatory {:op ':loop :id t/Sym :bindings (t/Vec Binding) :body Expr} :complete? true) + (t/HMap :mandatory {:op ':recur :target RecurTarget :args (t/Vec Expr)} :complete? true)) + Supply (t/HMap :mandatory {:prefix t/Str :next t/Int} :complete? true) + Context (t/HMap :mandatory {:locals (t/Map t/Sym Local) :macro-env (t/Map t/Sym Raw) + :markers (t/Map t/AnyVar t/Kw) :outer? t/Bool :transfer? t/Bool + :site Site + :recur (t/Option RecurTarget) :statement? t/Bool :tail? t/Bool} :complete? true)}) + +(np/derive-language Native Bound + {:remove {Node #{:setq :psetq :label :goto}}}) + +(np/derive-language Classified Bound + {:add {Node [(t/HMap :mandatory {:op ':native :value l/Native-Expr} + :complete? true)]}}) + +(np/derive-language Sequential Classified + {:remove {Node #{:setq}} + :add {Node [(t/HMap :mandatory {:op ':write :target Local :value Expr} :complete? true)]}}) + +(np/derive-language Assigned Sequential + {:remove {Node #{:psetq}}}) + +(np/derive-language Cases Assigned + {:remove {Node #{:case}} + :add {Node [(t/HMap :mandatory {:op ':select :selector Expr :arms Exprs} :complete? true)]}}) + +(np/derive-language Collections Cases + {:remove {Node #{:collection}} + :add {Node [(t/HMap :mandatory {:op ':build :kind CollectionKind :items Exprs :unique? t/Bool + :metadata (t/Option Expr)} :complete? true)]}}) + +(np/derive-language Prepared Collections + {:remove {Node #{:host}} + :add {Node [(t/HMap :mandatory {:op ':host-call :operation l/JVM-Operation :args (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':convert :conversion l/JVM-Conversion :value Expr} :complete? true)]}}) + +(np/derive-language Ordered Prepared + {:remove {Node #{:invoke :build :host-call :convert :recur :dispatch}} + :add {Node [(t/HMap :mandatory {:op ':convert :conversion l/JVM-Conversion :value Local} :complete? true) + (t/HMap :mandatory {:op ':dispatch :value Local :spec CaseSpec} :complete? true) + (t/HMap :mandatory {:op ':recur :target RecurTarget :args (t/Vec Local)} :complete? true) + (t/HMap :mandatory {:op ':host-call :operation l/JVM-Operation :args (t/Vec Local)} :complete? true) + (t/HMap :mandatory {:op ':invoke :function Local :args (t/Vec Local)} :complete? true) + (t/HMap :mandatory {:op ':build :kind CollectionKind :items (t/Vec Local) :unique? t/Bool} :complete? true) + (t/HMap :mandatory {:op ':metadata :value Local :metadata Local} :complete? true)]}}) + +;; Graph execution order is Symbolic -> CFG -> Machine. Derivation runs +;; the other way because earlier graphs extend the allocated machine grammar. (np/deflanguage Machine {Atom (t/U (t/HMap :mandatory {:op ':literal :value t/Any} :complete? true) (t/HMap :mandatory {:op ':local :id t/Sym} :complete? true)) - Operand (t/HMap :mandatory {:value Atom :jvm j/JVM-Fact} :complete? true) - Rhs (t/U (t/HMap :mandatory {:op ':convert :conversion j/JVM-Conversion :value Operand} :complete? true) - (t/HMap :mandatory {:op ':dispatch :value Operand :spec s/Bound-CaseSpec} :complete? true) - (t/HMap :mandatory {:op ':native :value s/Native-Expr} :complete? true) - (t/HMap :mandatory {:op ':host :operation j/JVM-Operation :args (t/Vec Operand)} :complete? true) + Operand (t/HMap :mandatory {:value Atom :jvm l/JVM-Fact} :complete? true) + Rhs (t/U (t/HMap :mandatory {:op ':convert :conversion l/JVM-Conversion :value Operand} :complete? true) + (t/HMap :mandatory {:op ':dispatch :value Operand :spec l/Bound-CaseSpec} :complete? true) + (t/HMap :mandatory {:op ':native :value l/Native-Expr} :complete? true) + (t/HMap :mandatory {:op ':host :operation l/JVM-Operation :args (t/Vec Operand)} :complete? true) (t/HMap :mandatory {:op ':value :value Atom} :complete? true) (t/HMap :mandatory {:op ':global :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':external :name t/Sym :jvm j/JVM-Fact} :complete? true) + (t/HMap :mandatory {:op ':external :name t/Sym :jvm l/JVM-Fact} :complete? true) (t/HMap :mandatory {:op ':var :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':construct :kind s/Bound-CollectionKind :items (t/Vec Atom) :unique? t/Bool} :complete? true) + (t/HMap :mandatory {:op ':construct :kind l/Bound-CollectionKind :items (t/Vec Atom) :unique? t/Bool} :complete? true) (t/HMap :mandatory {:op ':metadata :value Atom :metadata Atom} :complete? true) (t/HMap :mandatory {:op ':invoke :function Atom :args (t/Vec Atom)} :complete? true)) Operation (t/HMap :mandatory {:target t/Sym :value Rhs} :complete? true) @@ -45,10 +168,73 @@ Predecessors (t/Map Label Labels)}}) (np/derive-language Symbolic CFG - {:add {Term [(t/HMap :mandatory {:op ':transfer :name t/Sym :site s/Bound-Site} :complete? true)]} + {:add {Term [(t/HMap :mandatory {:op ':transfer :name t/Sym :site l/Bound-Site} :complete? true)]} :replace {LabelTable (t/Map t/Sym Label) Program (t/HMap :mandatory {:entry Label :blocks (t/Map Label Block) :labels LabelTable} :complete? true) - Builder (t/HMap :mandatory {:supply s/Bound-Supply :ops (t/Vec Operation) :next Label :current Label + Builder (t/HMap :mandatory {:supply l/Bound-Supply :ops (t/Vec Operation) :next Label :current Label :blocks (t/Map Label Block) :labels LabelTable :loops (t/Map t/Sym Label)} :complete? true)}}) + +;; Facts and plans used by structured control recovery. +(np/deflanguage Analysis + {Type (t/U ':bottom ':boxed java.lang.Class) + Types (t/Map t/Sym Type) + Definitions (t/Map t/Sym (t/Vec l/Machine-Rhs)) + Program (t/HMap :mandatory {:machine l/Machine-Program :types Types + :reachable (t/Set l/Machine-Label)} :complete? true)}) + +(np/derive-language Planned Analysis + {:replace + {Postdominators (t/Map l/Machine-Label (t/Set l/Machine-Label)) + Region (t/HMap :mandatory {:definite (t/Set t/Sym) :changed (t/Set t/Sym)} :complete? true) + Regions (t/Map l/Machine-Label Region) + Join (t/HMap :mandatory {:target l/Machine-Label :result t/Sym} :complete? true) + Joins (t/Map l/Machine-Label Join) + Program (t/HMap :mandatory {:machine l/Machine-Program :types Types + :reachable (t/Set l/Machine-Label) :joins Joins} :complete? true)}}) + +;; Structured recovery produces Joined expressions, then Stored programs. +(np/deflanguage Structured + {Expr (t/U (t/HMap :mandatory {:op ':bind :binding l/Machine-Operation :body Expr} :complete? true) + (t/HMap :mandatory {:op ':branch :test l/Machine-Atom :yes Expr :no Expr} :complete? true) + (t/HMap :mandatory {:op ':switch :test l/Machine-Atom :arms (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':loop :slots (t/Vec t/Sym) :body Expr} :complete? true) + (t/HMap :mandatory {:op ':recur :slots (t/Vec t/Sym)} :complete? true) + (t/HMap :mandatory {:op ':return :value l/Machine-Atom} :complete? true) + (t/HMap :mandatory {:op ':throw :value l/Machine-Atom} :complete? true)) + Build (t/U (t/HMap :mandatory {:op ':built :budget t/Int :blocks (t/Vec l/Machine-Label) :expr Expr} :complete? true) + (t/HMap :mandatory {:op ':exhausted} :complete? true)) + Arms (t/U (t/HMap :mandatory {:op ':built :budget t/Int :blocks (t/Vec l/Machine-Label) :arms (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':exhausted} :complete? true)) + Scan (t/HVec [(t/Set l/Machine-Label) (t/Set l/Machine-Label)]) + Fallback (t/U ':cycles ':validation ':budget ':native-sites) + Decision (t/U (t/HMap :mandatory {:op ':structured :body Expr} :complete? true) + (t/HMap :mandatory {:op ':fallback :reason Fallback} :complete? true))}) + +(np/derive-language Joined Structured + {:add {Expr [(t/HMap :mandatory {:op ':join :target t/Sym :value Expr :body Expr} :complete? true)]} + :replace {Context (t/HMap :mandatory {:machine l/Machine-Program :header (t/Option l/Machine-Label) + :slots (t/Vec t/Sym) :joins l/Planned-Joins + :stop (t/Option (t/HVec [l/Machine-Label t/Sym]))} :complete? true)}}) + +(np/derive-language Stored Joined + {:replace {Types (t/Map t/Sym java.lang.Class) + Program (t/HMap :mandatory {:body Expr :types Types} :complete? true)}}) + +;; Compact extends machine operands for lexical emission after storage. +(np/derive-language Compact Machine + {:add {Atom [(t/HMap :mandatory {:op ':inline :value Rhs :type (t/Option java.lang.Class)} :complete? true)]} + :replace + {Binding (t/HMap :mandatory {:target t/Sym :value Atom} :complete? true) + Expr (t/U (t/HMap :mandatory {:op ':bind :binding Operation :body Expr} :complete? true) + (t/HMap :mandatory {:op ':effect :binding Operation :body Expr} :complete? true) + (t/HMap :mandatory {:op ':join :target t/Sym :value Expr :body Expr} :complete? true) + (t/HMap :mandatory {:op ':branch :test Atom :yes Expr :no Expr} :complete? true) + (t/HMap :mandatory {:op ':switch :test Atom :arms (t/Vec Expr)} :complete? true) + (t/HMap :mandatory {:op ':loop :bindings (t/Vec Binding) :body Expr} :complete? true) + (t/HMap :mandatory {:op ':recur :args (t/Vec Atom)} :complete? true) + (t/HMap :mandatory {:op ':return :value Atom} :complete? true) + (t/HMap :mandatory {:op ':throw :value Atom} :complete? true)) + Types (t/Map t/Sym java.lang.Class) + Program (t/HMap :mandatory {:body Expr :types Types} :complete? true)}}) diff --git a/src/com/tailrecursion/blacktide/compiler/lower.clj b/src/com/tailrecursion/blacktide/compiler/lower.clj deleted file mode 100644 index e889868..0000000 --- a/src/com/tailrecursion/blacktide/compiler/lower.clj +++ /dev/null @@ -1,141 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.lower - "Lower ordered expressions into a CFG with explicit branch joins and exits. - Operands have explicit bindings; operations retain their execution position." - (:require [com.tailrecursion.blacktide.compiler.errors :as errors] - [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.source :as source] - [typed.clojure :as-alias t])) - -(declare lower lower-forms lower-bindings) - -(np/defpass fresh-block - {:from [l/Symbolic Builder] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Label]) :body true} - [builder] - [(assoc builder :next (inc (:next builder))) (:next builder)]) - -(np/defpass start - {:from [l/Symbolic Label] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} - [builder label] - (assoc builder :current label :ops [])) - -(np/defpass seal - {:from [l/Symbolic Term] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} - [builder term] - (if (contains? (:blocks builder) (:current builder)) - (throw (ex-info "Control-flow block already has a terminator" {:kind :invalid-graph :block (:current builder)})) - (assoc builder :blocks (assoc (:blocks builder) (:current builder) {:ops (:ops builder) :term term}) :ops []))) - -(np/defpass evaluate - {:from [l/Symbolic Rhs] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) - :args [l/Symbolic-Builder] :body true} - [builder rhs] - (let [[supply id] (source/fresh "value" (:supply builder))] - [(-> builder (assoc :supply supply) (update :ops conj {:target id :value rhs})) {:op :local :id id}])) - -(np/defpass lower-forms - {:from [s/Ordered Exprs] :to (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Atom)]) - :args [l/Symbolic-Builder] :body true} - [builder forms] - (reduce (fn [^{::t/- (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Atom)])} acc ^{::t/- s/Ordered-Expr} expr] - (let [[builder out] acc [builder value] (lower builder expr)] [builder (conj out value)])) - [builder []] forms)) - -(np/defpass lower-bindings - {:from [s/Ordered Bindings] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} - [builder bindings] - (reduce (fn [^{::t/- l/Symbolic-Builder} builder ^{::t/- s/Ordered-Binding} binding] - (let [[builder value] (lower builder (:init binding))] - (update builder :ops conj {:target (:id (:local binding)) :value {:op :value :value value}}))) - builder bindings)) - -(np/defpass lower-node - {:from [s/Ordered Node] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) - :args [l/Symbolic-Builder s/Ordered-Expr]} - [builder expr node] - :literal [builder {:op :literal :value (:value node)}] - :local (let [local (:local node)] - (evaluate builder (if (:external? local) {:op :external :name (:id local) :jvm (:jvm local)} - {:op :value :value {:op :local :id (:id local)}}))) - :global (evaluate builder {:op :global :name (:name node)}) - :var (evaluate builder {:op :var :name (:name node)}) - :native (evaluate builder node) - :dispatch (evaluate builder {:op :dispatch :spec (:spec node) - :value {:value {:op :local :id (:id (:value node))} :jvm (:jvm (:value node))}}) - :select - (let [[builder test] (lower builder (:selector node)) - [builder join] (fresh-block builder) - [supply result] (source/fresh "case_result" (:supply builder)) - [builder targets] (reduce (fn [^{::t/- (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Label)])} acc - ^{::t/- s/Ordered-Expr} _] - (let [[builder targets] acc [builder label] (fresh-block builder)] - [builder (conj targets label)])) - [(assoc builder :supply supply) []] (:arms node)) - builder (seal builder {:op :switch :test test :targets targets}) - builder (reduce (fn [^{::t/- l/Symbolic-Builder} builder ^{::t/- t/Int} index] - (let [[builder value] (lower (start builder (nth targets index)) (nth (:arms node) index))] - (seal (update builder :ops conj {:target result :value {:op :value :value value}}) - {:op :jump :target join}))) builder (range (count targets)))] - [(start builder join) {:op :local :id result}]) - :loop (let [builder (lower-bindings builder (:bindings node)) - [builder header] (fresh-block builder) - builder (assoc builder :loops (assoc (:loops builder) (:id node) header))] - (lower (start (seal builder {:op :jump :target header}) header) (:body node))) - :recur (let [target (:target node) - header (or (get (:loops builder) (:id target)) - (errors/fail :invalid-graph "Missing source recur target" {} (:site expr))) - writes (mapv (fn [local arg] {:target (:id local) :value {:op :value :value {:op :local :id (:id arg)}}}) - (:bindings target) (:args node)) - builder (seal (update builder :ops into writes) {:op :jump :target header}) - [builder next] (fresh-block builder)] - [(start builder next) {:op :literal :value nil}]) - :if (let [[builder test] (lower builder (:test node)) - [builder yes] (fresh-block builder) [builder no] (fresh-block builder) [builder join] (fresh-block builder) - [supply result] (source/fresh "join" (:supply builder)) - builder (seal (assoc builder :supply supply) {:op :branch :test test :yes yes :no no}) - [builder a] (lower (start builder yes) (:then node)) - builder (seal (update builder :ops conj {:target result :value {:op :value :value a}}) {:op :jump :target join}) - [builder b] (lower (start builder no) (:else node)) - builder (seal (update builder :ops conj {:target result :value {:op :value :value b}}) {:op :jump :target join})] - [(start builder join) {:op :local :id result}]) - :throw (let [[builder value] (lower builder (:value node)) - builder (seal builder {:op :throw :value value}) - [builder next] (fresh-block builder)] - [(start builder next) {:op :literal :value nil}]) - :write (let [[builder value] (lower builder (:value node))] - [(update builder :ops conj {:target (:id (:target node)) :value {:op :value :value value}}) value]) - :convert (evaluate builder {:op :convert :conversion (:conversion node) - :value {:value {:op :local :id (:id (:value node))} :jvm (:jvm (:value node))}}) - :host-call (evaluate builder {:op :host :operation (:operation node) - :args (mapv (fn [local] {:value {:op :local :id (:id local)} :jvm (:jvm local)}) (:args node))}) - :invoke (evaluate builder {:op :invoke :function {:op :local :id (:id (:function node))} - :args (mapv (fn [local] {:op :local :id (:id local)}) (:args node))}) - :build (evaluate builder {:op :construct :kind (:kind node) :unique? (:unique? node) - :items (mapv (fn [local] {:op :local :id (:id local)}) (:items node))}) - :metadata (evaluate builder {:op :metadata :value {:op :local :id (:id (:value node))} - :metadata {:op :local :id (:id (:metadata node))}}) - :label (let [key (:name node)] - (when (contains? (:labels builder) key) - (errors/fail :duplicate-label "Duplicate label" {:label (:name node)} (:site expr))) - (let [[builder label] (fresh-block builder) - builder (assoc builder :labels (assoc (:labels builder) key label)) - builder (seal builder {:op :jump :target label})] - [(start builder label) {:op :literal :value nil}])) - :goto (let [builder (seal builder {:op :transfer :name (:name node) :site (:site expr)}) - [builder next] (fresh-block builder)] - [(start builder next) {:op :literal :value nil}]) - :do (let [[builder values] (lower-forms builder (:forms node))] - [builder (or (peek values) {:op :literal :value nil})]) - :let (lower (lower-bindings builder (:bindings node)) (:body node))) - -(np/defpass lower - {:from [s/Ordered Expr] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) :args [l/Symbolic-Builder] :body true} - [builder expr] - (lower-node builder expr (:node expr))) - -(np/defpass compile-graph - {:from [s/Ordered Expr] :to [l/Symbolic Program] :args [s/Bound-Supply] :body true} - [supply expr] - (let [[builder value] (lower {:supply supply :ops [] :next 1 :current 0 :blocks {} :labels {} :loops {}} expr)] - {:entry 0 :labels (:labels builder) :blocks (:blocks (seal builder {:op :return :value value}))})) diff --git a/src/com/tailrecursion/blacktide/compiler/native.clj b/src/com/tailrecursion/blacktide/compiler/native.clj deleted file mode 100644 index fdc6176..0000000 --- a/src/com/tailrecursion/blacktide/compiler/native.clj +++ /dev/null @@ -1,108 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.native - "Analyze native free reads and recover their source types from machine storage. - Primitive source reads cannot observe the slot's box; reference reads retain - it. Internal bindings keep native inference." - (:require [clojure.set :as set] - [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.collections :as collections] - [com.tailrecursion.blacktide.compiler.host-emit :as host] - [com.tailrecursion.blacktide.compiler.case-emit :as cases] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] - [typed.clojure :as-alias t])) - -(declare boxed-reads form) - -(np/defpass hinted-form {:from [l/Machine Code] :to [l/Machine Code] :args [j/JVM-Raw] :body true} - [hint code] - ;; Var return tags may be Class objects; source form tags cannot be. - (let [hint (if (class? hint) (symbol (.getName ^Class hint)) hint)] - (with-meta (if (symbol? code) code (apply list code)) - (cond-> (meta code) hint (assoc :tag hint))))) - -(np/defpass reads-node {:from [s/Native Node] :to (t/Set t/Sym) - :args [(t/Map t/Sym java.lang.Class)]} - [types node] - :literal #{} :global #{} :var #{} - :local (if (:external? (:local node)) #{} #{(:id (:local node))}) - :host (into #{} (mapcat (partial boxed-reads types)) (:args node)) - :invoke (into (boxed-reads types (:function node)) (mapcat (partial boxed-reads types)) (:args node)) - :if (set/union (boxed-reads types (:test node)) (boxed-reads types (:then node)) (boxed-reads types (:else node))) - :case (into (boxed-reads types (:selector node)) (mapcat (partial boxed-reads types)) (:arms node)) - :dispatch (boxed-reads types (:value node)) - :throw (boxed-reads types (:value node)) - :collection (into (if-let [metadata (:metadata node)] (boxed-reads types metadata) #{}) (mapcat (partial boxed-reads types)) (:items node)) - :do (into #{} (mapcat (partial boxed-reads types)) (:forms node)) - :recur (into #{} (mapcat (partial boxed-reads types)) (:args node)) - :let (set/difference (into (boxed-reads types (:body node)) (mapcat #(boxed-reads types (:init %))) (:bindings node)) - (set (map #(get-in % [:local :id]) (:bindings node)))) - :loop (set/difference (into (boxed-reads types (:body node)) (mapcat #(boxed-reads types (:init %))) (:bindings node)) - (set (map #(get-in % [:local :id]) (:bindings node))))) - -(np/defpass boxed-reads {:from [s/Native Expr] :to (t/Set t/Sym) - :args [(t/Map t/Sym java.lang.Class)] :body true} - [types expr] - (let [node (:node expr) fact (:jvm expr)] - (if (and (= :local (:op node)) (:primitive? fact) - (= (:type fact) (get types (:id (:local node))))) - #{} (reads-node types node)))) - -(np/defpass reads {:from [s/Native Expr] :to (t/Set t/Sym) :body true} - [expr] (boxed-reads {} expr)) - - -(np/defpass literal-values {:from [s/Native Exprs] :to (t/Option (t/Vec t/Any)) :body true} - [items] - (reduce (fn [^{::t/- (t/Option (t/Vec t/Any))} values ^{::t/- s/Native-Expr} expr] - (let [node (:node expr)] - (if (and values (= :literal (:op node))) (conj values (:value node)) (reduced nil)))) [] items)) - -(np/defpass bindings-form - {:from [s/Native Bindings] :to (t/Vec t/Any) :args [(t/Set t/Sym)] :body true} - [bound bindings] - (vec (mapcat (fn [^{::t/- s/Native-Binding} binding] - [(with-meta (:id (:local binding)) (meta (:name (:local binding)))) - (form bound (:init binding))]) bindings))) - - -(np/defpass node-form - {:from [s/Native Node] :to [l/Machine Code] :args [(t/Set t/Sym) s/Native-Expr]} - [bound expr node] - :literal (list 'quote (:value node)) - :global (host/typed-form (:jvm expr) (host/global-form (:name node))) - :var (list 'var (:name node)) - :local (let [local (:local node) id (:id local)] - (if (contains? bound id) - (with-meta id (when-let [hint (:hint (:jvm expr))] {:tag hint})) - (host/typed-form (:jvm expr) (host/box-form id)))) - :host (hinted-form (:hint (:jvm expr)) (host/host-form (mapv #(form bound %) (:args node)) (:operation node))) - :invoke (let [function (:function node) source (:form (:site function)) - ;; Quote changes KeywordExpr into ConstantExpr, bypassing the - ;; source call site's IKeywordLookup cache. Preserve its syntax. - callee (if (and (keyword? source) (= :literal (:op (:node function)))) - source (form bound function))] - (hinted-form (:hint (:jvm expr)) (list* callee (mapv #(form bound %) (:args node))))) - :if (list 'if (form bound (:test node)) (form bound (:then node)) (form bound (:else node))) - :case (cases/arms-form (mapv #(form bound %) (:arms node)) (form bound (:selector node))) - :dispatch (cases/selector-form (form bound (:value node)) (:spec node)) - :throw (list 'throw (form bound (:value node))) - :do (list* 'do (mapv #(form bound %) (:forms node))) - :recur (list* 'recur (mapv #(form bound %) (:args node))) - :let (let [bound (into bound (map #(:id (:local %))) (:bindings node))] - (list 'let* (bindings-form bound (:bindings node)) (form bound (:body node)))) - :loop (let [bound (into bound (map #(:id (:local %))) (:bindings node))] - (list 'loop* (bindings-form bound (:bindings node)) (form bound (:body node)))) - :collection - (let [items (:items node) metadata (:metadata node) values (literal-values items) - keys (when (= :map (:kind node)) (literal-values (vec (take-nth 2 items)))) - unique? (boolean (and keys (= (count keys) (count (set keys))))) - _ (when (and (nil? metadata) keys (not unique?)) - (throw (IllegalArgumentException. "Duplicate constant keys in map"))) - value (if (and (nil? metadata) values) - (list 'quote (collections/constant values (:kind node))) - (collections/form (mapv #(form bound %) items) unique? (:kind node)))] - (if metadata (list '. value 'withMeta (form bound metadata)) value))) - -(np/defpass form {:from [s/Native Expr] :to [l/Machine Code] :args [(t/Set t/Sym)] :body true} - [bound expr] (node-form bound expr (:node expr))) diff --git a/src/com/tailrecursion/blacktide/compiler/order.clj b/src/com/tailrecursion/blacktide/compiler/order.clj deleted file mode 100644 index ea63a7e..0000000 --- a/src/com/tailrecursion/blacktide/compiler/order.clj +++ /dev/null @@ -1,116 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.order - "Make operand evaluation explicit before CFG construction. Calls and collection - construction consume immutable saved locals; source scopes stay unchanged." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as l] - [com.tailrecursion.blacktide.compiler.source :as source] - [typed.clojure :as-alias t])) - -(declare normalize) - -(np/defpass save - {:from [l/Ordered Expr] :to (t/HVec [l/Bound-Supply l/Ordered-Binding l/Ordered-Local]) - :args [l/Bound-Supply] :body true} - [supply expr] - (let [[supply id] (source/fresh "operand" supply) - local {:id id :name id :mutable? false :external? false :jvm (:jvm expr)}] - [supply {:local local :init expr} local])) - -(np/defpass operands - {:from [l/Prepared Exprs] - :to (t/HVec [l/Bound-Supply (t/Vec l/Ordered-Binding) (t/Vec l/Ordered-Local)]) - :args [l/Bound-Supply] :body true} - [supply exprs] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply (t/Vec l/Ordered-Binding) (t/Vec l/Ordered-Local)])} acc - ^{::t/- l/Prepared-Expr} expr] - (let [[supply bindings locals] acc - [supply expr] (normalize supply expr) - [supply binding local] (save supply expr)] - [supply (conj bindings binding) (conj locals local)])) [supply [] []] exprs)) - -(np/defpass normalize-forms - {:from [l/Prepared Exprs] :to (t/HVec [l/Bound-Supply l/Ordered-Exprs]) - :args [l/Bound-Supply] :body true} - [supply exprs] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Ordered-Exprs])} acc ^{::t/- l/Prepared-Expr} expr] - (let [[supply out] acc [supply expr] (normalize supply expr)] [supply (conj out expr)])) - [supply []] exprs)) - -(np/defpass normalize-bindings - {:from [l/Prepared Bindings] :to (t/HVec [l/Bound-Supply l/Ordered-Bindings]) - :args [l/Bound-Supply] :body true} - [supply bindings] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Ordered-Bindings])} acc ^{::t/- l/Prepared-Binding} binding] - (let [[supply out] acc [supply init] (normalize supply (:init binding))] - [supply (conj out (assoc binding :init init))])) [supply []] bindings)) - -(np/defpass normalize-node - {:from [l/Prepared Node] :to (t/HVec [l/Bound-Supply l/Ordered-Expr]) - :args [l/Bound-Supply l/Prepared-Expr]} - [supply expr node] - :literal [supply (assoc expr :node node)] - :local [supply (assoc expr :node node)] - :global [supply (assoc expr :node node)] - :var [supply (assoc expr :node node)] - :label [supply (assoc expr :node node)] - :goto [supply (assoc expr :node node)] - :native [supply (assoc expr :node node)] - :select (let [[supply selector] (normalize supply (:selector node)) - [supply arms] (normalize-forms supply (:arms node))] - [supply (assoc expr :node {:op :select :selector selector :arms arms})]) - :dispatch (let [[supply value] (normalize supply (:value node)) - [supply binding local] (save supply value)] - [supply (assoc expr :node {:op :let :bindings [binding] - :body (assoc expr :node {:op :dispatch :value local :spec (:spec node)})})]) - :recur (let [[supply bindings args] (operands supply (:args node))] - [supply (assoc expr :node {:op :let :bindings bindings - :body (assoc expr :node {:op :recur :target (:target node) :args args})})]) - :loop (let [[supply bindings] (normalize-bindings supply (:bindings node)) - [supply body] (normalize supply (:body node))] - [supply (assoc expr :node {:op :loop :id (:id node) :bindings bindings :body body})]) - :if (let [[supply test] (normalize supply (:test node)) - [supply yes] (normalize supply (:then node)) - [supply no] (normalize supply (:else node))] - [supply (assoc expr :node {:op :if :test test :then yes :else no})]) - :throw (let [[supply value] (normalize supply (:value node))] - [supply (assoc expr :node {:op :throw :value value})]) - :write (let [[supply value] (normalize supply (:value node))] - [supply (assoc expr :node {:op :write :target (:target node) :value value})]) - :do (let [[supply forms] (normalize-forms supply (:forms node))] - [supply (assoc expr :node {:op :do :forms forms})]) - :let (let [[supply bindings] (normalize-bindings supply (:bindings node)) - [supply body] (normalize supply (:body node))] - [supply (assoc expr :node {:op :let :bindings bindings :body body})]) - :convert - (let [[supply value] (normalize supply (:value node)) - [supply binding local] (save supply value)] - [supply (assoc expr :node {:op :let :bindings [binding] - :body (assoc expr :node {:op :convert :conversion (:conversion node) :value local})})]) - :host-call - (let [[supply bindings args] (operands supply (:args node))] - [supply (assoc expr :node {:op :let :bindings bindings - :body (assoc expr :node {:op :host-call :operation (:operation node) :args args})})]) - :invoke - (let [[supply function] (normalize supply (:function node)) - [supply binding local] (save supply function) - [supply bindings args] (operands supply (:args node))] - [supply (assoc expr :node {:op :let :bindings (into [binding] bindings) - :body (assoc expr :node {:op :invoke :function local :args args})})]) - :build - (let [[supply bindings items] (operands supply (:items node)) - value (assoc expr :node {:op :build :kind (:kind node) :items items :unique? (:unique? node)}) - [supply bindings value] - (if-let [metadata (:metadata node)] - (let [[supply binding local] (save supply value) - [supply metadata] (normalize supply metadata) - [supply meta-binding meta-local] (save supply metadata)] - [supply (into bindings [binding meta-binding]) - (assoc expr :node {:op :metadata :value local :metadata meta-local})]) - [supply bindings value])] - [supply (assoc expr :node {:op :let :bindings bindings :body value})])) - -(np/defpass normalize - {:from [l/Prepared Expr] :to (t/HVec [l/Bound-Supply l/Ordered-Expr]) - :args [l/Bound-Supply] :body true} - [supply expr] - (normalize-node supply expr (:node expr))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj new file mode 100644 index 0000000..118510f --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/assignment.clj @@ -0,0 +1,123 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.assignment + "Lower sequential batches to writes, then parallel batches to saved RHSs and + writes. Pending parallel writes do not execute if an RHS abandons evaluation." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.source :as source] + [typed.clojure :as-alias t])) + +(np/defpasses + (sequential-pair + {:from [l/Classified Assignment] :to [l/Sequential Assignment] + :processors {Local clojure.core/identity Expr sequential}} + [pair]) + + (sequential-pairs + {:from [l/Classified Assignments] :to [l/Sequential Assignments] :processors {Assignment sequential-pair}} + [pairs]) + + (sequential-forms + {:from [l/Classified Exprs] :to [l/Sequential Exprs] :processors {Expr sequential}} + [forms]) + + (sequential-node + {:from [l/Classified Node] :to [l/Sequential Node] + :processors {Local clojure.core/identity Raw clojure.core/identity CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity + Expr sequential Exprs sequential-forms Binding sequential-binding Assignments sequential-pairs}} + [node] + :setq {:op :do :forms + (mapv (fn [pair] + (let [value (sequential (:value pair))] + (assoc value :node {:op :write :target (:target pair) :value value}))) (:pairs node))}) + + (sequential-binding + {:from [l/Classified Binding] :to [l/Sequential Binding] + :processors {Local clojure.core/identity Expr sequential}} + [binding]) + + (sequential + {:from [l/Classified Expr] :to [l/Sequential Expr] :body true} + [expr] + (assoc expr :node (sequential-node (:node expr))))) + +(np/defpasses + (parallel-forms + {:from [l/Sequential Exprs] :to (t/HVec [l/Bound-Supply l/Assigned-Exprs]) + :args [l/Bound-Supply] :body true} + [supply forms] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Exprs])} acc ^{::t/- l/Sequential-Expr} expr] + (let [[supply out] acc [supply expr] (parallel supply expr)] [supply (conj out expr)])) + [supply []] forms)) + + (parallel-bindings + {:from [l/Sequential Bindings] :to (t/HVec [l/Bound-Supply l/Assigned-Bindings]) + :args [l/Bound-Supply] :body true} + [supply bindings] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings])} acc ^{::t/- l/Sequential-Binding} binding] + (let [[supply out] acc [supply init] (parallel supply (:init binding))] + [supply (conj out (assoc binding :init init))])) [supply []] bindings)) + + (save-parallel-values + {:from [l/Sequential Assignments] + :to (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs]) :args [l/Bound-Supply] :body true} + [supply pairs] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Assigned-Bindings l/Assigned-Exprs])} acc + ^{::t/- l/Sequential-Assignment} pair] + (let [[supply bindings writes] acc [supply value] (parallel supply (:value pair)) + [supply id] (source/fresh "parallel" supply) + local {:id id :name id :mutable? false :external? false :jvm (:jvm value)} + read (assoc value :node {:op :local :local local}) + write (assoc value :node {:op :write :target (:target pair) :value read})] + [supply (conj bindings {:local local :init value}) (conj writes write)])) [supply [] []] pairs)) + + (parallel-node + {:from [l/Sequential Node] :to (t/HVec [l/Bound-Supply l/Assigned-Node]) :args [l/Bound-Supply]} + [supply node] + :literal [supply node] + :local [supply node] + :global [supply node] + :var [supply node] + :label [supply node] + :goto [supply node] + :native [supply node] + :dispatch (let [[supply value] (parallel supply (:value node))] + [supply {:op :dispatch :value value :spec (:spec node)}]) + :case (let [[supply selector] (parallel supply (:selector node)) + [supply arms] (parallel-forms supply (:arms node))] + [supply {:op :case :selector selector :arms arms}]) + :recur (let [[supply args] (parallel-forms supply (:args node))] + [supply {:op :recur :target (:target node) :args args}]) + :loop (let [[supply bindings] (parallel-bindings supply (:bindings node)) + [supply body] (parallel supply (:body node))] + [supply {:op :loop :id (:id node) :bindings bindings :body body}]) + :if (let [[supply test] (parallel supply (:test node)) + [supply yes] (parallel supply (:then node)) + [supply no] (parallel supply (:else node))] + [supply {:op :if :test test :then yes :else no}]) + :throw (let [[supply value] (parallel supply (:value node))] [supply {:op :throw :value value}]) + :write (let [[supply value] (parallel supply (:value node))] + [supply {:op :write :target (:target node) :value value}]) + :do (let [[supply forms] (parallel-forms supply (:forms node))] [supply {:op :do :forms forms}]) + :host (let [[supply args] (parallel-forms supply (:args node))] + [supply {:op :host :operation (:operation node) :args args}]) + :invoke (let [[supply function] (parallel supply (:function node)) + [supply args] (parallel-forms supply (:args node))] + [supply {:op :invoke :function function :args args}]) + :collection (let [[supply items] (parallel-forms supply (:items node)) + [supply metadata] (if-let [metadata (:metadata node)] (parallel supply metadata) [supply nil])] + [supply {:op :collection :kind (:kind node) :items items :metadata metadata}]) + :let (let [[supply bindings] (parallel-bindings supply (:bindings node)) + [supply body] (parallel supply (:body node))] + [supply {:op :let :bindings bindings :body body}]) + :psetq (let [[supply bindings writes] (save-parallel-values supply (:pairs node)) + template (first writes)] + (if template + [supply {:op :let :bindings bindings + :body (assoc template :node {:op :do :forms (conj writes (assoc template :node {:op :literal :value nil}))})}] + [supply {:op :literal :value nil}]))) + + (parallel + {:from [l/Sequential Expr] :to (t/HVec [l/Bound-Supply l/Assigned-Expr]) + :args [l/Bound-Supply] :body true} + [supply expr] + (let [[supply node] (parallel-node supply (:node expr))] [supply (assoc expr :node node)]))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj b/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj new file mode 100644 index 0000000..ef7596d --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/boundaries.clj @@ -0,0 +1,76 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.boundaries + "Classify analyzed native bodies before expression lowering. Native subtrees + exclude DSL effects; lowered loops materialize their source representations." + (:require [com.tailrecursion.blacktide.compiler.passes.effects :refer [effects]] + [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.loop-facts :as loops] + [typed.clojure :as-alias t])) + +(np/defpasses + (native-node + {:from [l/Bound Node] :to [l/Native Node] + :processors {Expr native Exprs native-forms Binding native-binding}} + [node] + :literal node :local node :global node :var node + :case {:op :case :selector (native (:selector node)) + :arms (native-forms (:arms node))} + :dispatch {:op :dispatch :value (native (:value node)) :spec (:spec node)} + :collection {:op :collection :kind (:kind node) :items (native-forms (:items node)) + :metadata (when-let [metadata (:metadata node)] (native metadata))} + :recur {:op :recur :target (:target node) :args (mapv native (:args node))} + :loop {:op :loop :id (:id node) :bindings (mapv native-binding (:bindings node)) + :body (native (:body node))} + :setq (if (empty? (:pairs node)) {:op :literal :value nil} (throw (IllegalStateException. "Native local write"))) + :psetq (if (empty? (:pairs node)) {:op :literal :value nil} (throw (IllegalStateException. "Native parallel write"))) + :label (throw (IllegalStateException. "Native label")) + :goto (throw (IllegalStateException. "Native transfer"))) + + (native-binding {:from [l/Bound Binding] :to [l/Native Binding] :body true} + [binding] (assoc binding :init (native (:init binding)))) + + (native-forms {:from [l/Bound Exprs] :to [l/Native Exprs] :processors {Expr native}} + [forms]) + + (native {:from [l/Bound Expr] :to [l/Native Expr] :body true} + [expr] + (assoc expr :node (native-node (:node expr))))) + +(np/defpasses + (classify-node + {:from [l/Bound Node] :to [l/Classified Node] + :processors {Expr classify Exprs classify-forms Binding classify-binding Assignments classify-pairs + Local clojure.core/identity RecurTarget clojure.core/identity + Raw clojure.core/identity CollectionKind clojure.core/identity CaseSpec clojure.core/identity}} + [node] + :loop {:op :loop :id (:id node) :bindings (mapv classify-binding (:bindings node)) :body (classify (:body node))}) + + (classify-pair {:from [l/Bound Assignment] :to [l/Classified Assignment] + :processors {Expr classify Local clojure.core/identity}} + [pair]) + + (classify-pairs {:from [l/Bound Assignments] :to [l/Classified Assignments] :processors {Assignment classify-pair}} + [pairs]) + + (classify-binding {:from [l/Bound Binding] :to [l/Classified Binding] + :processors {Expr classify Local clojure.core/identity}} + [binding]) + + (classify-forms {:from [l/Bound Exprs] :to [l/Classified Exprs] :processors {Expr classify}} + [forms]) + + (classify {:from [l/Bound Expr] :to [l/Classified Expr] :body true} + [expr] + (let [node (:node expr)] + (cond + ;; KeywordInvokeExpr snapshots its cache before evaluating the target. + ;; Retain the whole call when the target needs no DSL control or writes; + ;; saving the target first would change reentrant lookup behavior. + (and (= :invoke (:op node)) (= 1 (count (:args node))) + (= :literal (:op (:node (:function node)))) + (keyword? (:form (:site (:function node)))) (not (effects expr))) + (assoc expr :node {:op :native :value (native expr)}) + (and (= :loop (:op node)) (not (effects expr))) + (assoc expr :node {:op :native :value (native expr)}) + (= :loop (:op node)) (let [expr (loops/lower expr)] (assoc expr :node (classify-node (:node expr)))) + :else (assoc expr :node (classify-node node)))))) diff --git a/src/com/tailrecursion/blacktide/compiler/box_origins.clj b/src/com/tailrecursion/blacktide/compiler/passes/box_origins.clj similarity index 86% rename from src/com/tailrecursion/blacktide/compiler/box_origins.clj rename to src/com/tailrecursion/blacktide/compiler/passes/box_origins.clj index 3c78396..3d8cbbf 100644 --- a/src/com/tailrecursion/blacktide/compiler/box_origins.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/box_origins.clj @@ -1,12 +1,11 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.box-origins +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.box-origins "Find values that can be reboxed at return, following copies. Primitive computations create boxes; compiled small Long literals use the guaranteed cache. Storage analysis must still exclude all other identity observations." (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] - [com.tailrecursion.blacktide.compiler.value-facts :as facts] + [com.tailrecursion.blacktide.compiler.passes.value-facts :as facts] [typed.clojure :as-alias t])) (np/defpass copy-source {:from [l/Machine Rhs] :to (t/Option t/Sym) :body true} @@ -22,7 +21,7 @@ (:host :convert) (class? (facts/rhs-type {} rhs)) false)) -(np/defpass reboxable-values {:from [a/Analysis Program] :to (t/Set t/Sym) :body true} +(np/defpass reboxable-values {:from [l/Analysis Program] :to (t/Set t/Sym) :body true} [analysis] (let [definitions (facts/definitions (:reachable analysis) (:machine analysis)) initial (reduce-kv diff --git a/src/com/tailrecursion/blacktide/compiler/case_emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/case_emit.clj similarity index 85% rename from src/com/tailrecursion/blacktide/compiler/case_emit.clj rename to src/com/tailrecursion/blacktide/compiler/passes/case_emit.clj index bf670f6..449cfbc 100644 --- a/src/com/tailrecursion/blacktide/compiler/case_emit.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/case_emit.clj @@ -1,13 +1,12 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.case-emit +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.case-emit "Emit native case* dispatch from constant table data. Result arms are separate from constant matching; selection never substitutes ordinary equality for case." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) (np/defpass selector-form - {:from [s/Bound CaseSpec] :to [l/Machine Code] :args [l/Machine-Code] :body true} + {:from [l/Bound CaseSpec] :to [l/Machine Code] :args [l/Machine-Code] :body true} [value spec] (let [local (gensym "dispatch__")] (list 'let* [local value] diff --git a/src/com/tailrecursion/blacktide/compiler/passes/cases.clj b/src/com/tailrecursion/blacktide/compiler/passes/cases.clj new file mode 100644 index 0000000..2718523 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/cases.clj @@ -0,0 +1,25 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.cases + "Separate source case from ordinal arm selection after native constant dispatch. + Each source arm occurs once, including grouped and hash-collision alternatives." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l])) + +(np/defpasses + (normalize-node + {:from [l/Assigned Node] :to [l/Cases Node] + :processors {Expr normalize Exprs normalize-forms Binding normalize-binding + Raw clojure.core/identity Local clojure.core/identity CollectionKind clojure.core/identity + RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} + [node] + :case + {:op :select :selector (normalize (:selector node)) :arms (normalize-forms (:arms node))}) + + (normalize-binding {:from [l/Assigned Binding] :to [l/Cases Binding] + :processors {Expr normalize Local clojure.core/identity}} + [binding]) + + (normalize-forms {:from [l/Assigned Exprs] :to [l/Cases Exprs] :processors {Expr normalize}} + [forms]) + + (normalize {:from [l/Assigned Expr] :to [l/Cases Expr] :body true} + [expr] (assoc expr :node (normalize-node (:node expr))))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/collections.clj b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj new file mode 100644 index 0000000..04fbc12 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/collections.clj @@ -0,0 +1,69 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.collections + "Preserve native constant collections and construction checks. Metadata keeps + construction dynamic and is evaluated only after that construction succeeds." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.host-emit :as host] + [typed.clojure :as-alias t])) + +(np/defpass constant + {:from [l/Collections CollectionKind] :to t/Any :args [(t/Vec t/Any)] :body true} + [values kind] + (case kind + :vector (vec values) + :map (reduce (fn [^{::t/- (t/Map t/Any t/Any)} out ^{::t/- (t/Seq t/Any)} pair] + (assoc out (first pair) (second pair))) {} (partition 2 values)) + :set (set values))) + +(np/defpass form + {:from [l/Collections CollectionKind] :to [l/Machine Code] + :args [(t/Vec l/Machine-Code) t/Bool] :body true} + [items unique? kind] + (if (= :vector kind) + (if (<= (count items) 6) + (list* 'clojure.lang.Tuple/create items) + ;; An Object expression prevents constant folding without unboxing the + ;; first literal, which would lose its original numeric width or identity. + (list 'do (assoc items 0 (host/box-form (nth items 0))))) + (list (if (= :set kind) 'clojure.lang.RT/set + (if unique? 'clojure.lang.RT/mapUniqueKeys 'clojure.lang.RT/map)) + (list 'clojure.lang.RT/object_array items)))) + +(np/defpass literal-values + {:from [l/Collections Exprs] :to (t/Option (t/Vec t/Any)) :body true} + [items] + (reduce (fn [^{::t/- (t/Option (t/Vec t/Any))} values ^{::t/- l/Collections-Expr} expr] + (let [node (:node expr)] + (if (and values (= :literal (:op node))) (conj values (:value node)) (reduced nil)))) + [] items)) + +(np/defpasses + (normalize-forms + {:from [l/Cases Exprs] :to [l/Collections Exprs] :processors {Expr normalize}} + [forms]) + + (normalize-node + {:from [l/Cases Node] :to [l/Collections Node] + :processors {Expr normalize Exprs normalize-forms Binding normalize-binding Raw clojure.core/identity + Local clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} + [node] + :collection + (let [items (mapv normalize (:items node)) metadata (some-> (:metadata node) normalize) + values (literal-values items) + keys (when (= :map (:kind node)) (literal-values (vec (take-nth 2 items)))) + unique? (boolean (and keys (= (count keys) (count (set keys)))))] + (when (and (nil? metadata) keys (not unique?)) + (throw (IllegalArgumentException. "Duplicate constant keys in map"))) + (if (and (nil? metadata) values) + {:op :literal :value (constant values (:kind node))} + {:op :build :kind (:kind node) :items items :metadata metadata :unique? unique?}))) + + (normalize-binding + {:from [l/Cases Binding] :to [l/Collections Binding] + :processors {Expr normalize Local clojure.core/identity}} + [binding]) + + (normalize + {:from [l/Cases Expr] :to [l/Collections Expr] :body true} + [expr] + (assoc expr :node (normalize-node (:node expr))))) diff --git a/src/com/tailrecursion/blacktide/compiler/compact.clj b/src/com/tailrecursion/blacktide/compiler/passes/compact.clj similarity index 75% rename from src/com/tailrecursion/blacktide/compiler/compact.clj rename to src/com/tailrecursion/blacktide/compiler/passes/compact.clj index ea027c1..372317c 100644 --- a/src/com/tailrecursion/blacktide/compiler/compact.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/compact.clj @@ -1,16 +1,15 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.compact +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.compact "Discard unused storage without discarding evaluation. Substitute compatible copies and single-use, total primitive computations only before their inputs are rebound. Native bodies remain opaque, including their free local reads." (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.structured-languages :as s] - [com.tailrecursion.blacktide.compiler.compact-languages :as c] - [com.tailrecursion.blacktide.compiler.native :as native] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.native :as native] [com.tailrecursion.blacktide.compiler.jvm :as jvm] [typed.clojure :as-alias t])) -(np/defpass introduce {:from [s/Stored Expr] :to [c/Compact Expr]} +(np/defpass introduce {:from [l/Stored Expr] :to [l/Compact Expr]} [node] :bind (assoc node :body (introduce (:body node))) :join (assoc node :value (introduce (:value node)) :body (introduce (:body node))) @@ -21,38 +20,38 @@ :recur {:op :recur :args (mapv (fn [id] {:op :local :id id}) (:slots node))} :return node :throw node) -(declare rhs-reads) -(np/defpass atom-reads {:from [c/Compact Atom] :to (t/Vec t/Sym)} - [node] - :local [(:id node)] :literal [] :inline (rhs-reads (:value node))) - -(np/defpass rhs-reads {:from [c/Compact Rhs] :to (t/Vec t/Sym)} - [node] - :value (atom-reads (:value node)) - :global [] :external [] :var [] - :native (vec (native/reads (:value node))) - :dispatch (atom-reads (:value (:value node))) - :convert (atom-reads (:value (:value node))) - :construct (into [] (mapcat atom-reads) (:items node)) - :metadata (into (atom-reads (:value node)) (atom-reads (:metadata node))) - :host (into [] (mapcat #(atom-reads (:value %))) (:args node)) - :invoke (into (atom-reads (:function node)) (mapcat atom-reads) (:args node))) - -(np/defpass reads {:from [c/Compact Expr] :to (t/Vec t/Sym)} +(np/defpasses + (atom-reads {:from [l/Compact Atom] :to (t/Vec t/Sym)} + [node] + :local [(:id node)] :literal [] :inline (rhs-reads (:value node))) + + (rhs-reads {:from [l/Compact Rhs] :to (t/Vec t/Sym)} + [node] + :value (atom-reads (:value node)) + :global [] :external [] :var [] + :native (vec (native/reads (:value node))) + :dispatch (atom-reads (:value (:value node))) + :convert (atom-reads (:value (:value node))) + :construct (into [] (mapcat atom-reads) (:items node)) + :metadata (into (atom-reads (:value node)) (atom-reads (:metadata node))) + :host (into [] (mapcat #(atom-reads (:value %))) (:args node)) + :invoke (into (atom-reads (:function node)) (mapcat atom-reads) (:args node)))) + +(np/defpass reads {:from [l/Compact Expr] :to (t/Vec t/Sym)} [node] :bind (into (rhs-reads (:value (:binding node))) (filterv #(not= % (:target (:binding node))) (reads (:body node)))) :effect (into (rhs-reads (:value (:binding node))) (reads (:body node))) :join (into (reads (:value node)) (filterv #(not= % (:target node)) (reads (:body node)))) :branch (into (into (atom-reads (:test node)) (reads (:yes node))) (reads (:no node))) :switch (into (atom-reads (:test node)) (mapcat reads) (:arms node)) - :loop (reduce (fn [^{::t/- (t/Vec t/Sym)} refs ^{::t/- c/Compact-Binding} binding] + :loop (reduce (fn [^{::t/- (t/Vec t/Sym)} refs ^{::t/- l/Compact-Binding} binding] (into (atom-reads (:value binding)) (filterv #(not= % (:target binding)) refs))) (reads (:body node)) (rseq (:bindings node))) :recur (into [] (mapcat atom-reads) (:args node)) :return (atom-reads (:value node)) :throw (atom-reads (:value node))) (np/defpass stable? - {:from [c/Compact Expr] :to t/Bool :args [t/Sym (t/Set t/Sym)]} + {:from [l/Compact Expr] :to t/Bool :args [t/Sym (t/Set t/Sym)]} [id dependencies node] :bind (if (contains? dependencies (:target (:binding node))) (not (some #{id} (reads (:body node)))) (stable? id dependencies (:body node))) @@ -68,64 +67,22 @@ (stable? id dependencies (:body node))) :recur true :return true :throw true) -(declare substitute-rhs) -(np/defpass substitute-atom - {:from [c/Compact Atom] :to [c/Compact Atom] :args [t/Sym c/Compact-Atom] - :processors {Rhs substitute-rhs}} - [from replacement node] - :local (if (= from (:id node)) replacement node)) - -(np/defpass substitute-operand - {:from [c/Compact Operand] :to [c/Compact Operand] :args [t/Sym c/Compact-Atom] - :processors {Atom substitute-atom}} [from replacement node]) - -(np/defpass substitute-rhs - {:from [c/Compact Rhs] :to [c/Compact Rhs] :args [t/Sym c/Compact-Atom] - :processors {Atom substitute-atom Operand substitute-operand}} [from replacement node]) - -(np/defpass substitute-operation - {:from [c/Compact Operation] :to [c/Compact Operation] :args [t/Sym c/Compact-Atom] - :processors {Rhs substitute-rhs}} [from replacement node]) - -(np/defpass substitute-binding - {:from [c/Compact Binding] :to [c/Compact Binding] :args [t/Sym c/Compact-Atom] - :processors {Atom substitute-atom}} [from replacement node]) - -(np/defpass substitute - {:from [c/Compact Expr] :to [c/Compact Expr] :args [t/Sym c/Compact-Atom] - :processors {Atom substitute-atom Rhs substitute-rhs Operation substitute-operation Binding substitute-binding}} - [from replacement node] - :bind (assoc node :binding (substitute-operation from replacement (:binding node)) - :body (if (= from (:target (:binding node))) (:body node) - (substitute from replacement (:body node)))) - :join (assoc node :value (substitute from replacement (:value node)) - :body (if (= from (:target node)) (:body node) - (substitute from replacement (:body node)))) - ;; Loop initializers see earlier bindings, but the body sees all loop binders. - :loop ((fn ^{::t/- [t/Int (t/Vec c/Compact-Binding) t/Bool :-> c/Compact-Expr]} bindings [index pairs shadowed?] - (if (< index (count (:bindings node))) - (let [binding (nth (:bindings node) index)] - (recur (inc index) (conj pairs (if shadowed? binding (substitute-binding from replacement binding))) - (or shadowed? (= from (:target binding))))) - (assoc node :bindings pairs :body (if shadowed? (:body node) - (substitute from replacement (:body node)))))) 0 [] false)) - (np/defpass atom-type - {:from [c/Compact Atom] :to (t/Option java.lang.Class) :args [c/Compact-Types]} + {:from [l/Compact Atom] :to (t/Option java.lang.Class) :args [l/Compact-Types]} [types node] :local (get types (:id node)) :literal (cond (instance? Long (:value node)) Long/TYPE (instance? Double (:value node)) Double/TYPE :else nil) :inline (:type node)) (np/defpass movable? - {:from [c/Compact Rhs] :to t/Bool :args [c/Compact-Types] :body true} + {:from [l/Compact Rhs] :to t/Bool :args [l/Compact-Types] :body true} [types rhs] (and (= :host (:op rhs)) (let [operation (:operation rhs)] (and (= :static (:op operation)) (= clojure.lang.Numbers (:class operation)) (contains? '#{lt lte gt gte equiv unchecked_add unchecked_minus unchecked_multiply unchecked_inc unchecked_dec unchecked_negate} (:member operation)) - (every? (fn [^{::t/- c/Compact-Operand} operand] + (every? (fn [^{::t/- l/Compact-Operand} operand] (let [type (atom-type types (:value operand))] (and (contains? #{Long/TYPE Double/TYPE} type) (= type (:type (:jvm operand)))))) (:args rhs)) @@ -133,7 +90,7 @@ (and (class? type) (.isPrimitive ^Class type) (not= Void/TYPE type))))))) (np/defpass replacement - {:from [c/Compact Operation] :to (t/Option c/Compact-Atom) :args [c/Compact-Types t/Int] :body true} + {:from [l/Compact Operation] :to (t/Option l/Compact-Atom) :args [l/Compact-Types t/Int] :body true} [types uses binding] (let [rhs (:value binding) ^Class type (get types (:target binding))] (case (:op rhs) @@ -160,14 +117,56 @@ (when (and type (= uses 1) (movable? types rhs)) {:op :inline :value rhs :type type}))) nil))) -(np/defpass discardable? {:from [c/Compact Rhs] :to t/Bool :body true} +(np/defpasses + (substitute-atom + {:from [l/Compact Atom] :to [l/Compact Atom] :args [t/Sym l/Compact-Atom] + :processors {Rhs substitute-rhs}} + [from replacement node] + :local (if (= from (:id node)) replacement node)) + + (substitute-operand + {:from [l/Compact Operand] :to [l/Compact Operand] :args [t/Sym l/Compact-Atom] + :processors {Atom substitute-atom}} [from replacement node]) + + (substitute-rhs + {:from [l/Compact Rhs] :to [l/Compact Rhs] :args [t/Sym l/Compact-Atom] + :processors {Atom substitute-atom Operand substitute-operand}} [from replacement node])) + +(np/defpass substitute-operation + {:from [l/Compact Operation] :to [l/Compact Operation] :args [t/Sym l/Compact-Atom] + :processors {Rhs substitute-rhs}} [from replacement node]) + +(np/defpass substitute-binding + {:from [l/Compact Binding] :to [l/Compact Binding] :args [t/Sym l/Compact-Atom] + :processors {Atom substitute-atom}} [from replacement node]) + +(np/defpass substitute + {:from [l/Compact Expr] :to [l/Compact Expr] :args [t/Sym l/Compact-Atom] + :processors {Atom substitute-atom Rhs substitute-rhs Operation substitute-operation Binding substitute-binding}} + [from replacement node] + :bind (assoc node :binding (substitute-operation from replacement (:binding node)) + :body (if (= from (:target (:binding node))) (:body node) + (substitute from replacement (:body node)))) + :join (assoc node :value (substitute from replacement (:value node)) + :body (if (= from (:target node)) (:body node) + (substitute from replacement (:body node)))) + ;; Loop initializers see earlier bindings, but the body sees all loop binders. + :loop ((fn ^{::t/- [t/Int (t/Vec l/Compact-Binding) t/Bool :-> l/Compact-Expr]} bindings [index pairs shadowed?] + (if (< index (count (:bindings node))) + (let [binding (nth (:bindings node) index)] + (recur (inc index) (conj pairs (if shadowed? binding (substitute-binding from replacement binding))) + (or shadowed? (= from (:target binding))))) + (assoc node :bindings pairs :body (if shadowed? (:body node) + (substitute from replacement (:body node)))))) 0 [] false)) + +(np/defpass discardable? {:from [l/Compact Rhs] :to t/Bool :body true} [rhs] ;; Keep native validation, including constants that may fail AOT embedding. (and (= :value (:op rhs)) (let [value (:value rhs)] (or (= :local (:op value)) (and (= :literal (:op value)) (nil? (:value value))))))) -(np/defpass simplify {:from [c/Compact Expr] :to [c/Compact Expr] :args [c/Compact-Types]} +(np/defpass simplify {:from [l/Compact Expr] :to [l/Compact Expr] :args [l/Compact-Types]} [types node] :bind (let [binding (:binding node) id (:target binding) body (simplify types (:body node)) uses (count (filter #{id} (reads body))) @@ -192,6 +191,6 @@ :loop (assoc node :body (simplify types (:body node))) :recur node :return node :throw node) -(np/defpass program {:from [s/Stored Program] :to [c/Compact Program] :body true} +(np/defpass program {:from [l/Stored Program] :to [l/Compact Program] :body true} [program] {:types (:types program) :body (simplify (:types program) (introduce (:body program)))}) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/effects.clj b/src/com/tailrecursion/blacktide/compiler/passes/effects.clj new file mode 100644 index 0000000..9c6e9ea --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/effects.clj @@ -0,0 +1,27 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.effects + "Identify local writes and transfers that prevent native subtree emission." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [typed.clojure :as-alias t])) + +(np/defpasses + (effect-node {:from [l/Bound Node] :to t/Bool} + [node] + :literal false :local false :global false :var false + :setq (boolean (seq (:pairs node))) + :psetq (boolean (seq (:pairs node))) + :label true :goto true + :if (or (effects (:test node)) (effects (:then node)) (effects (:else node))) + :case (or (effects (:selector node)) (boolean (some effects (:arms node)))) + :dispatch (effects (:value node)) + :throw (effects (:value node)) + :host (boolean (some effects (:args node))) + :invoke (or (effects (:function node)) (boolean (some effects (:args node)))) + :collection (or (boolean (some effects (:items node))) (boolean (some-> (:metadata node) effects))) + :do (boolean (some effects (:forms node))) + :let (or (boolean (some #(effects (:init %)) (:bindings node))) (effects (:body node))) + :loop (or (boolean (some #(effects (:init %)) (:bindings node))) (effects (:body node))) + :recur (boolean (some effects (:args node)))) + + (effects {:from [l/Bound Expr] :to t/Bool :body true} + [expr] (effect-node (:node expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/emit.clj similarity index 90% rename from src/com/tailrecursion/blacktide/compiler/emit.clj rename to src/com/tailrecursion/blacktide/compiler/passes/emit.clj index 7492023..138f12b 100644 --- a/src/com/tailrecursion/blacktide/compiler/emit.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/emit.clj @@ -1,13 +1,13 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.emit +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.emit "Emit allocated machines as ordinary Clojure forms. Source effects occur in sequential lets; edges carry live values through boxed loop bindings." (:require [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.native :as native] - [com.tailrecursion.blacktide.compiler.collections :as collections] - [com.tailrecursion.blacktide.compiler.case-emit :as cases] + [com.tailrecursion.blacktide.compiler.passes.native :as native] + [com.tailrecursion.blacktide.compiler.passes.collections :as collections] + [com.tailrecursion.blacktide.compiler.passes.case-emit :as cases] [typed.clojure :as-alias t] - [com.tailrecursion.blacktide.compiler.host-emit :as host])) + [com.tailrecursion.blacktide.compiler.passes.host-emit :as host])) (np/defpass atom-form {:from [l/Machine Atom] :to [l/Machine Code]} [node] diff --git a/src/com/tailrecursion/blacktide/compiler/feasible.clj b/src/com/tailrecursion/blacktide/compiler/passes/feasible.clj similarity index 92% rename from src/com/tailrecursion/blacktide/compiler/feasible.clj rename to src/com/tailrecursion/blacktide/compiler/passes/feasible.clj index b2a138d..c5e0999 100644 --- a/src/com/tailrecursion/blacktide/compiler/feasible.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/feasible.clj @@ -1,4 +1,4 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.feasible +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.feasible "Fold literal truth tests after source validation. Keep every block so dead source still receives JVM validation." (:require [com.tailrecursion.nanopass :as np] diff --git a/src/com/tailrecursion/blacktide/compiler/graph.clj b/src/com/tailrecursion/blacktide/compiler/passes/graph.clj similarity index 97% rename from src/com/tailrecursion/blacktide/compiler/graph.clj rename to src/com/tailrecursion/blacktide/compiler/passes/graph.clj index ace2765..ba56c9b 100644 --- a/src/com/tailrecursion/blacktide/compiler/graph.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/graph.clj @@ -1,10 +1,10 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.graph +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.graph "Check initialization on reachable paths; retain all blocks for native Clojure validation. Liveness carries values through edges without evaluating dead code." (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.native :as native] + [com.tailrecursion.blacktide.compiler.passes.native :as native] [typed.clojure :as-alias t])) (np/defpass atom-reads {:from [l/CFG Atom] :to (t/Set t/Sym)} diff --git a/src/com/tailrecursion/blacktide/compiler/host_emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/host_emit.clj similarity index 93% rename from src/com/tailrecursion/blacktide/compiler/host_emit.clj rename to src/com/tailrecursion/blacktide/compiler/passes/host_emit.clj index ae9cd66..178c380 100644 --- a/src/com/tailrecursion/blacktide/compiler/host_emit.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/host_emit.clj @@ -1,8 +1,7 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.host-emit +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.host-emit "Emit host descriptors with source-typed saved operands. Known nil is retained explicitly; Object prevents boxed storage from inventing a more specific type." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.runtime] [typed.clojure :as-alias t])) @@ -21,7 +20,7 @@ (list (with-meta 'clojure.lang.RT/box {:param-tags ['java.lang.Object]}) form)) (np/defpass typed-form - {:from [l/Machine Code] :to [l/Machine Code] :args [j/JVM-Fact] :body true} + {:from [l/Machine Code] :to [l/Machine Code] :args [l/JVM-Fact] :body true} [fact form] (let [^Class type (:type fact) object-form (with-meta (if (symbol? form) form (apply list form)) {:tag 'java.lang.Object})] @@ -33,7 +32,7 @@ {:tag (if (class? type) (symbol (.getName type)) 'java.lang.Object)})))) (np/defpass conversion-form - {:from [l/Machine Code] :to [l/Machine Code] :args [j/JVM-Conversion j/JVM-Fact] :body true} + {:from [l/Machine Code] :to [l/Machine Code] :args [l/JVM-Conversion l/JVM-Fact] :body true} [conversion fact form] (let [^Class type (:type conversion)] (cond @@ -50,7 +49,7 @@ (np/defpass host-form - {:from [j/JVM Operation] :to [l/Machine Code] :args [(t/Vec l/Machine-Code)]} + {:from [l/JVM Operation] :to [l/Machine Code] :args [(t/Vec l/Machine-Code)]} [args operation] :keyword-site (list 'com.tailrecursion.blacktide.runtime/keyword-site (:key operation)) :protocol diff --git a/src/com/tailrecursion/blacktide/compiler/invariants.clj b/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj similarity index 83% rename from src/com/tailrecursion/blacktide/compiler/invariants.clj rename to src/com/tailrecursion/blacktide/compiler/passes/invariants.clj index f6092ae..43d29ff 100644 --- a/src/com/tailrecursion/blacktide/compiler/invariants.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/invariants.clj @@ -1,12 +1,12 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.invariants +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.invariants "Remove loop parameters that only forward an outer lexical value. Their initializers remain in place, and recur keeps exactly the retained parameters." (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.structured-languages :as s] + [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) -(np/defpass assigned {:from [s/Joined Expr] :to (t/Set t/Sym)} +(np/defpass assigned {:from [l/Joined Expr] :to (t/Set t/Sym)} [node] :bind (conj (assigned (:body node)) (:target (:binding node))) :join (conj (set/union (assigned (:value node)) (assigned (:body node))) (:target node)) @@ -15,7 +15,7 @@ :loop (into (assigned (:body node)) (:slots node)) :recur #{} :return #{} :throw #{}) -(np/defpass expression {:from [s/Joined Expr] :to [s/Joined Expr] :args [(t/Set t/Sym)]} +(np/defpass expression {:from [l/Joined Expr] :to [l/Joined Expr] :args [(t/Set t/Sym)]} [invariants node] :bind (assoc node :body (expression invariants (:body node))) :join (assoc node :value (expression invariants (:value node)) :body (expression invariants (:body node))) diff --git a/src/com/tailrecursion/blacktide/compiler/joins.clj b/src/com/tailrecursion/blacktide/compiler/passes/joins.clj similarity index 80% rename from src/com/tailrecursion/blacktide/compiler/joins.clj rename to src/com/tailrecursion/blacktide/compiler/passes/joins.clj index 141cc29..fd70633 100644 --- a/src/com/tailrecursion/blacktide/compiler/joins.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/joins.clj @@ -1,20 +1,19 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.joins +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.joins "Share acyclic branch continuations with at most one escaping definition. Every incoming path must define the result. Postdominators nominate exits; region proofs reject cycles and incomplete paths before recovery." (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] - [com.tailrecursion.blacktide.compiler.graph :as graph] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] [typed.clojure :as-alias t])) -(np/defpass postdominators {:from [l/Machine Program] :to [a/Planned Postdominators] :body true} +(np/defpass postdominators {:from [l/Machine Program] :to [l/Planned Postdominators] :body true} [machine] (let [labels (set (keys (:blocks machine)))] - ((fn ^{::t/- [a/Planned-Postdominators :-> a/Planned-Postdominators]} solve [known] + ((fn ^{::t/- [l/Planned-Postdominators :-> l/Planned-Postdominators]} solve [known] (let [next (reduce-kv - (fn [^{::t/- a/Planned-Postdominators} out ^{::t/- l/Machine-Label} id + (fn [^{::t/- l/Planned-Postdominators} out ^{::t/- l/Machine-Label} id ^{::t/- l/Machine-Block} block] (let [successors (graph/successors (:term block)) common (if (seq successors) @@ -23,14 +22,14 @@ (if (= known next) known (recur next)))) (zipmap labels (repeat labels))))) (np/defpass region - {:from [l/Machine Label] :to (t/Option a/Planned-Region) + {:from [l/Machine Label] :to (t/Option l/Planned-Region) :args [l/Machine-Program l/Machine-Label] :body true} [machine stop id] ;; Prove blocks only after every successor is proved. Each block contributes ;; once; shared paths do not exhaust a traversal budget. Cycles never qualify. - ((fn ^{::t/- [a/Planned-Regions :-> (t/Option a/Planned-Region)]} solve [known] + ((fn ^{::t/- [l/Planned-Regions :-> (t/Option l/Planned-Region)]} solve [known] (let [next (reduce-kv - (fn [^{::t/- a/Planned-Regions} out ^{::t/- l/Machine-Label} label + (fn [^{::t/- l/Planned-Regions} out ^{::t/- l/Machine-Label} label ^{::t/- l/Machine-Block} block] (let [successors (graph/successors (:term block))] (if (and (not (contains? out label)) (seq successors) @@ -44,8 +43,8 @@ (when (not= known next) (recur next))))) {stop {:definite #{} :changed #{}}})) (np/defpass candidate - {:from [l/Machine Label] :to (t/Option a/Planned-Join) - :args [l/Machine-Program a/Planned-Postdominators] :body true} + {:from [l/Machine Label] :to (t/Option l/Planned-Join) + :args [l/Machine-Program l/Planned-Postdominators] :body true} [machine known id] (when-let [block (get (:blocks machine) id)] (let [term (:term block) @@ -62,10 +61,10 @@ (when (and (<= (count outputs) 1) (symbol? result) (contains? (:definite proof) result)) {:target target :result result}))))))))) -(np/defpass plan {:from [a/Analysis Program] :to [a/Planned Program] :body true} +(np/defpass plan {:from [l/Analysis Program] :to [l/Planned Program] :body true} [analysis] (let [machine (:machine analysis) known (postdominators machine)] (assoc analysis :joins - (reduce (fn [^{::t/- a/Planned-Joins} out ^{::t/- l/Machine-Label} id] + (reduce (fn [^{::t/- l/Planned-Joins} out ^{::t/- l/Machine-Label} id] (if-let [join (candidate machine known id)] (assoc out id join) out)) {} (keys (:blocks machine)))))) diff --git a/src/com/tailrecursion/blacktide/compiler/loop_facts.clj b/src/com/tailrecursion/blacktide/compiler/passes/loop_facts.clj similarity index 51% rename from src/com/tailrecursion/blacktide/compiler/loop_facts.clj rename to src/com/tailrecursion/blacktide/compiler/passes/loop_facts.clj index 74c805f..567a5ea 100644 --- a/src/com/tailrecursion/blacktide/compiler/loop_facts.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/loop_facts.clj @@ -1,16 +1,15 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.loop-facts +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.loop-facts "Recover source loop representations using Clojure's monotone boxing analysis. Reanalyze expanded bindings in order without repeating user macros. Native syntax retains its initializers; lowered loops materialize the conversions." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] + [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.jvm :as jvm] [com.tailrecursion.blacktide.compiler.errors :as errors] - [com.tailrecursion.blacktide.compiler.source-jvm :as facts] + [com.tailrecursion.blacktide.compiler.passes.source-jvm :as facts] [typed.clojure :as-alias t])) -(np/defpass children {:from [s/Bound Expr] :to [s/Bound Exprs] :body true} +(np/defpass children {:from [l/Bound Expr] :to [l/Bound Exprs] :body true} [expr] (let [node (:node expr)] (case (:op node) @@ -25,119 +24,119 @@ (:setq :psetq) (mapv :value (:pairs node)) []))) - -(np/defpass primitive {:from [j/JVM Fact] :to (t/Option java.lang.Class) :body true} +(np/defpass primitive {:from [l/JVM Fact] :to (t/Option java.lang.Class) :body true} [fact] (let [type (:type fact)] (when (and (:primitive? fact) (class? type) (.isPrimitive ^Class type) (not= Void/TYPE type)) type))) -(np/defpass inferred {:from [s/Bound Expr] :to [s/Bound Expr] :args [s/Bound-Node] :body true} +(np/defpass inferred {:from [l/Bound Expr] :to [l/Bound Expr] :args [l/Bound-Node] :body true} [node expr] (assoc expr :node node :jvm (facts/infer {:hint (:hint (:jvm expr)) :var-meta (:var-meta (:jvm expr))} node))) -(np/defpass converted {:from [s/Bound Expr] :to [s/Bound Expr] :args [java.lang.Class] :body true} +(np/defpass converted {:from [l/Bound Expr] :to [l/Bound Expr] :args [java.lang.Class] :body true} [^Class type expr] (let [node {:op :host :operation {:op :static :class clojure.lang.RT :member (symbol (str (.getName type) "Cast")) :tags [type]} :args [expr]}] (assoc expr :node node :jvm (facts/infer {:hint nil :var-meta {}} node)))) -(np/defpass boxed {:from [s/Bound Expr] :to [s/Bound Expr] :body true} +(np/defpass boxed {:from [l/Bound Expr] :to [l/Bound Expr] :body true} [expr] (let [node {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags nil} :args [expr]}] ;; Reanalysis can select RT.box(Object), rather than RT.box(long/double). (assoc expr :node node :jvm (facts/infer {:hint nil :var-meta {}} node)))) -(np/defpass initializer {:from [s/Bound Expr] :to [s/Bound Expr] :args [t/Bool] :body true} +(np/defpass initializer {:from [l/Bound Expr] :to [l/Bound Expr] :args [t/Bool] :body true} [box? init] (let [type (primitive (:jvm init))] (if box? (boxed init) (if-let [promoted (cond (= type Integer/TYPE) Long/TYPE (= type Float/TYPE) Double/TYPE)] (converted promoted init) init)))) -(declare expression solve) -(np/defpass bindings - {:from [s/Bound Bindings] :to (t/HVec [(t/Map t/Sym s/Bound-Local) s/Bound-Bindings]) - :args [(t/Map t/Sym s/Bound-Local) t/Bool (t/Set t/Sym)] :body true} - [env loop? boxed-ids originals] - (reduce (fn [^{::t/- (t/HVec [(t/Map t/Sym s/Bound-Local) s/Bound-Bindings])} state - ^{::t/- s/Bound-Binding} binding] - (let [[env out] state init (expression env (:init binding)) local (:local binding) - effective (if loop? (initializer (contains? boxed-ids (:id local)) init) init) - value (facts/binding-facts (:name local) (:mutable? local) (:jvm effective)) - local (assoc local :jvm value)] - [(assoc env (:id local) local) (conj out {:local local :init init})])) [env []] originals)) - -(np/defpass expression - {:from [s/Bound Expr] :to [s/Bound Expr] :args [(t/Map t/Sym s/Bound-Local)] :body true} - [env expr] - (let [node (:node expr) - node (case (:op node) - :local (assoc node :local (get env (:id (:local node)) (:local node))) - :if (assoc node :test (expression env (:test node)) - :then (expression env (:then node)) :else (expression env (:else node))) - :case (assoc node :selector (expression env (:selector node)) - :arms (mapv #(expression env %) (:arms node))) - (:dispatch :throw) (assoc node :value (expression env (:value node))) - :do (assoc node :forms (mapv #(expression env %) (:forms node))) - :collection (assoc node :items (mapv #(expression env %) (:items node)) - :metadata (some->> (:metadata node) (expression env))) - :host (assoc node :args (mapv #(expression env %) (:args node))) - :recur (assoc node :args (mapv #(expression env %) (:args node)) - :target (assoc (:target node) :bindings - (mapv #(get env (:id %) %) (:bindings (:target node))))) - :invoke (assoc node :function (expression env (:function node)) - :args (mapv #(expression env %) (:args node))) - :let (let [[inner values] (bindings env false #{} (:bindings node))] - (assoc node :bindings values :body (expression inner (:body node)))) - :loop (:node (first (solve env expr))) - (:setq :psetq) (assoc node :pairs (mapv #(assoc % :value (expression env (:value %))) (:pairs node))) - node)] - (inferred node expr))) - -(np/defpass recurs {:from [s/Bound Expr] :to [s/Bound Exprs] :args [t/Sym] :body true} +(np/defpass recurs {:from [l/Bound Expr] :to [l/Bound Exprs] :args [t/Sym] :body true} [target expr] (if (and (= :recur (:op (:node expr))) (= target (:id (:target (:node expr))))) [expr] (into [] (mapcat #(recurs target %)) (children expr)))) -(np/defpass compatible? {:from [j/JVM Fact] :to t/Bool :args [java.lang.Class] :body true} +(np/defpass compatible? {:from [l/JVM Fact] :to t/Bool :args [java.lang.Class] :body true} [type fact] (let [actual (primitive fact)] (or (= type actual) (and (= type Long/TYPE) (contains? #{Integer/TYPE Short/TYPE Byte/TYPE Character/TYPE} actual)) (and (= type Double/TYPE) (= actual Float/TYPE))))) -(np/defpass solve - {:from [s/Bound Expr] :to (t/HVec [s/Bound-Expr (t/Set t/Sym)]) - :args [(t/Map t/Sym s/Bound-Local)] :body true} - [env expr] - (let [node (:node expr)] - (if-not (= :loop (:op node)) (throw (IllegalStateException. "Expected source loop")) - ((fn ^{::t/- [(t/Set t/Sym) :-> (t/HVec [s/Bound-Expr (t/Set t/Sym)])]} fixed [boxed-ids] - (let [[env values] (bindings env true boxed-ids (:bindings node)) - body (expression env (:body node)) - mismatches (reduce - (fn [^{::t/- (t/Set t/Sym)} out ^{::t/- s/Bound-Expr} edge] - (into out (keep identity) - (map (fn [^{::t/- s/Bound-Binding} binding ^{::t/- s/Bound-Expr} arg] - (let [local (:local binding) type (primitive (:jvm local))] - (when (and type (contains? #{Long/TYPE Double/TYPE} type) - (not (compatible? type (:jvm arg)))) (:id local)))) - values (:args (:node edge))))) boxed-ids (recurs (:id node) body))] - (if (not= boxed-ids mismatches) (recur mismatches) - [(inferred (assoc node :bindings values :body body) expr) boxed-ids]))) #{})))) +(np/defpasses + (bindings + {:from [l/Bound Bindings] :to (t/HVec [(t/Map t/Sym l/Bound-Local) l/Bound-Bindings]) + :args [(t/Map t/Sym l/Bound-Local) t/Bool (t/Set t/Sym)] :body true} + [env loop? boxed-ids originals] + (reduce (fn [^{::t/- (t/HVec [(t/Map t/Sym l/Bound-Local) l/Bound-Bindings])} state + ^{::t/- l/Bound-Binding} binding] + (let [[env out] state init (expression env (:init binding)) local (:local binding) + effective (if loop? (initializer (contains? boxed-ids (:id local)) init) init) + value (facts/binding-facts (:name local) (:mutable? local) (:jvm effective)) + local (assoc local :jvm value)] + [(assoc env (:id local) local) (conj out {:local local :init init})])) [env []] originals)) + + (expression + {:from [l/Bound Expr] :to [l/Bound Expr] :args [(t/Map t/Sym l/Bound-Local)] :body true} + [env expr] + (let [node (:node expr) + node (case (:op node) + :local (assoc node :local (get env (:id (:local node)) (:local node))) + :if (assoc node :test (expression env (:test node)) + :then (expression env (:then node)) :else (expression env (:else node))) + :case (assoc node :selector (expression env (:selector node)) + :arms (mapv #(expression env %) (:arms node))) + (:dispatch :throw) (assoc node :value (expression env (:value node))) + :do (assoc node :forms (mapv #(expression env %) (:forms node))) + :collection (assoc node :items (mapv #(expression env %) (:items node)) + :metadata (some->> (:metadata node) (expression env))) + :host (assoc node :args (mapv #(expression env %) (:args node))) + :recur (assoc node :args (mapv #(expression env %) (:args node)) + :target (assoc (:target node) :bindings + (mapv #(get env (:id %) %) (:bindings (:target node))))) + :invoke (assoc node :function (expression env (:function node)) + :args (mapv #(expression env %) (:args node))) + :let (let [[inner values] (bindings env false #{} (:bindings node))] + (assoc node :bindings values :body (expression inner (:body node)))) + :loop (:node (first (solve env expr))) + (:setq :psetq) (assoc node :pairs (mapv #(assoc % :value (expression env (:value %))) (:pairs node))) + node)] + (inferred node expr))) + + (solve + {:from [l/Bound Expr] :to (t/HVec [l/Bound-Expr (t/Set t/Sym)]) + :args [(t/Map t/Sym l/Bound-Local)] :body true} + [env expr] + (let [node (:node expr)] + (if-not (= :loop (:op node)) (throw (IllegalStateException. "Expected source loop")) + ((fn ^{::t/- [(t/Set t/Sym) :-> (t/HVec [l/Bound-Expr (t/Set t/Sym)])]} fixed [boxed-ids] + (let [[env values] (bindings env true boxed-ids (:bindings node)) + body (expression env (:body node)) + mismatches (reduce + (fn [^{::t/- (t/Set t/Sym)} out ^{::t/- l/Bound-Expr} edge] + (into out (keep identity) + (map (fn [^{::t/- l/Bound-Binding} binding ^{::t/- l/Bound-Expr} arg] + (let [local (:local binding) type (primitive (:jvm local))] + (when (and type (contains? #{Long/TYPE Double/TYPE} type) + (not (compatible? type (:jvm arg)))) (:id local)))) + values (:args (:node edge))))) boxed-ids (recurs (:id node) body))] + (if (not= boxed-ids mismatches) (recur mismatches) + [(inferred (assoc node :bindings values :body body) expr) boxed-ids]))) #{}))))) ;; RecurExpr's parser admits byte/short/char to long, but its emitter rejects ;; them. Preserve that validation before replacing recur with graph edges. + (np/defpass recur-error - {:from [s/Bound Expr] :to (t/Option (t/HVec [s/Bound-Local s/Bound-Expr])) :body true} + {:from [l/Bound Expr] :to (t/Option (t/HVec [l/Bound-Local l/Bound-Expr])) :body true} [expr] (let [node (:node expr)] (when (= :loop (:op node)) (first - (keep (fn [^{::t/- s/Bound-Expr} edge] + (keep (fn [^{::t/- l/Bound-Expr} edge] (first (keep identity - (map (fn [^{::t/- s/Bound-Binding} binding ^{::t/- s/Bound-Expr} arg] + (map (fn [^{::t/- l/Bound-Binding} binding ^{::t/- l/Bound-Expr} arg] (let [local (:local binding) type (primitive (:jvm local)) actual (primitive (:jvm arg))] (when (and type (not (or (= type actual) (and (= type Long/TYPE) (= actual Integer/TYPE)) @@ -145,7 +144,7 @@ [local arg]))) (:bindings node) (:args (:node edge)))))) (recurs (:id node) (:body node))))))) -(np/defpass recurrence {:from [s/Bound Expr] :to [s/Bound Expr] :body true} +(np/defpass recurrence {:from [l/Bound Expr] :to [l/Bound Expr] :body true} [expr] (let [node (:node expr)] (case (:op node) @@ -161,7 +160,7 @@ :let (assoc expr :node (assoc node :body (recurrence (:body node)))) expr))) -(np/defpass materialize {:from [s/Bound Expr] :to [s/Bound Expr] :args [(t/Set t/Sym)] :body true} +(np/defpass materialize {:from [l/Bound Expr] :to [l/Bound Expr] :args [(t/Set t/Sym)] :body true} [boxed-ids expr] (let [node (:node expr)] (if-not (= :loop (:op node)) (throw (IllegalStateException. "Expected source loop")) @@ -177,7 +176,7 @@ init (if hint (inferred {:op :host :operation {:op :adapt :class hint} :args [init]} init) init)] (assoc binding :init init))) (:bindings node))))))) -(np/defpass lower {:from [s/Bound Expr] :to [s/Bound Expr] :body true} +(np/defpass lower {:from [l/Bound Expr] :to [l/Bound Expr] :body true} [expr] (let [[expr boxed-ids] (solve {} expr)] (when-let [[local arg] (recur-error expr)] diff --git a/src/com/tailrecursion/blacktide/compiler/passes/lower.clj b/src/com/tailrecursion/blacktide/compiler/passes/lower.clj new file mode 100644 index 0000000..b170b65 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/lower.clj @@ -0,0 +1,139 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.lower + "Lower ordered expressions into a CFG with explicit branch joins and exits. + Operands have explicit bindings; operations retain their execution position." + (:require [com.tailrecursion.blacktide.compiler.errors :as errors] + [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.source :as source] + [typed.clojure :as-alias t])) + +(np/defpass fresh-block + {:from [l/Symbolic Builder] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Label]) :body true} + [builder] + [(assoc builder :next (inc (:next builder))) (:next builder)]) + +(np/defpass start + {:from [l/Symbolic Label] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} + [builder label] + (assoc builder :current label :ops [])) + +(np/defpass seal + {:from [l/Symbolic Term] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} + [builder term] + (if (contains? (:blocks builder) (:current builder)) + (throw (ex-info "Control-flow block already has a terminator" {:kind :invalid-graph :block (:current builder)})) + (assoc builder :blocks (assoc (:blocks builder) (:current builder) {:ops (:ops builder) :term term}) :ops []))) + +(np/defpass evaluate + {:from [l/Symbolic Rhs] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) + :args [l/Symbolic-Builder] :body true} + [builder rhs] + (let [[supply id] (source/fresh "value" (:supply builder))] + [(-> builder (assoc :supply supply) (update :ops conj {:target id :value rhs})) {:op :local :id id}])) + +(np/defpasses + (lower-forms + {:from [l/Ordered Exprs] :to (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Atom)]) + :args [l/Symbolic-Builder] :body true} + [builder forms] + (reduce (fn [^{::t/- (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Atom)])} acc ^{::t/- l/Ordered-Expr} expr] + (let [[builder out] acc [builder value] (lower builder expr)] [builder (conj out value)])) + [builder []] forms)) + + (lower-bindings + {:from [l/Ordered Bindings] :to [l/Symbolic Builder] :args [l/Symbolic-Builder] :body true} + [builder bindings] + (reduce (fn [^{::t/- l/Symbolic-Builder} builder ^{::t/- l/Ordered-Binding} binding] + (let [[builder value] (lower builder (:init binding))] + (update builder :ops conj {:target (:id (:local binding)) :value {:op :value :value value}}))) + builder bindings)) + + (lower-node + {:from [l/Ordered Node] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) + :args [l/Symbolic-Builder l/Ordered-Expr]} + [builder expr node] + :literal [builder {:op :literal :value (:value node)}] + :local (let [local (:local node)] + (evaluate builder (if (:external? local) {:op :external :name (:id local) :jvm (:jvm local)} + {:op :value :value {:op :local :id (:id local)}}))) + :global (evaluate builder {:op :global :name (:name node)}) + :var (evaluate builder {:op :var :name (:name node)}) + :native (evaluate builder node) + :dispatch (evaluate builder {:op :dispatch :spec (:spec node) + :value {:value {:op :local :id (:id (:value node))} :jvm (:jvm (:value node))}}) + :select + (let [[builder test] (lower builder (:selector node)) + [builder join] (fresh-block builder) + [supply result] (source/fresh "case_result" (:supply builder)) + [builder targets] (reduce (fn [^{::t/- (t/HVec [l/Symbolic-Builder (t/Vec l/Symbolic-Label)])} acc + ^{::t/- l/Ordered-Expr} _] + (let [[builder targets] acc [builder label] (fresh-block builder)] + [builder (conj targets label)])) + [(assoc builder :supply supply) []] (:arms node)) + builder (seal builder {:op :switch :test test :targets targets}) + builder (reduce (fn [^{::t/- l/Symbolic-Builder} builder ^{::t/- t/Int} index] + (let [[builder value] (lower (start builder (nth targets index)) (nth (:arms node) index))] + (seal (update builder :ops conj {:target result :value {:op :value :value value}}) + {:op :jump :target join}))) builder (range (count targets)))] + [(start builder join) {:op :local :id result}]) + :loop (let [builder (lower-bindings builder (:bindings node)) + [builder header] (fresh-block builder) + builder (assoc builder :loops (assoc (:loops builder) (:id node) header))] + (lower (start (seal builder {:op :jump :target header}) header) (:body node))) + :recur (let [target (:target node) + header (or (get (:loops builder) (:id target)) + (errors/fail :invalid-graph "Missing source recur target" {} (:site expr))) + writes (mapv (fn [local arg] {:target (:id local) :value {:op :value :value {:op :local :id (:id arg)}}}) + (:bindings target) (:args node)) + builder (seal (update builder :ops into writes) {:op :jump :target header}) + [builder next] (fresh-block builder)] + [(start builder next) {:op :literal :value nil}]) + :if (let [[builder test] (lower builder (:test node)) + [builder yes] (fresh-block builder) [builder no] (fresh-block builder) [builder join] (fresh-block builder) + [supply result] (source/fresh "join" (:supply builder)) + builder (seal (assoc builder :supply supply) {:op :branch :test test :yes yes :no no}) + [builder a] (lower (start builder yes) (:then node)) + builder (seal (update builder :ops conj {:target result :value {:op :value :value a}}) {:op :jump :target join}) + [builder b] (lower (start builder no) (:else node)) + builder (seal (update builder :ops conj {:target result :value {:op :value :value b}}) {:op :jump :target join})] + [(start builder join) {:op :local :id result}]) + :throw (let [[builder value] (lower builder (:value node)) + builder (seal builder {:op :throw :value value}) + [builder next] (fresh-block builder)] + [(start builder next) {:op :literal :value nil}]) + :write (let [[builder value] (lower builder (:value node))] + [(update builder :ops conj {:target (:id (:target node)) :value {:op :value :value value}}) value]) + :convert (evaluate builder {:op :convert :conversion (:conversion node) + :value {:value {:op :local :id (:id (:value node))} :jvm (:jvm (:value node))}}) + :host-call (evaluate builder {:op :host :operation (:operation node) + :args (mapv (fn [local] {:value {:op :local :id (:id local)} :jvm (:jvm local)}) (:args node))}) + :invoke (evaluate builder {:op :invoke :function {:op :local :id (:id (:function node))} + :args (mapv (fn [local] {:op :local :id (:id local)}) (:args node))}) + :build (evaluate builder {:op :construct :kind (:kind node) :unique? (:unique? node) + :items (mapv (fn [local] {:op :local :id (:id local)}) (:items node))}) + :metadata (evaluate builder {:op :metadata :value {:op :local :id (:id (:value node))} + :metadata {:op :local :id (:id (:metadata node))}}) + :label (let [key (:name node)] + (when (contains? (:labels builder) key) + (errors/fail :duplicate-label "Duplicate label" {:label (:name node)} (:site expr))) + (let [[builder label] (fresh-block builder) + builder (assoc builder :labels (assoc (:labels builder) key label)) + builder (seal builder {:op :jump :target label})] + [(start builder label) {:op :literal :value nil}])) + :goto (let [builder (seal builder {:op :transfer :name (:name node) :site (:site expr)}) + [builder next] (fresh-block builder)] + [(start builder next) {:op :literal :value nil}]) + :do (let [[builder values] (lower-forms builder (:forms node))] + [builder (or (peek values) {:op :literal :value nil})]) + :let (lower (lower-bindings builder (:bindings node)) (:body node))) + + (lower + {:from [l/Ordered Expr] :to (t/HVec [l/Symbolic-Builder l/Symbolic-Atom]) :args [l/Symbolic-Builder] :body true} + [builder expr] + (lower-node builder expr (:node expr)))) + +(np/defpass compile-graph + {:from [l/Ordered Expr] :to [l/Symbolic Program] :args [l/Bound-Supply] :body true} + [supply expr] + (let [[builder value] (lower {:supply supply :ops [] :next 1 :current 0 :blocks {} :labels {} :loops {}} expr)] + {:entry 0 :labels (:labels builder) :blocks (:blocks (seal builder {:op :return :value value}))})) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/native.clj b/src/com/tailrecursion/blacktide/compiler/passes/native.clj new file mode 100644 index 0000000..08c5c00 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/native.clj @@ -0,0 +1,104 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.native + "Analyze native free reads and recover their source types from machine storage. + Primitive source reads cannot observe the slot's box; reference reads retain + it. Internal bindings keep native inference." + (:require [clojure.set :as set] + [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.collections :as collections] + [com.tailrecursion.blacktide.compiler.passes.host-emit :as host] + [com.tailrecursion.blacktide.compiler.passes.case-emit :as cases] + [typed.clojure :as-alias t])) + +(np/defpass literal-values {:from [l/Native Exprs] :to (t/Option (t/Vec t/Any)) :body true} + [items] + (reduce (fn [^{::t/- (t/Option (t/Vec t/Any))} values ^{::t/- l/Native-Expr} expr] + (let [node (:node expr)] + (if (and values (= :literal (:op node))) (conj values (:value node)) (reduced nil)))) [] items)) + +(np/defpass hinted-form {:from [l/Machine Code] :to [l/Machine Code] :args [l/JVM-Raw] :body true} + [hint code] + ;; Var return tags may be Class objects; source form tags cannot be. + (let [hint (if (class? hint) (symbol (.getName ^Class hint)) hint)] + (with-meta (if (symbol? code) code (apply list code)) + (cond-> (meta code) hint (assoc :tag hint))))) + +(np/defpasses + (bindings-form + {:from [l/Native Bindings] :to (t/Vec t/Any) :args [(t/Set t/Sym)] :body true} + [bound bindings] + (vec (mapcat (fn [^{::t/- l/Native-Binding} binding] + [(with-meta (:id (:local binding)) (meta (:name (:local binding)))) + (form bound (:init binding))]) bindings))) + + (node-form + {:from [l/Native Node] :to [l/Machine Code] :args [(t/Set t/Sym) l/Native-Expr]} + [bound expr node] + :literal (list 'quote (:value node)) + :global (host/typed-form (:jvm expr) (host/global-form (:name node))) + :var (list 'var (:name node)) + :local (let [local (:local node) id (:id local)] + (if (contains? bound id) + (with-meta id (when-let [hint (:hint (:jvm expr))] {:tag hint})) + (host/typed-form (:jvm expr) (host/box-form id)))) + :host (hinted-form (:hint (:jvm expr)) (host/host-form (mapv #(form bound %) (:args node)) (:operation node))) + :invoke (let [function (:function node) source (:form (:site function)) + ;; Quote changes KeywordExpr into ConstantExpr, bypassing the + ;; source call site's IKeywordLookup cache. Preserve its syntax. + callee (if (and (keyword? source) (= :literal (:op (:node function)))) + source (form bound function))] + (hinted-form (:hint (:jvm expr)) (list* callee (mapv #(form bound %) (:args node))))) + :if (list 'if (form bound (:test node)) (form bound (:then node)) (form bound (:else node))) + :case (cases/arms-form (mapv #(form bound %) (:arms node)) (form bound (:selector node))) + :dispatch (cases/selector-form (form bound (:value node)) (:spec node)) + :throw (list 'throw (form bound (:value node))) + :do (list* 'do (mapv #(form bound %) (:forms node))) + :recur (list* 'recur (mapv #(form bound %) (:args node))) + :let (let [bound (into bound (map #(:id (:local %))) (:bindings node))] + (list 'let* (bindings-form bound (:bindings node)) (form bound (:body node)))) + :loop (let [bound (into bound (map #(:id (:local %))) (:bindings node))] + (list 'loop* (bindings-form bound (:bindings node)) (form bound (:body node)))) + :collection + (let [items (:items node) metadata (:metadata node) values (literal-values items) + keys (when (= :map (:kind node)) (literal-values (vec (take-nth 2 items)))) + unique? (boolean (and keys (= (count keys) (count (set keys))))) + _ (when (and (nil? metadata) keys (not unique?)) + (throw (IllegalArgumentException. "Duplicate constant keys in map"))) + value (if (and (nil? metadata) values) + (list 'quote (collections/constant values (:kind node))) + (collections/form (mapv #(form bound %) items) unique? (:kind node)))] + (if metadata (list '. value 'withMeta (form bound metadata)) value))) + + (form {:from [l/Native Expr] :to [l/Machine Code] :args [(t/Set t/Sym)] :body true} + [bound expr] (node-form bound expr (:node expr)))) + +(np/defpasses + (reads-node {:from [l/Native Node] :to (t/Set t/Sym) + :args [(t/Map t/Sym java.lang.Class)]} + [types node] + :literal #{} :global #{} :var #{} + :local (if (:external? (:local node)) #{} #{(:id (:local node))}) + :host (into #{} (mapcat (partial boxed-reads types)) (:args node)) + :invoke (into (boxed-reads types (:function node)) (mapcat (partial boxed-reads types)) (:args node)) + :if (set/union (boxed-reads types (:test node)) (boxed-reads types (:then node)) (boxed-reads types (:else node))) + :case (into (boxed-reads types (:selector node)) (mapcat (partial boxed-reads types)) (:arms node)) + :dispatch (boxed-reads types (:value node)) + :throw (boxed-reads types (:value node)) + :collection (into (if-let [metadata (:metadata node)] (boxed-reads types metadata) #{}) (mapcat (partial boxed-reads types)) (:items node)) + :do (into #{} (mapcat (partial boxed-reads types)) (:forms node)) + :recur (into #{} (mapcat (partial boxed-reads types)) (:args node)) + :let (set/difference (into (boxed-reads types (:body node)) (mapcat #(boxed-reads types (:init %))) (:bindings node)) + (set (map #(get-in % [:local :id]) (:bindings node)))) + :loop (set/difference (into (boxed-reads types (:body node)) (mapcat #(boxed-reads types (:init %))) (:bindings node)) + (set (map #(get-in % [:local :id]) (:bindings node))))) + + (boxed-reads {:from [l/Native Expr] :to (t/Set t/Sym) + :args [(t/Map t/Sym java.lang.Class)] :body true} + [types expr] + (let [node (:node expr) fact (:jvm expr)] + (if (and (= :local (:op node)) (:primitive? fact) + (= (:type fact) (get types (:id (:local node))))) + #{} (reads-node types node))))) + +(np/defpass reads {:from [l/Native Expr] :to (t/Set t/Sym) :body true} + [expr] (boxed-reads {} expr)) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/order.clj b/src/com/tailrecursion/blacktide/compiler/passes/order.clj new file mode 100644 index 0000000..1d9d83c --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/order.clj @@ -0,0 +1,115 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.order + "Make operand evaluation explicit before CFG construction. Calls and collection + construction consume immutable saved locals; source scopes stay unchanged." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.source :as source] + [typed.clojure :as-alias t])) + +(np/defpass save + {:from [l/Ordered Expr] :to (t/HVec [l/Bound-Supply l/Ordered-Binding l/Ordered-Local]) + :args [l/Bound-Supply] :body true} + [supply expr] + (let [[supply id] (source/fresh "operand" supply) + local {:id id :name id :mutable? false :external? false :jvm (:jvm expr)}] + [supply {:local local :init expr} local])) + +(np/defpasses + (operands + {:from [l/Prepared Exprs] + :to (t/HVec [l/Bound-Supply (t/Vec l/Ordered-Binding) (t/Vec l/Ordered-Local)]) + :args [l/Bound-Supply] :body true} + [supply exprs] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply (t/Vec l/Ordered-Binding) (t/Vec l/Ordered-Local)])} acc + ^{::t/- l/Prepared-Expr} expr] + (let [[supply bindings locals] acc + [supply expr] (normalize supply expr) + [supply binding local] (save supply expr)] + [supply (conj bindings binding) (conj locals local)])) [supply [] []] exprs)) + + (normalize-forms + {:from [l/Prepared Exprs] :to (t/HVec [l/Bound-Supply l/Ordered-Exprs]) + :args [l/Bound-Supply] :body true} + [supply exprs] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Ordered-Exprs])} acc ^{::t/- l/Prepared-Expr} expr] + (let [[supply out] acc [supply expr] (normalize supply expr)] [supply (conj out expr)])) + [supply []] exprs)) + + (normalize-bindings + {:from [l/Prepared Bindings] :to (t/HVec [l/Bound-Supply l/Ordered-Bindings]) + :args [l/Bound-Supply] :body true} + [supply bindings] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Ordered-Bindings])} acc ^{::t/- l/Prepared-Binding} binding] + (let [[supply out] acc [supply init] (normalize supply (:init binding))] + [supply (conj out (assoc binding :init init))])) [supply []] bindings)) + + (normalize-node + {:from [l/Prepared Node] :to (t/HVec [l/Bound-Supply l/Ordered-Expr]) + :args [l/Bound-Supply l/Prepared-Expr]} + [supply expr node] + :literal [supply (assoc expr :node node)] + :local [supply (assoc expr :node node)] + :global [supply (assoc expr :node node)] + :var [supply (assoc expr :node node)] + :label [supply (assoc expr :node node)] + :goto [supply (assoc expr :node node)] + :native [supply (assoc expr :node node)] + :select (let [[supply selector] (normalize supply (:selector node)) + [supply arms] (normalize-forms supply (:arms node))] + [supply (assoc expr :node {:op :select :selector selector :arms arms})]) + :dispatch (let [[supply value] (normalize supply (:value node)) + [supply binding local] (save supply value)] + [supply (assoc expr :node {:op :let :bindings [binding] + :body (assoc expr :node {:op :dispatch :value local :spec (:spec node)})})]) + :recur (let [[supply bindings args] (operands supply (:args node))] + [supply (assoc expr :node {:op :let :bindings bindings + :body (assoc expr :node {:op :recur :target (:target node) :args args})})]) + :loop (let [[supply bindings] (normalize-bindings supply (:bindings node)) + [supply body] (normalize supply (:body node))] + [supply (assoc expr :node {:op :loop :id (:id node) :bindings bindings :body body})]) + :if (let [[supply test] (normalize supply (:test node)) + [supply yes] (normalize supply (:then node)) + [supply no] (normalize supply (:else node))] + [supply (assoc expr :node {:op :if :test test :then yes :else no})]) + :throw (let [[supply value] (normalize supply (:value node))] + [supply (assoc expr :node {:op :throw :value value})]) + :write (let [[supply value] (normalize supply (:value node))] + [supply (assoc expr :node {:op :write :target (:target node) :value value})]) + :do (let [[supply forms] (normalize-forms supply (:forms node))] + [supply (assoc expr :node {:op :do :forms forms})]) + :let (let [[supply bindings] (normalize-bindings supply (:bindings node)) + [supply body] (normalize supply (:body node))] + [supply (assoc expr :node {:op :let :bindings bindings :body body})]) + :convert + (let [[supply value] (normalize supply (:value node)) + [supply binding local] (save supply value)] + [supply (assoc expr :node {:op :let :bindings [binding] + :body (assoc expr :node {:op :convert :conversion (:conversion node) :value local})})]) + :host-call + (let [[supply bindings args] (operands supply (:args node))] + [supply (assoc expr :node {:op :let :bindings bindings + :body (assoc expr :node {:op :host-call :operation (:operation node) :args args})})]) + :invoke + (let [[supply function] (normalize supply (:function node)) + [supply binding local] (save supply function) + [supply bindings args] (operands supply (:args node))] + [supply (assoc expr :node {:op :let :bindings (into [binding] bindings) + :body (assoc expr :node {:op :invoke :function local :args args})})]) + :build + (let [[supply bindings items] (operands supply (:items node)) + value (assoc expr :node {:op :build :kind (:kind node) :items items :unique? (:unique? node)}) + [supply bindings value] + (if-let [metadata (:metadata node)] + (let [[supply binding local] (save supply value) + [supply metadata] (normalize supply metadata) + [supply meta-binding meta-local] (save supply metadata)] + [supply (into bindings [binding meta-binding]) + (assoc expr :node {:op :metadata :value local :metadata meta-local})]) + [supply bindings value])] + [supply (assoc expr :node {:op :let :bindings bindings :body value})])) + + (normalize + {:from [l/Prepared Expr] :to (t/HVec [l/Bound-Supply l/Ordered-Expr]) + :args [l/Bound-Supply] :body true} + [supply expr] + (normalize-node supply expr (:node expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj b/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj new file mode 100644 index 0000000..b524474 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/prepare.clj @@ -0,0 +1,44 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.prepare + "Make function casts and resolved JVM argument conversions explicit before + ordering operands. A failing conversion must prevent subsequent evaluation." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.jvm :as jvm] + [typed.clojure :as-alias t])) + +(np/defpass conversion + {:from [l/Prepared Expr] :to [l/Prepared Expr] :args [(t/Option l/JVM-Conversion)] :body true} + [conversion expr] + (if conversion + (let [fact (jvm/fact (.isPrimitive ^Class (:type conversion)) {:hint nil :var-meta {}} (:type conversion))] + ;; The consumer still sees Object, but evaluating an Object cast cannot + ;; fail. Operand normalization supplies the required value snapshot. + (if (= Object (:type conversion)) (assoc expr :jvm fact) + (assoc expr :jvm fact :node {:op :convert :conversion conversion :value expr}))) expr)) + +(np/defpasses + (prepare-forms + {:from [l/Collections Exprs] :to [l/Prepared Exprs] :processors {Expr prepare}} + [forms]) + + (prepare-node + {:from [l/Collections Node] :to [l/Prepared Node] + :processors {Expr prepare Exprs prepare-forms Binding prepare-binding Raw clojure.core/identity Local clojure.core/identity + CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} + [node] + :invoke {:op :invoke :function (conversion {:type clojure.lang.IFn :parameter? false} (prepare (:function node))) + :args (mapv prepare (:args node))} + :host + (let [plan (jvm/prepare-call (mapv #(:type (:jvm %)) (:args node)) (:operation node))] + {:op :host-call :operation (:operation plan) + :args (mapv (fn [how value] (conversion how (prepare value))) (:conversions plan) (:args node))})) + + (prepare-binding + {:from [l/Collections Binding] :to [l/Prepared Binding] + :processors {Expr prepare Local clojure.core/identity}} + [binding]) + + (prepare + {:from [l/Collections Expr] :to [l/Prepared Expr] :body true} + [expr] + (assoc expr :node (prepare-node (:node expr))))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/source.clj b/src/com/tailrecursion/blacktide/compiler/passes/source.clj new file mode 100644 index 0000000..50cbb46 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/source.clj @@ -0,0 +1,410 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source + "Expand in the caller namespace while discovering lexical bindings. Macro + expansion and binding discovery stay ordered; every child becomes bound IR." + (:require [com.tailrecursion.blacktide.compiler.errors :as errors] + [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [typed.clojure :as-alias t] + [com.tailrecursion.blacktide.compiler.jvm :as jvm] + [com.tailrecursion.blacktide.compiler.passes.source-jvm :as facts] + [com.tailrecursion.blacktide.compiler.passes.loop-facts :as loops] + [com.tailrecursion.blacktide.compiler.passes.source-case :as cases] + [com.tailrecursion.blacktide.compiler.passes.effects :refer [effects]] + [com.tailrecursion.blacktide.compiler.passes.source-host :as host])) + +(np/defpass fresh + {:from [l/Bound Supply] :to (t/HVec [l/Bound-Supply t/Sym]) :args [t/Str] :body true} + [stem supply] + ;; Put the unique counter before the user-controlled stem. A source name + ;; must not impersonate another binding's generated identity. + [(assoc supply :next (inc (:next supply))) (symbol (str (:prefix supply) "__" (:next supply) "__" stem))]) + +(np/defpass expression + {:from [l/Bound Node] :to [l/Bound Expr] :args [l/Bound-Context l/JVM-Seed] :body true} + [ctx jvm node] + {:site (:site ctx) :jvm (facts/infer jvm node) :node node}) + +(np/defpass resolve-name + {:from [l/Bound Raw] :to (t/U nil t/AnyVar java.lang.Class) :args [l/Bound-Context] :body true} + [ctx name] + (if (var? name) name + (when (and (symbol? name) (not (contains? (:locals ctx) name))) (ns-resolve *ns* name)))) + +(np/defpass parse-symbol + {:from [l/Bound Raw] :to [l/Bound Expr] :args [l/Bound-Context] :body true} + [ctx form] + (if (symbol? form) + (if-let [local (get (:locals ctx) form)] + (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :local :local local}) + (let [resolved (resolve-name ctx form) qualified (host/qualified form)] + (cond + (var? resolved) + (let [m (or (meta resolved) {})] + (errors/check-visibility resolved (:site ctx)) + (when (:macro m) (errors/fail :invalid-expression "Cannot take the value of a macro" {} (:site ctx))) + (if (:const m) + (expression ctx {:hint nil :var-meta {}} {:op :literal :value (.get ^clojure.lang.Var resolved)}) + (expression ctx {:hint (jvm/source-tag form) :var-meta m} + {:op :global :name (.toSymbol ^clojure.lang.Var resolved)}))) + (class? resolved) (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :literal :value resolved}) + qualified + (let [operation qualified] + (expression ctx {:hint (jvm/source-tag form) :var-meta {}} + {:op :host :operation (if (= :static-field (:op operation)) operation + {:op :method-value :name form}) :args []})) + :else (errors/fail :unresolved-symbol "Unable to resolve symbol" {:binding form} (:site ctx))))) + (errors/fail :invalid-expression "Expected a symbol" {} (:site ctx)))) + +(np/defpass bind-local + {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Context l/Bound-Local]) + :args [l/Bound-Supply l/Bound-Context t/Bool l/JVM-Fact] :body true} + [supply ctx mutable? init name] + (if (simple-symbol? name) + (let [[supply id] (fresh (str name) supply) + jvm (facts/binding-facts name mutable? init) + type (:type jvm) + tag (or (:hint jvm) (when (class? type) (symbol (.getName ^Class type)))) + local {:id id :name name :mutable? mutable? :external? false + :jvm jvm}] + [supply (assoc ctx :locals (assoc (:locals ctx) name local) + :macro-env (assoc (:macro-env ctx) name {:name name :tag tag})) local]) + (errors/fail :invalid-binding "Binding targets must be simple symbols" {:binding name} (:site ctx)))) + +(np/defpass case-expression + {:from [l/Bound Exprs] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context l/Bound-Expr] :body true} + [supply ctx selector arms] + [supply (expression ctx {:hint nil :var-meta {}} {:op :case :selector selector :arms arms})]) + +(np/defpass protocol-argument + {:from [l/Bound Expr] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context l/Bound-Expr java.lang.Class] :body true} + [supply ctx direct? type arg] + (let [seed {:hint nil :var-meta {}} [supply id] (fresh "protocol-argument" supply) + local {:id id :name id :mutable? false :external? false :jvm (facts/binding-facts id false (:jvm arg))} + read (expression ctx seed {:op :local :local local}) + boxed (expression ctx seed {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [Object]} :args [read]}) + converted (if (.isPrimitive ^Class type) + (expression ctx seed {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [type]} :args [read]}) + (expression ctx seed {:op :host :operation {:op :instance :class Class :member 'cast :mode :method :tags [Object]} + :args [(expression ctx seed {:op :literal :value type}) + (expression ctx seed {:op :host :operation {:op :adapt :class type} :args [read]})]}))] + [supply (expression ctx seed {:op :let :bindings [{:local local :init arg}] + :body (expression ctx seed {:op :if :test direct? :then converted :else boxed})})])) + +(np/defpass protocol-call + {:from [l/Bound Exprs] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context l/JVM-Seed l/JVM-Protocol t/Sym] :body true} + [supply ctx seed method name args] + (let [plain {:hint nil :var-meta {}} [supply id] (fresh "protocol-receiver" supply) + receiver (expression ctx plain {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [Object]} :args [(nth args 0)]}) + local {:id id :name id :mutable? false :external? false :jvm (:jvm receiver)} + read (expression ctx plain {:op :local :local local}) + direct? (if-let [class (:class method)] + (expression ctx plain {:op :host :operation {:op :instanceof :class class} :args [read]}) + (expression ctx plain {:op :literal :value false})) + root (expression ctx plain {:op :host :operation {:op :instance :class clojure.lang.Var :member 'getRawRoot :mode :method :tags []} + :args [(expression ctx plain {:op :var :name name})]}) + root (expression ctx plain {:op :if :test direct? :then (expression ctx plain {:op :literal :value nil}) :else root}) + [supply args] (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Exprs])} acc ^{::t/- t/Int} index] + (let [[supply out] acc arg (nth args index) type (get (:parameters method) (dec index) Object) + [supply arg] (if (= Object type) [supply arg] (protocol-argument supply ctx direct? type arg))] + [supply (conj out arg)])) [supply [read root]] (range 1 (count args)))] + [supply (expression ctx seed {:op :let :bindings [{:local local :init receiver}] + :body (expression ctx seed {:op :host :operation {:op :protocol :method method} :args args})})])) + +(np/defpass keyword-call + {:from [l/Bound Expr] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context l/JVM-Seed t/Kw] :body true} + [supply ctx seed key target] + (let [plain {:hint nil :var-meta {}} [supply id] (fresh "keyword-site" supply) + site (expression ctx plain {:op :host :operation {:op :keyword-site :key key} :args []}) + local {:id id :name id :mutable? false :external? false :jvm (:jvm site)} + read (expression ctx plain {:op :local :local local}) + snapshot (expression ctx plain {:op :host :operation {:op :instance :class com.tailrecursion.blacktide.runtime.KeywordSite + :member 'snapshot :mode :method :tags []} :args [read]})] + ;; The first argument snapshots the cache before the target runs. + ;; An IFn call retains KeywordInvokeExpr's unknown result type. + [supply (expression ctx seed {:op :let :bindings [{:local local :init site}] + :body (expression ctx seed {:op :invoke :function read :args [snapshot target]})})])) + +(np/defpass parse-marker + {:from [l/Bound Forms] :to [l/Bound Expr] :args [l/Bound-Context (t/U ':label ':goto)] :body true} + [ctx kind forms] + (let [name (second forms)] + (when-not (and (= 2 (count forms)) (simple-symbol? name)) + (errors/fail :invalid-marker "label/goto requires one simple symbol" {:label name} (:site ctx))) + (when-not (and (:statement? ctx) (:transfer? ctx) (or (= kind :goto) (:outer? ctx))) + (errors/fail :marker-position "Labels require the outer body; goto requires a statement outside operands and initializers" + {:operator kind :label name} (:site ctx))) + (expression ctx {:hint nil :var-meta {}} + (if (= kind :label) {:op :label :name name} + {:op :goto :name name})))) + +(np/defpasses + (parse-operand + {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx form] + (parse supply (assoc ctx :statement? false :tail? false :outer? false :transfer? false) form)) + + (parse-forms + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply (t/Vec l/Bound-Expr)]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply (t/Vec l/Bound-Expr)])} acc ^{::t/- l/Bound-Raw} form] + (let [[supply out] acc + last? (= (count out) (dec (count forms))) + ctx (assoc ctx :tail? (and (:tail? ctx) last?)) + [supply expr] (if (:statement? ctx) (parse supply ctx form) (parse-operand supply ctx form))] + [supply (conj out expr)])) + [supply []] forms)) + + (parse-body + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [[supply forms] (parse-forms supply (assoc ctx :statement? true) forms)] + [supply (expression ctx {:hint nil :var-meta {}} {:op :do :forms forms})])) + + (parse-bindings + {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Context (t/Vec l/Bound-Binding)]) + :args [l/Bound-Supply l/Bound-Context (t/U ':mutable ':immutable ':loop)] :body true} + [supply ctx mode bindings] + (if (and (vector? bindings) (even? (count bindings))) + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Context (t/Vec l/Bound-Binding)])} acc + ^{::t/- (t/Seq t/Any)} pair] + (let [[supply ctx out] acc + [supply init] (parse-operand supply ctx (second pair)) + [supply ctx local] (bind-local supply ctx (not= mode :immutable) (:jvm init) (first pair)) + local (cond-> local (= mode :loop) (assoc :mutable? false)) + ctx (assoc ctx :locals (assoc (:locals ctx) (:name local) local)) + hint (jvm/tag-class (:hint (:jvm local))) + init (if (and hint (= mode :immutable)) + (expression ctx {:hint nil :var-meta {}} {:op :host :operation {:op :adapt :class hint} :args [init]}) init)] + [supply ctx (conj out {:local local :init init})])) [supply ctx []] (partition 2 bindings)) + (errors/fail :invalid-bindings "Expected an even binding vector" {} (:site ctx)))) + + (parse-scope + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context t/Bool] :body true} + [supply ctx mutable? forms] + (let [inner (assoc ctx :outer? mutable?) + [supply inner bindings] (parse-bindings supply inner (if mutable? :mutable :immutable) (second forms)) + [supply body] (parse-body supply inner (vec (drop 2 forms)))] + [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} + {:op :let :bindings bindings :body body})])) + + (parse-loop + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [[supply id] (fresh "loop" supply) + inner (assoc ctx :outer? false :recur nil :tail? false) + [supply inner bindings] (parse-bindings supply inner :loop (second forms)) + target {:id id :bindings (mapv :local bindings)} + [supply body] (parse-body supply (assoc inner :recur target :tail? true) (vec (drop 2 forms)))] + [supply (first (loops/solve {} (expression ctx {:hint nil :var-meta {}} {:op :loop :id id :bindings bindings :body body})))])) + + (parse-recur + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [target (:recur ctx)] + (when-not (and target (:tail? ctx)) + (errors/fail :invalid-recur "recur requires the tail position of a local loop" {} (:site ctx))) + (when-not (= (count (:bindings target)) (dec (count forms))) + (errors/fail :recur-arity "recur argument count does not match its target" {} (:site ctx))) + (let [[supply args] (parse-forms supply (assoc ctx :statement? false :tail? false) (subvec forms 1))] + [supply (expression ctx {:hint nil :var-meta {}} {:op :recur :target target :args args})]))) + + (parse-arms + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Exprs]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Exprs])} acc ^{::t/- t/Any} form] + (let [[supply arms] acc [supply arm] (parse supply (assoc ctx :outer? false) form)] [supply (conj arms arm)])) + [supply []] forms)) + + (parse-case + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [{:keys [selector arms]} (cases/expand-case (:macro-env ctx) forms)] + (if (empty? arms) (parse supply ctx selector) + (do + (when-not (and (seq? selector) (= 3 (count selector))) + (errors/fail :invalid-case "Expected core case's selector binding" {} (:site ctx))) + (let [[supply inner bindings] (parse-bindings supply ctx :immutable (second selector)) + [supply selector] (parse-operand supply inner (first (nnext selector))) + [supply arms] (parse-arms supply inner arms) + [supply body] (case-expression supply ctx selector arms)] + [supply (expression ctx {:hint nil :var-meta {}} {:op :let :bindings bindings :body body})]))))) + + (parse-dispatch + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [{:keys [value spec arms]} (cases/dispatch-syntax forms) + [supply value] (parse-operand supply ctx value)] + (when-not (= :local (:op (:node value))) + (errors/fail :invalid-case "case* dispatch must reference a lexical local" {} (:site ctx))) + (let [selector (expression ctx {:hint nil :var-meta {}} {:op :dispatch :value value :spec spec}) + [supply arms] (parse-arms supply ctx arms)] + (case-expression supply ctx selector arms)))) + + (parse-call + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [inner (assoc ctx :statement? false :tail? false) + [supply function] (parse-operand supply inner (first forms)) + arity (dec (count forms)) + direct (facts/direct-call arity function) + metadata (:var-meta (:jvm function)) + primitive (when-not direct (facts/primitive-interface arity (:arglists metadata))) + hint (or (jvm/source-tag (:form (:site ctx))) + (jvm/source-tag (facts/signature arity (:arglists metadata))) + (when (and (not direct) (not primitive) (= :global (:op (:node function)))) (:hint (:jvm function))) + (:tag metadata)) + [supply args] (parse-forms supply inner (vec (rest forms))) + class-arg (some-> args first :node) + node (cond + (and (= :global (:op (:node function))) (= 'clojure.core/instance? (:name (:node function))) + (= 2 (count args)) (= :literal (:op class-arg)) (class? (:value class-arg))) + {:op :host :operation {:op :instanceof :class (:value class-arg)} :args [(nth args 1)]} + direct {:op :host :operation direct :args args} + primitive {:op :host :operation {:op :instance :class primitive :member 'invokePrim :mode :method :tags nil} + :args (into [function] args)} + :else {:op :invoke :function function :args args})] + (let [method (when-not (or direct primitive) (facts/protocol-call arity function)) + fnode (:node function) key (:form (:site function))] + (cond + (and method (= :global (:op fnode))) + (protocol-call supply ctx {:hint hint :var-meta {}} method (:name fnode) args) + (and (= :literal (:op fnode)) (keyword? key) (= 1 arity) + (effects (nth args 0))) + (keyword-call supply ctx {:hint hint :var-meta {}} key (nth args 0)) + :else [supply (expression ctx {:hint hint :var-meta {}} node)])))) + + (parse-host + {:from [l/JVM Syntax] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx syntax] + (let [[supply args] (parse-forms supply (assoc ctx :statement? false :tail? false) (:operands syntax))] + [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} + {:op :host :operation (:operation syntax) :args args})])) + + (parse-assignment + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context (t/U ':setq ':psetq)] :body true} + [supply ctx kind forms] + (if (even? (count forms)) + (let [[supply pairs] + (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Assignments])} acc ^{::t/- (t/Seq t/Any)} pair] + (let [[supply pairs] acc target (first pair)] + (when-not (simple-symbol? target) + (errors/fail :invalid-assignment "Assignment targets must be simple symbols" {:binding target} (:site ctx))) + (let [local (or (get (:locals ctx) target) + (errors/fail :unknown-assignment "Expected a mutable loophole local" {:binding target} (:site ctx)))] + (when-not (:mutable? local) + (errors/fail :immutable-assignment "Cannot assign to an immutable local" {:binding target} (:site ctx))) + (let [[supply value] (parse-operand supply ctx (second pair))] + [supply (conj pairs {:target local :value value})])))) [supply []] (partition 2 forms))] + [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} + (if (= kind :setq) {:op :setq :pairs pairs} {:op :psetq :pairs pairs}))]) + (errors/fail :invalid-assignment "setq/psetq requires symbol/value pairs" {} (:site ctx)))) + + (parse-list + {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx forms] + (let [op (first forms) form (:form (:site ctx)) resolved (resolve-name ctx op) + marker (when (var? resolved) (get (:markers ctx) resolved)) + _ (when (var? resolved) (errors/check-visibility resolved (:site ctx)))] + (cond + (= :loophole marker) + (errors/fail :unsupported-form "Nested loophole regions are unsupported" {:operator op} (:site ctx)) + (= :setq marker) (parse-assignment supply ctx :setq (vec (rest forms))) + (= :psetq marker) (parse-assignment supply ctx :psetq (vec (rest forms))) + (= :label marker) [supply (parse-marker ctx :label forms)] + (= :goto marker) [supply (parse-marker ctx :goto forms)] + (= op 'quote) + (if (= 2 (count forms)) + [supply (expression ctx {:hint nil :var-meta {}} {:op :literal :value (second forms)})] + (errors/fail :invalid-quote "quote requires one argument" {} (:site ctx))) + (= op 'var) + (let [name (second forms) resolved (when (symbol? name) (ns-resolve *ns* name))] + (if (and (= 2 (count forms)) (var? resolved)) + [supply (expression ctx {:hint nil :var-meta {}} + {:op :var :name (.toSymbol ^clojure.lang.Var resolved)})] + (errors/fail :invalid-var "var requires a resolvable Var name" {} (:site ctx)))) + (#{'fn* 'letfn* 'try 'set! 'monitor-enter 'monitor-exit} op) + (errors/fail :unsupported-form "Form is unsupported inside loophole; place it outside the region" {:operator op} (:site ctx)) + (#{'new '.} op) (parse-host supply ctx (or (host/syntax ctx forms) + (errors/fail :invalid-interop "Missing host syntax" {} (:site ctx)))) + (= op 'do) (parse-body supply ctx (vec (rest forms))) + (= op 'let*) (parse-scope supply ctx false forms) + (= op 'loop*) (parse-loop supply ctx forms) + (= op 'recur) (parse-recur supply ctx forms) + (= op 'case*) (parse-dispatch supply ctx forms) + (and (= resolved #'clojure.core/case) (< (count forms) 2)) + (errors/fail :invalid-case "case requires a dispatch expression" {} (:site ctx)) + (= resolved #'clojure.core/case) (parse-case supply ctx forms) + (= op 'if) + (if (<= 3 (count forms) 4) + (let [[supply test] (parse-operand supply ctx (second forms)) + [supply yes] (parse supply (assoc ctx :outer? false) (nth forms 2)) + [supply no] (parse supply (assoc ctx :outer? false) (get forms 3))] + [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :if :test test :then yes :else no})]) + (errors/fail :invalid-if "if requires a test, then, and optional else" {} (:site ctx))) + (= op 'throw) + (if (= 2 (count forms)) + (let [[supply value] (parse-operand supply ctx (second forms))] + [supply (expression ctx {:hint nil :var-meta {}} {:op :throw :value value})]) + (errors/fail :invalid-throw "throw requires one value" {} (:site ctx))) + (and (symbol? op) (special-symbol? op)) + (errors/fail :unsupported-form "Special form is not supported inside loophole" {:operator op} (:site ctx)) + (and (var? resolved) (:macro (meta resolved))) + (let [expanded (.applyTo ^clojure.lang.Var resolved (cons form (cons (:macro-env ctx) (rest forms)))) + expanded (if (instance? clojure.lang.IObj expanded) + (with-meta expanded (merge (meta expanded) (meta form))) expanded)] + (parse supply ctx expanded)) + (and (var? resolved) (ifn? (:inline (meta resolved))) + (let [arity (:inline-arities (meta resolved))] (or (nil? arity) (and (ifn? arity) (.invoke ^clojure.lang.IFn arity (dec (count forms))))))) + (let [inline (:inline (meta resolved)) + expanded (if (ifn? inline) (.applyTo ^clojure.lang.IFn inline (rest forms)) + (errors/fail :invalid-expression "Invalid inline expander" {} (:site ctx))) + expanded (if (instance? clojure.lang.IObj expanded) + (with-meta expanded (merge (meta expanded) (meta form))) expanded)] + (parse supply ctx expanded)) + :else (if-let [syntax (host/syntax ctx forms)] + (parse-host supply ctx syntax) + (parse-call supply ctx forms))))) + + (parse-collection + {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx form] + (if (or (vector? form) (map? form) (set? form)) + (let [kind (cond (vector? form) :vector (map? form) :map :else :set) + ;; Freeze source traversal before alpha-renaming changes element hashes. + items (if (map? form) (vec (mapcat identity form)) (vec form)) + inner (assoc ctx :statement? false :tail? false) + [supply items] (parse-forms supply inner items) + [supply metadata] (if (some? (meta form)) (parse-operand supply inner (meta form)) [supply nil])] + [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} + {:op :collection :kind kind :items items :metadata metadata})]) + (errors/fail :invalid-expression "Expected a collection" {} (:site ctx)))) + + (parse + {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) + :args [l/Bound-Supply l/Bound-Context] :body true} + [supply ctx form] + (let [ctx (assoc ctx :site (errors/site (:site ctx) form))] + (cond + (symbol? form) [supply (parse-symbol ctx form)] + (and (seq? form) (seq form)) (parse-list supply ctx (vec form)) + (or (vector? form) (map? form) (set? form)) + (parse-collection supply ctx form) + :else [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :literal :value form})])))) diff --git a/src/com/tailrecursion/blacktide/compiler/source_case.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_case.clj similarity index 86% rename from src/com/tailrecursion/blacktide/compiler/source_case.clj rename to src/com/tailrecursion/blacktide/compiler/passes/source_case.clj index 7d565c7..2427814 100644 --- a/src/com/tailrecursion/blacktide/compiler/source_case.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/source_case.clj @@ -1,12 +1,12 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.source-case +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-case "Ask core case to select source-arm ordinals. Constants stay opaque, while source bodies remain unique. Decode prepared case* tables without evaluating them." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] + [com.tailrecursion.blacktide.compiler.languages :as l] [typed.clojure :as-alias t])) (np/defpass expand-case - {:from [s/Bound Forms] :to [s/Bound CaseSyntax] :args [(t/Map t/Sym t/Any)] :body true} + {:from [l/Bound Forms] :to [l/Bound CaseSyntax] :args [(t/Map t/Sym t/Any)] :body true} [env forms] (let [_ (when (< (count forms) 2) (.applyTo ^clojure.lang.Var #'clojure.core/case (cons (apply list forms) (cons env (rest forms))))) @@ -17,12 +17,12 @@ form (list* 'clojure.core/case (second forms) clauses)] {:selector (.applyTo ^clojure.lang.Var #'clojure.core/case (cons form (cons env (rest form)))) :arms arms})) -(np/defpass int32 {:from [s/Bound Raw] :to t/Int :body true} +(np/defpass int32 {:from [l/Bound Raw] :to t/Int :body true} [value] (if (instance? Number value) (.intValue ^Number value) (throw (IllegalArgumentException. "case* expects numeric dispatch data")))) -(np/defpass dispatch-syntax {:from [s/Bound Forms] :to [s/Bound DispatchSyntax] :body true} +(np/defpass dispatch-syntax {:from [l/Bound Forms] :to [l/Bound DispatchSyntax] :body true} [forms] (let [_ (when-not (<= 8 (count forms) 9) (throw (IllegalArgumentException. "Malformed case*"))) [_ value shift mask default table switch test skip] forms] @@ -30,7 +30,7 @@ (#{:int :hash-equiv :hash-identity} test) (or (nil? skip) (set? skip))) (throw (IllegalArgumentException. "Malformed case* dispatch table"))) (let [[tests arms] - (reduce (fn [^{::t/- (t/HVec [(t/Map t/Int (t/HVec [t/Any t/Int])) s/Bound-Forms])} acc + (reduce (fn [^{::t/- (t/HVec [(t/Map t/Int (t/HVec [t/Any t/Int])) l/Bound-Forms])} acc ^{::t/- (t/HVec [t/Any t/Any])} entry] (let [[tests arms] acc [key pair] entry] (when-not (and (vector? pair) (= 2 (count pair))) diff --git a/src/com/tailrecursion/blacktide/compiler/source_host.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj similarity index 93% rename from src/com/tailrecursion/blacktide/compiler/source_host.clj rename to src/com/tailrecursion/blacktide/compiler/passes/source_host.clj index 5e8f5b7..431e186 100644 --- a/src/com/tailrecursion/blacktide/compiler/source_host.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/source_host.clj @@ -1,9 +1,8 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.source-host +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-host "Recognize native JVM syntax with the source lexical environment. Return host descriptors and ordered source operands; none of those operands is evaluated." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.jvm-languages :as l] + [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.jvm :as jvm] [typed.clojure :as-alias t])) @@ -28,7 +27,7 @@ :else {:op :static :class class :member (clojure.core/symbol member) :tags tags}))))) (np/defpass syntax - {:from [s/Bound Forms] :to (t/Option l/JVM-Syntax) :args [s/Bound-Context] :body true} + {:from [l/Bound Forms] :to (t/Option l/JVM-Syntax) :args [l/Bound-Context] :body true} [ctx forms] (let [op (first forms) args (subvec forms 1)] (cond diff --git a/src/com/tailrecursion/blacktide/compiler/source_jvm.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_jvm.clj similarity index 95% rename from src/com/tailrecursion/blacktide/compiler/source_jvm.clj rename to src/com/tailrecursion/blacktide/compiler/passes/source_jvm.clj index 45d2fc2..9f9adf0 100644 --- a/src/com/tailrecursion/blacktide/compiler/source_jvm.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/source_jvm.clj @@ -1,9 +1,8 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.source-jvm +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-jvm "Infer source expression and binding facts before normalization erases native typing evidence. Mutable locals use declared hints, never initializer guesses." (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [com.tailrecursion.blacktide.compiler.jvm-languages :as l] + [com.tailrecursion.blacktide.compiler.languages :as l] [com.tailrecursion.blacktide.compiler.jvm :as jvm] [typed.clojure :as-alias t])) @@ -28,7 +27,7 @@ (clojure.lang.RT/classForNameNonLoading name))))) (np/defpass direct-call - {:from [s/Bound Expr] :to (t/Option l/JVM-Operation) :args [t/Int] :body true} + {:from [l/Bound Expr] :to (t/Option l/JVM-Operation) :args [t/Int] :body true} [arity function] (let [node (:node function)] (when (and (:direct-linking *compiler-options*) (= :global (:op node))) @@ -55,7 +54,7 @@ (when (or (class? on) (symbol? on)) (clojure.lang.Compiler$HostExpr/maybeClass on false))) (np/defpass protocol-call - {:from [s/Bound Expr] :to (t/Option l/JVM-Protocol) :args [t/Int] :body true} + {:from [l/Bound Expr] :to (t/Option l/JVM-Protocol) :args [t/Int] :body true} [arity function] ;; InvokeExpr reads protocol metadata after analyzing the operands. A macro ;; in an argument may change it; later source must not change this call. @@ -76,7 +75,7 @@ {:class nil :member nil :parameters []})))))) (np/defpass infer - {:from [s/Bound Node] :to [l/JVM Fact] :args [l/JVM-Seed] :body true} + {:from [l/Bound Node] :to [l/JVM Fact] :args [l/JVM-Seed] :body true} [seed node] (let [hint (when (#{:local :global :invoke :host} (:op node)) (jvm/tag-class (:hint seed))) fact (fn ^{::t/- [l/JVM-Type t/Bool :-> l/JVM-Fact]} describe [type primitive?] @@ -112,7 +111,7 @@ (assoc (fact (jvm/join-type (:type a) (:type b)) (and (:primitive? a) (:primitive? b) (= (:type a) (:type b)))) :hint-error (or (:hint-error a) (:hint-error b)))) - :case (let [values (reduce (fn [^{::t/- (t/Vec l/JVM-Fact)} values ^{::t/- s/Bound-Expr} arm] + :case (let [values (reduce (fn [^{::t/- (t/Vec l/JVM-Fact)} values ^{::t/- l/Bound-Expr} arm] (if (= :throw (:op (:node arm))) values (conj values (:jvm arm)))) [] (:arms node)) type (some-> values first :type) type (if (and (class? type) (every? #(and (= type (:type %)) (nil? (:hint-error %))) values)) type :unknown)] diff --git a/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj b/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj new file mode 100644 index 0000000..2705187 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/source_values.clj @@ -0,0 +1,38 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.source-values + "Restore primitive source-local reads before compiler temporaries exist. + Clojure boxes each Object use of a primitive local independently; a machine + slot must not turn those reads into aliases of one box. Native trees already + preserve this behavior, and mutable locals have reference semantics." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [typed.clojure :as-alias t])) + +(np/defpasses + (node + {:from [l/Classified Node] :to [l/Classified Node] + :processors {Expr expression Exprs forms Binding binding-form Assignments pairs + Local clojure.core/identity Raw clojure.core/identity + CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} + [node] + :native node) + + (binding-form {:from [l/Classified Binding] :to [l/Classified Binding] + :processors {Expr expression Local clojure.core/identity}} [binding]) + + (pair {:from [l/Classified Assignment] :to [l/Classified Assignment] + :processors {Expr expression Local clojure.core/identity}} [assignment]) + + (pairs {:from [l/Classified Assignments] :to [l/Classified Assignments] + :processors {Assignment pair}} [assignments]) + + (forms {:from [l/Classified Exprs] :to [l/Classified Exprs] :processors {Expr expression}} [exprs]) + + (expression {:from [l/Classified Expr] :to [l/Classified Expr] :body true} + [expr] + (let [expr (assoc expr :node (node (:node expr))) + type (:type (:jvm expr))] + (if (and (= :local (:op (:node expr))) (:primitive? (:jvm expr)) + (class? type) (.isPrimitive ^Class type) (not= Void/TYPE type)) + (assoc expr :node {:op :host :operation {:op :static :class clojure.lang.RT + :member (symbol (str (.getName ^Class type) "Cast")) :tags [type]} + :args [expr]}) expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/storage.clj b/src/com/tailrecursion/blacktide/compiler/passes/storage.clj similarity index 81% rename from src/com/tailrecursion/blacktide/compiler/storage.clj rename to src/com/tailrecursion/blacktide/compiler/passes/storage.clj index 4bb8ea6..887ad3e 100644 --- a/src/com/tailrecursion/blacktide/compiler/storage.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/storage.clj @@ -1,4 +1,4 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.storage +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.storage "Select primitive storage only when every producer and consumer agrees. Object observations and reference-valued native reads retain identity; loop slots specialize only to long/double. Observed copies share boxing requirements; @@ -6,15 +6,13 @@ (:require [clojure.set :as set] [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] - [com.tailrecursion.blacktide.compiler.structured-languages :as s] - [com.tailrecursion.blacktide.compiler.graph :as graph] - [com.tailrecursion.blacktide.compiler.native :as native] - [com.tailrecursion.blacktide.compiler.value-facts :as facts] - [com.tailrecursion.blacktide.compiler.box-origins :as origins] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] + [com.tailrecursion.blacktide.compiler.passes.native :as native] + [com.tailrecursion.blacktide.compiler.passes.value-facts :as facts] + [com.tailrecursion.blacktide.compiler.passes.box-origins :as origins] [typed.clojure :as-alias t])) -(np/defpass loop-slots {:from [s/Joined Expr] :to (t/Set t/Sym)} +(np/defpass loop-slots {:from [l/Joined Expr] :to (t/Set t/Sym)} [node] :bind (loop-slots (:body node)) :join (set/union (loop-slots (:value node)) (loop-slots (:body node))) @@ -23,7 +21,7 @@ :loop (into (loop-slots (:body node)) (:slots node)) :recur #{} :return #{} :throw #{}) -(np/defpass cyclic? {:from [s/Joined Expr] :to t/Bool} +(np/defpass cyclic? {:from [l/Joined Expr] :to t/Bool} [node] :bind (cyclic? (:body node)) :join (or (cyclic? (:value node)) (cyclic? (:body node))) @@ -32,13 +30,13 @@ :loop true :recur true :return false :throw false) (np/defpass operand-boxes - {:from [l/Machine Operand] :to (t/Set t/Sym) :args [s/Stored-Types] :body true} + {:from [l/Machine Operand] :to (t/Set t/Sym) :args [l/Stored-Types] :body true} [types operand] (let [type (facts/atom-type types (:value operand))] (if (and (class? type) (= type (:type (:jvm operand)))) #{} (graph/atom-reads (:value operand))))) (np/defpass operation-boxes - {:from [l/Machine Operation] :to (t/Set t/Sym) :args [s/Stored-Types (t/Set t/Sym)] :body true} + {:from [l/Machine Operation] :to (t/Set t/Sym) :args [l/Stored-Types (t/Set t/Sym)] :body true} [types observed operation] (let [rhs (:value operation)] (case (:op rhs) @@ -68,7 +66,7 @@ (if (= observed next) observed (recur next)))) initial))) (np/defpass term-boxes - {:from [l/Machine Term] :to (t/Set t/Sym) :args [s/Stored-Types (t/Set t/Sym)] :body true} + {:from [l/Machine Term] :to (t/Set t/Sym) :args [l/Stored-Types (t/Set t/Sym)] :body true} [types reboxable term] (case (:op term) :return (if (and (= :local (:op (:value term))) (contains? reboxable (:id (:value term)))) @@ -78,7 +76,7 @@ (graph/term-reads term))) (np/defpass select-storage - {:from [s/Joined Expr] :to [s/Stored Program] :args [a/Analysis-Program] :body true} + {:from [l/Joined Expr] :to [l/Stored Program] :args [l/Analysis-Program] :body true} [analysis expr] ;; Clojure wraps an expression-position loop in an Object-returning call. ;; Acyclic code has no such boundary: keep its return boxed so outer numeric @@ -86,13 +84,13 @@ (let [reboxable (if (cyclic? expr) (origins/reboxable-values analysis) #{}) slots (loop-slots expr) initial (reduce-kv - (fn [^{::t/- s/Stored-Types} out ^{::t/- t/Sym} id ^{::t/- a/Analysis-Type} type] + (fn [^{::t/- l/Stored-Types} out ^{::t/- t/Sym} id ^{::t/- l/Analysis-Type} type] (if (and (class? type) (or (not (contains? slots id)) (#{Long/TYPE Double/TYPE} type))) (assoc out id type) out)) {} (:types analysis)) machine (update (:machine analysis) :blocks #(select-keys % (:reachable analysis))) observed (observed-values machine) blocks (vals (:blocks machine)) - types ((fn ^{::t/- [s/Stored-Types :-> s/Stored-Types]} solve [types] + types ((fn ^{::t/- [l/Stored-Types :-> l/Stored-Types]} solve [types] (let [boxed (into (into #{} (mapcat #(operation-boxes types observed %)) (mapcat :ops blocks)) (mapcat #(term-boxes types reboxable (:term %))) blocks) next (apply dissoc types boxed)] diff --git a/src/com/tailrecursion/blacktide/compiler/passes/structure.clj b/src/com/tailrecursion/blacktide/compiler/passes/structure.clj new file mode 100644 index 0000000..46216ef --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/structure.clj @@ -0,0 +1,160 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.structure + "Recover acyclic control or a single-header loop with bounded duplication. + Construction records block identities to preserve native construction and + cache sites. Dead native code keeps the dispatcher for source validation." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] + [typed.clojure :as-alias t])) + +(np/defpass block + {:from [l/Machine Label] :to [l/Machine Block] :args [l/Machine-Program] :body true} + [machine id] + (or (get (:blocks machine) id) (throw (IllegalStateException. "Missing checked block")))) + +(np/defpass scan + {:from [l/Machine Label] :to [l/Joined Scan] + :args [l/Machine-Program (t/Set l/Machine-Label) l/Joined-Scan] :body true} + [machine path state id] + (let [[seen headers] state] + (cond (contains? path id) [seen (conj headers id)] + (contains? seen id) state + :else (reduce (fn [^{::t/- l/Joined-Scan} state ^{::t/- l/Machine-Label} next] + (scan machine (conj path id) state next)) + [(conj seen id) headers] + (graph/successors (:term (block machine id))))))) + +(np/defpass harmless-atom? {:from [l/Machine Atom] :to t/Bool} + [atom] + :local true + :literal (nil? (:value atom))) + +(np/defpass harmless-block? {:from [l/Machine Block] :to t/Bool :body true} + [block] + ;; Lowering leaves nil result copies after transfers. No host/native form or + ;; arbitrary quoted constant may disappear before Clojure validates it. + (and (every? (fn [op] (let [rhs (:value op)] + (and (= :value (:op rhs)) (harmless-atom? (:value rhs))))) (:ops block)) + (let [term (:term block)] + (case (:op term) + :jump true + (:return :throw) (harmless-atom? (:value term)) + (:branch :switch) (harmless-atom? (:test term)))))) + +(np/defpass wrap + {:from [l/Machine Block] :to [l/Joined Expr] :args [l/Joined-Expr] :body true} + [end block] + (reduce (fn [^{::t/- l/Joined-Expr} body ^{::t/- l/Machine-Operation} op] + {:op :bind :binding op :body body}) end (reverse (:ops block)))) + +(np/defpasses + (arms + {:from [l/Machine Term] :to [l/Joined Arms] :args [l/Joined-Context t/Bool t/Int] :body true} + [ctx inside? budget term] + (if (= :switch (:op term)) + ((fn ^{::t/- [t/Int t/Int (t/Vec l/Joined-Expr) (t/Vec l/Machine-Label) :-> l/Joined-Arms]} build [index budget out blocks] + (if (= index (count (:targets term))) {:op :built :budget budget :arms out :blocks blocks} + (let [result (edge ctx inside? budget (nth (:targets term) index))] + (if (= :built (:op result)) + (recur (inc index) (:budget result) (conj out (:expr result)) (into blocks (:blocks result))) result)))) 0 budget [] []) + {:op :exhausted})) + + (terminator + {:from [l/Machine Term] :to [l/Joined Build] :args [l/Joined-Context t/Bool t/Int]} + [ctx inside? budget term] + :jump (edge ctx inside? budget (:target term)) + :return {:op :built :budget budget :blocks [] :expr term} + :throw {:op :built :budget budget :blocks [] :expr term} + :switch (let [result (arms ctx inside? budget term)] + (if (= :built (:op result)) + {:op :built :budget (:budget result) :blocks (:blocks result) :expr {:op :switch :test (:test term) :arms (:arms result)}} result)) + :branch (let [yes (edge ctx inside? budget (:yes term))] + (if (= :exhausted (:op yes)) yes + (let [no (edge ctx inside? (:budget yes) (:no term))] + (if (= :exhausted (:op no)) no + {:op :built :budget (:budget no) :blocks (into (:blocks yes) (:blocks no)) + :expr {:op :branch :test (:test term) :yes (:expr yes) :no (:expr no)}}))))) + + (joined + {:from [l/Planned Join] :to [l/Joined Build] :args [l/Joined-Context t/Bool t/Int l/Machine-Term] :body true} + [ctx inside? budget term join] + (let [inner (assoc ctx :stop [(:target join) (:result join)]) + result (terminator inner inside? budget term)] + (if (= :built (:op result)) + (let [tail (edge ctx inside? (:budget result) (:target join))] + (if (= :built (:op tail)) + (assoc tail :blocks (into (:blocks result) (:blocks tail)) + :expr {:op :join :target (:result join) :value (:expr result) :body (:expr tail)}) tail)) + {:op :exhausted}))) + + (body + {:from [l/Machine Label] :to [l/Joined Build] :args [l/Joined-Context t/Bool t/Int] :body true} + [ctx inside? budget id] + (let [block (block (:machine ctx) id) + budget (- budget (inc (count (:ops block))))] + (if (neg? budget) {:op :exhausted} + (let [result (if-let [join (get (:joins ctx) id)] + (joined ctx inside? budget (:term block) join) + (terminator ctx inside? budget (:term block)))] + (if (= :built (:op result)) (assoc result :blocks (conj (:blocks result) id) :expr (wrap (:expr result) block)) result))))) + + (edge + {:from [l/Machine Label] :to [l/Joined Build] :args [l/Joined-Context t/Bool t/Int] :body true} + [ctx inside? budget id] + (let [stop (:stop ctx)] + (cond + (and stop (= id (first stop))) + {:op :built :budget budget :blocks [] :expr {:op :return :value {:op :local :id (second stop)}}} + (= id (:header ctx)) + (if inside? (if (pos? budget) {:op :built :budget (dec budget) :blocks [] :expr {:op :recur :slots (:slots ctx)}} + {:op :exhausted}) + (let [result (body ctx true (dec budget) id)] + (if (= :built (:op result)) (assoc result :expr {:op :loop :slots (:slots ctx) :body (:expr result)}) result))) + :else (body ctx inside? budget id))))) + +(np/defpass construction-site? + {:from [l/Machine Rhs] :to t/Bool :body true} + [rhs] + (case (:op rhs) + :native true + :convert (let [conversion (:conversion rhs)] + (and (:parameter? conversion) + (.isAnnotationPresent ^Class (:type conversion) java.lang.FunctionalInterface))) + :host (let [operation (:operation rhs)] + (case (:op operation) + (:keyword-site :method-value) true + :adapt (.isAnnotationPresent ^Class (:class operation) java.lang.FunctionalInterface) + :protocol (boolean (some (fn [^Class type] (.isAnnotationPresent type java.lang.FunctionalInterface)) + (:parameters (:method operation)))) + false)) + false)) + +(np/defpass site-failure + {:from [l/Machine Program] :to (t/Option ':native-sites) + :args [(t/Vec l/Machine-Label)] :body true} + [blocks machine] + (let [copies (frequencies blocks) + repeated (mapv (fn [[id _]] (block machine id)) (filter (fn [[_ n]] (> n 1)) copies))] + (when (some (fn [block] + (some (fn [operation] (construction-site? (:value operation))) + (:ops block))) repeated) + :native-sites))) + +(np/defpass recover {:from [l/Planned Program] :to [l/Joined Decision] :body true} + [analysis] + (let [machine (:machine analysis) reachable (:reachable analysis) + [_ headers] (scan machine #{} [#{} #{}] (:entry machine)) + dead (apply dissoc (:blocks machine) reachable) + header (first headers)] + (cond + (> (count headers) 1) {:op :fallback :reason :cycles} + (not (every? harmless-block? (vals dead))) {:op :fallback :reason :validation} + :else + (let [slots (if header (vec (sort (get (:live machine) header #{}))) []) + ctx {:machine machine :header header :slots slots :joins (:joins analysis) :stop nil} + budget (* 4 (reduce + 0 (map #(inc (count (:ops %))) (vals (select-keys (:blocks machine) reachable))))) + result (edge ctx false budget (:entry machine))] + (if (= :built (:op result)) + (if-let [reason (site-failure (:blocks result) machine)] + {:op :fallback :reason reason} {:op :structured :body (:expr result)}) + {:op :fallback :reason :budget}))))) diff --git a/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj b/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj new file mode 100644 index 0000000..135d033 --- /dev/null +++ b/src/com/tailrecursion/blacktide/compiler/passes/structured_emit.clj @@ -0,0 +1,108 @@ +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.structured-emit + "Emit lexical control with proven storage classes. Consumer source types stay + distinct from storage; Java argument conversions remain explicit evaluations." + (:require [com.tailrecursion.nanopass :as np] + [com.tailrecursion.blacktide.compiler.languages :as l] + [com.tailrecursion.blacktide.compiler.passes.compact :as compact] + [com.tailrecursion.blacktide.compiler.passes.collections :as collections] + [com.tailrecursion.blacktide.compiler.jvm :as jvm] + [typed.clojure :as-alias t] + [com.tailrecursion.blacktide.compiler.passes.emit :as emit] + [com.tailrecursion.blacktide.compiler.passes.host-emit :as host] + [com.tailrecursion.blacktide.compiler.passes.case-emit :as cases])) + +(np/defpass primitive-form + {:from [l/Machine Code] :to [l/Machine Code] :args [java.lang.Class] :body true} + [^Class type form] + ;; The parameter conversion is exactly the conversion previously performed by + ;; RT.box(type); the matching primitive cast overload itself is an identity. + (list (with-meta (symbol "clojure.lang.RT" (str (.getName type) "Cast")) {:param-tags [type]}) form)) + +(np/defpass binding-form + {:from [l/Machine Code] :to [l/Machine Code] :args [l/Stored-Types t/Sym (t/Option java.lang.Class)] :body true} + [types id actual form] + (if-let [type (get types id)] (if (= type actual) form (primitive-form type form)) (host/box-form form))) + +(np/defpasses + (atom-form {:from [l/Compact Atom] :to [l/Machine Code] :args [l/Compact-Types]} + [types node] + :local (:id node) + :literal (list 'quote (:value node)) + :inline (rhs-form types (:value node))) + + (operand-form + {:from [l/Compact Operand] :to [l/Machine Code] :args [l/Compact-Types] :body true} + [types operand] + (let [form (atom-form types (:value operand)) actual (compact/atom-type types (:value operand)) + fact (:jvm operand)] + (if (and (class? actual) (= actual (:type fact))) form + (host/typed-form (assoc fact :primitive? false) (if (class? actual) (host/box-form form) form))))) + + (rhs-form + {:from [l/Compact Rhs] :to [l/Machine Code] :args [l/Compact-Types]} + [types rhs] + :host (host/host-form (mapv #(operand-form types %) (:args rhs)) (:operation rhs)) + :dispatch (cases/selector-form (operand-form types (:value rhs)) (:spec rhs)) + :convert (let [conversion (:conversion rhs) operand (:value rhs)] + (if (and (:parameter? conversion) (.isPrimitive ^Class (:type conversion))) + (if (= (:type conversion) (compact/atom-type types (:value operand)) (:type (:jvm operand))) + (atom-form types (:value operand)) + (primitive-form (:type conversion) (operand-form types operand))) + (host/conversion-form conversion (:jvm operand) (atom-form types (:value operand))))) + :value (atom-form types (:value rhs)) + :global (emit/rhs-form rhs) :external (emit/rhs-form rhs) :var (emit/rhs-form rhs) :native (emit/rhs-form rhs) + :construct (collections/form (mapv #(atom-form types %) (:items rhs)) (:unique? rhs) (:kind rhs)) + :metadata (let [value (gensym "value__") metadata (gensym "metadata__")] + (list 'let* [(with-meta value {:tag 'clojure.lang.IObj}) (atom-form types (:value rhs)) + (with-meta metadata {:tag 'clojure.lang.IPersistentMap}) (atom-form types (:metadata rhs))] + (list '. value 'withMeta metadata))) + :invoke (list* (atom-form types (:function rhs)) (mapv #(atom-form types %) (:args rhs))))) + +(np/defpass rhs-type + {:from [l/Compact Rhs] :to (t/Option java.lang.Class) :args [l/Compact-Types] :body true} + [types rhs] + (case (:op rhs) + :value (compact/atom-type types (:value rhs)) + :convert (when (:parameter? (:conversion rhs)) (:type (:conversion rhs))) + :host (let [type (jvm/host-type (mapv #(:type (:jvm %)) (:args rhs)) (:operation rhs))] + (when (and (class? type) (not (#{:adapt} (:op (:operation rhs))))) type)) + ;; Other producers retain an explicit storage conversion. + nil)) + +(np/defpass operation-form + {:from [l/Compact Operation] :to [l/Machine Code] :args [l/Compact-Types] :body true} + [types op] + (binding-form types (:target op) (rhs-type types (:value op)) (rhs-form types (:value op)))) + +(np/defpasses + (let-form {:from [l/Compact Expr] :to [l/Machine Code] :args [l/Compact-Types] :body true} + [types node] + ((fn ^{::t/- [(t/Vec t/Any) l/Compact-Expr :-> l/Machine-Code]} bindings [pairs node] + (case (:op node) + :bind (let [op (:binding node)] + (recur (conj pairs (:target op) (operation-form types op)) (:body node))) + :join (recur (conj pairs (:target node) + (binding-form types (:target node) nil (expression types (:value node)))) (:body node)) + (list 'let* pairs (expression types node)))) [] node)) + + (expression {:from [l/Compact Expr] :to [l/Machine Code] :args [l/Compact-Types]} + [types node] + :bind (let-form types node) + :effect (let [binding (:binding node) rhs (:value binding) type (get types (:target binding)) + form (rhs-form types rhs)] + (list 'do (if (and type (not= type (rhs-type types rhs))) (primitive-form type form) form) + (expression types (:body node)))) + :join (let-form types node) + :branch (list 'if (atom-form types (:test node)) (expression types (:yes node)) (expression types (:no node))) + :switch (cases/arms-form (mapv #(expression types %) (:arms node)) (atom-form types (:test node))) + :loop (list 'loop* (vec (mapcat (fn [binding] + [(:target binding) (binding-form types (:target binding) + (compact/atom-type types (:value binding)) + (atom-form types (:value binding)))]) (:bindings node))) + (expression types (:body node))) + :recur (list* 'recur (mapv #(atom-form types %) (:args node))) + :return (let [value (:value node) form (atom-form types value)] + ;; Tail compaction must retain the region's opaque result type. + (if (and (= :inline (:op value)) (= :native (:op (:value value)))) + (host/box-form form) form)) + :throw (list 'throw (atom-form types (:value node))))) diff --git a/src/com/tailrecursion/blacktide/compiler/transfers.clj b/src/com/tailrecursion/blacktide/compiler/passes/transfers.clj similarity index 94% rename from src/com/tailrecursion/blacktide/compiler/transfers.clj rename to src/com/tailrecursion/blacktide/compiler/passes/transfers.clj index 3be932b..a64bc28 100644 --- a/src/com/tailrecursion/blacktide/compiler/transfers.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/transfers.clj @@ -1,4 +1,4 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.transfers +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.transfers "Resolve region-local jumps after all labels exist, including dead source. Source analysis admits only outer-body labels and statement-position jumps." (:require [com.tailrecursion.blacktide.compiler.errors :as errors] diff --git a/src/com/tailrecursion/blacktide/compiler/value_facts.clj b/src/com/tailrecursion/blacktide/compiler/passes/value_facts.clj similarity index 68% rename from src/com/tailrecursion/blacktide/compiler/value_facts.clj rename to src/com/tailrecursion/blacktide/compiler/passes/value_facts.clj index 75d5732..dd45f4d 100644 --- a/src/com/tailrecursion/blacktide/compiler/value_facts.clj +++ b/src/com/tailrecursion/blacktide/compiler/passes/value_facts.clj @@ -1,23 +1,21 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.value-facts +(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.value-facts "Prove primitive results across all reachable definitions, including copy cycles. Hints describe consumers; only actual primitive producers prove values." (:require [com.tailrecursion.nanopass :as np] [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] - [com.tailrecursion.blacktide.compiler.graph :as graph] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] [typed.clojure :as-alias t])) -(np/defpass primitive {:from [j/JVM Type] :to [a/Analysis Type] :body true} +(np/defpass primitive {:from [l/JVM Type] :to [l/Analysis Type] :body true} [type] (if (and (class? type) (.isPrimitive ^Class type) (not= Void/TYPE type)) type :boxed)) -(np/defpass join {:from [a/Analysis Type] :to [a/Analysis Type] :args [a/Analysis-Type] :body true} +(np/defpass join {:from [l/Analysis Type] :to [l/Analysis Type] :args [l/Analysis-Type] :body true} [a b] (cond (= a :bottom) b (= b :bottom) a (= a b) a :else :boxed)) -(np/defpass atom-type {:from [l/Machine Atom] :to [a/Analysis Type] :args [a/Analysis-Types]} +(np/defpass atom-type {:from [l/Machine Atom] :to [l/Analysis Type] :args [l/Analysis-Types]} [types node] :local (get types (:id node) :bottom) ;; Quoted Integer constants must keep their boxed identity and width. @@ -25,7 +23,7 @@ (instance? Double (:value node)) (primitive Double/TYPE) :else :boxed)) -(np/defpass rhs-type {:from [l/Machine Rhs] :to [a/Analysis Type] :args [a/Analysis-Types]} +(np/defpass rhs-type {:from [l/Machine Rhs] :to [l/Analysis Type] :args [l/Analysis-Types]} [types rhs] :value (atom-type types (:value rhs)) :external (if (:primitive? (:jvm rhs)) (primitive (:type (:jvm rhs))) :boxed) @@ -41,25 +39,25 @@ :invoke :boxed) (np/defpass definitions - {:from [l/Machine Program] :to [a/Analysis Definitions] :args [(t/Set l/Machine-Label)] :body true} + {:from [l/Machine Program] :to [l/Analysis Definitions] :args [(t/Set l/Machine-Label)] :body true} [reachable machine] - (reduce (fn [^{::t/- a/Analysis-Definitions} out ^{::t/- l/Machine-Operation} op] + (reduce (fn [^{::t/- l/Analysis-Definitions} out ^{::t/- l/Machine-Operation} op] (assoc out (:target op) (conj (get out (:target op) []) (:value op)))) {} (mapcat :ops (vals (select-keys (:blocks machine) reachable))))) -(np/defpass solve {:from [a/Analysis Definitions] :to [a/Analysis Types] :body true} +(np/defpass solve {:from [l/Analysis Definitions] :to [l/Analysis Types] :body true} [definitions] - ((fn ^{::t/- [a/Analysis-Types :-> a/Analysis-Types]} fixed-point [types] + ((fn ^{::t/- [l/Analysis-Types :-> l/Analysis-Types]} fixed-point [types] (let [next (reduce-kv - (fn [^{::t/- a/Analysis-Types} out ^{::t/- t/Sym} id ^{::t/- (t/Vec l/Machine-Rhs)} values] + (fn [^{::t/- l/Analysis-Types} out ^{::t/- t/Sym} id ^{::t/- (t/Vec l/Machine-Rhs)} values] (assoc out id - ((fn ^{::t/- [t/Int a/Analysis-Type :-> a/Analysis-Type]} common [index type] + ((fn ^{::t/- [t/Int l/Analysis-Type :-> l/Analysis-Type]} common [index type] (if (< index (count values)) (recur (inc index) (join type (rhs-type types (nth values index)))) type)) 0 :bottom))) {} definitions)] (if (= types next) next (recur next)))) {})) -(np/defpass analyze {:from [l/Machine Program] :to [a/Analysis Program] :body true} +(np/defpass analyze {:from [l/Machine Program] :to [l/Analysis Program] :body true} [machine] (let [reachable (graph/reachable (graph/collect-facts {:entry (:entry machine) :blocks (:blocks machine)}) (:entry machine))] {:machine machine :reachable reachable :types (solve (definitions reachable machine))})) diff --git a/src/com/tailrecursion/blacktide/compiler/prepare.clj b/src/com/tailrecursion/blacktide/compiler/prepare.clj deleted file mode 100644 index 3d004e4..0000000 --- a/src/com/tailrecursion/blacktide/compiler/prepare.clj +++ /dev/null @@ -1,46 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.prepare - "Make function casts and resolved JVM argument conversions explicit before - ordering operands. A failing conversion must prevent subsequent evaluation." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as l] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] - [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [typed.clojure :as-alias t])) - -(declare prepare prepare-binding) - -(np/defpass prepare-forms - {:from [l/Collections Exprs] :to [l/Prepared Exprs] :processors {Expr prepare}} - [forms]) - -(np/defpass conversion - {:from [l/Prepared Expr] :to [l/Prepared Expr] :args [(t/Option j/JVM-Conversion)] :body true} - [conversion expr] - (if conversion - (let [fact (jvm/fact (.isPrimitive ^Class (:type conversion)) {:hint nil :var-meta {}} (:type conversion))] - ;; The consumer still sees Object, but evaluating an Object cast cannot - ;; fail. Operand normalization supplies the required value snapshot. - (if (= Object (:type conversion)) (assoc expr :jvm fact) - (assoc expr :jvm fact :node {:op :convert :conversion conversion :value expr}))) expr)) - -(np/defpass prepare-node - {:from [l/Collections Node] :to [l/Prepared Node] - :processors {Expr prepare Exprs prepare-forms Binding prepare-binding Raw clojure.core/identity Local clojure.core/identity - CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} - [node] - :invoke {:op :invoke :function (conversion {:type clojure.lang.IFn :parameter? false} (prepare (:function node))) - :args (mapv prepare (:args node))} - :host - (let [plan (jvm/prepare-call (mapv #(:type (:jvm %)) (:args node)) (:operation node))] - {:op :host-call :operation (:operation plan) - :args (mapv (fn [how value] (conversion how (prepare value))) (:conversions plan) (:args node))})) - -(np/defpass prepare-binding - {:from [l/Collections Binding] :to [l/Prepared Binding] - :processors {Expr prepare Local clojure.core/identity}} - [binding]) - -(np/defpass prepare - {:from [l/Collections Expr] :to [l/Prepared Expr] :body true} - [expr] - (assoc expr :node (prepare-node (:node expr)))) diff --git a/src/com/tailrecursion/blacktide/compiler/source.clj b/src/com/tailrecursion/blacktide/compiler/source.clj deleted file mode 100644 index 53ea460..0000000 --- a/src/com/tailrecursion/blacktide/compiler/source.clj +++ /dev/null @@ -1,412 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.source - "Expand in the caller namespace while discovering lexical bindings. Macro - expansion and binding discovery stay ordered; every child becomes bound IR." - (:require [com.tailrecursion.blacktide.compiler.errors :as errors] - [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as l] - [typed.clojure :as-alias t] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j] - [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [com.tailrecursion.blacktide.compiler.source-jvm :as facts] - [com.tailrecursion.blacktide.compiler.loop-facts :as loops] - [com.tailrecursion.blacktide.compiler.source-case :as cases] - [com.tailrecursion.blacktide.compiler.effects :refer [effects]] - [com.tailrecursion.blacktide.compiler.source-host :as host])) - -(declare parse parse-forms parse-list parse-bindings) - -(np/defpass fresh - {:from [l/Bound Supply] :to (t/HVec [l/Bound-Supply t/Sym]) :args [t/Str] :body true} - [stem supply] - ;; Put the unique counter before the user-controlled stem. A source name - ;; must not impersonate another binding's generated identity. - [(assoc supply :next (inc (:next supply))) (symbol (str (:prefix supply) "__" (:next supply) "__" stem))]) - -(np/defpass expression - {:from [l/Bound Node] :to [l/Bound Expr] :args [l/Bound-Context j/JVM-Seed] :body true} - [ctx jvm node] - {:site (:site ctx) :jvm (facts/infer jvm node) :node node}) - -(np/defpass resolve-name - {:from [l/Bound Raw] :to (t/U nil t/AnyVar java.lang.Class) :args [l/Bound-Context] :body true} - [ctx name] - (if (var? name) name - (when (and (symbol? name) (not (contains? (:locals ctx) name))) (ns-resolve *ns* name)))) - -(np/defpass parse-symbol - {:from [l/Bound Raw] :to [l/Bound Expr] :args [l/Bound-Context] :body true} - [ctx form] - (if (symbol? form) - (if-let [local (get (:locals ctx) form)] - (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :local :local local}) - (let [resolved (resolve-name ctx form) qualified (host/qualified form)] - (cond - (var? resolved) - (let [m (or (meta resolved) {})] - (errors/check-visibility resolved (:site ctx)) - (when (:macro m) (errors/fail :invalid-expression "Cannot take the value of a macro" {} (:site ctx))) - (if (:const m) - (expression ctx {:hint nil :var-meta {}} {:op :literal :value (.get ^clojure.lang.Var resolved)}) - (expression ctx {:hint (jvm/source-tag form) :var-meta m} - {:op :global :name (.toSymbol ^clojure.lang.Var resolved)}))) - (class? resolved) (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :literal :value resolved}) - qualified - (let [operation qualified] - (expression ctx {:hint (jvm/source-tag form) :var-meta {}} - {:op :host :operation (if (= :static-field (:op operation)) operation - {:op :method-value :name form}) :args []})) - :else (errors/fail :unresolved-symbol "Unable to resolve symbol" {:binding form} (:site ctx))))) - (errors/fail :invalid-expression "Expected a symbol" {} (:site ctx)))) - -(np/defpass parse-operand - {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx form] - (parse supply (assoc ctx :statement? false :tail? false :outer? false :transfer? false) form)) - -(np/defpass parse-forms - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply (t/Vec l/Bound-Expr)]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply (t/Vec l/Bound-Expr)])} acc ^{::t/- l/Bound-Raw} form] - (let [[supply out] acc - last? (= (count out) (dec (count forms))) - ctx (assoc ctx :tail? (and (:tail? ctx) last?)) - [supply expr] (if (:statement? ctx) (parse supply ctx form) (parse-operand supply ctx form))] - [supply (conj out expr)])) - [supply []] forms)) - -(np/defpass parse-body - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [[supply forms] (parse-forms supply (assoc ctx :statement? true) forms)] - [supply (expression ctx {:hint nil :var-meta {}} {:op :do :forms forms})])) - -(np/defpass bind-local - {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Context l/Bound-Local]) - :args [l/Bound-Supply l/Bound-Context t/Bool j/JVM-Fact] :body true} - [supply ctx mutable? init name] - (if (simple-symbol? name) - (let [[supply id] (fresh (str name) supply) - jvm (facts/binding-facts name mutable? init) - type (:type jvm) - tag (or (:hint jvm) (when (class? type) (symbol (.getName ^Class type)))) - local {:id id :name name :mutable? mutable? :external? false - :jvm jvm}] - [supply (assoc ctx :locals (assoc (:locals ctx) name local) - :macro-env (assoc (:macro-env ctx) name {:name name :tag tag})) local]) - (errors/fail :invalid-binding "Binding targets must be simple symbols" {:binding name} (:site ctx)))) - -(np/defpass parse-bindings - {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Context (t/Vec l/Bound-Binding)]) - :args [l/Bound-Supply l/Bound-Context (t/U ':mutable ':immutable ':loop)] :body true} - [supply ctx mode bindings] - (if (and (vector? bindings) (even? (count bindings))) - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Context (t/Vec l/Bound-Binding)])} acc - ^{::t/- (t/Seq t/Any)} pair] - (let [[supply ctx out] acc - [supply init] (parse-operand supply ctx (second pair)) - [supply ctx local] (bind-local supply ctx (not= mode :immutable) (:jvm init) (first pair)) - local (cond-> local (= mode :loop) (assoc :mutable? false)) - ctx (assoc ctx :locals (assoc (:locals ctx) (:name local) local)) - hint (jvm/tag-class (:hint (:jvm local))) - init (if (and hint (= mode :immutable)) - (expression ctx {:hint nil :var-meta {}} {:op :host :operation {:op :adapt :class hint} :args [init]}) init)] - [supply ctx (conj out {:local local :init init})])) [supply ctx []] (partition 2 bindings)) - (errors/fail :invalid-bindings "Expected an even binding vector" {} (:site ctx)))) - -(np/defpass parse-scope - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context t/Bool] :body true} - [supply ctx mutable? forms] - (let [inner (assoc ctx :outer? mutable?) - [supply inner bindings] (parse-bindings supply inner (if mutable? :mutable :immutable) (second forms)) - [supply body] (parse-body supply inner (vec (drop 2 forms)))] - [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} - {:op :let :bindings bindings :body body})])) - -(np/defpass parse-loop - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [[supply id] (fresh "loop" supply) - inner (assoc ctx :outer? false :recur nil :tail? false) - [supply inner bindings] (parse-bindings supply inner :loop (second forms)) - target {:id id :bindings (mapv :local bindings)} - [supply body] (parse-body supply (assoc inner :recur target :tail? true) (vec (drop 2 forms)))] - [supply (first (loops/solve {} (expression ctx {:hint nil :var-meta {}} {:op :loop :id id :bindings bindings :body body})))])) - -(np/defpass parse-recur - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [target (:recur ctx)] - (when-not (and target (:tail? ctx)) - (errors/fail :invalid-recur "recur requires the tail position of a local loop" {} (:site ctx))) - (when-not (= (count (:bindings target)) (dec (count forms))) - (errors/fail :recur-arity "recur argument count does not match its target" {} (:site ctx))) - (let [[supply args] (parse-forms supply (assoc ctx :statement? false :tail? false) (subvec forms 1))] - [supply (expression ctx {:hint nil :var-meta {}} {:op :recur :target target :args args})]))) - -(np/defpass parse-arms - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Exprs]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Exprs])} acc ^{::t/- t/Any} form] - (let [[supply arms] acc [supply arm] (parse supply (assoc ctx :outer? false) form)] [supply (conj arms arm)])) - [supply []] forms)) - -(np/defpass case-expression - {:from [l/Bound Exprs] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context l/Bound-Expr] :body true} - [supply ctx selector arms] - [supply (expression ctx {:hint nil :var-meta {}} {:op :case :selector selector :arms arms})]) - -(np/defpass parse-case - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [{:keys [selector arms]} (cases/expand-case (:macro-env ctx) forms)] - (if (empty? arms) (parse supply ctx selector) - (do - (when-not (and (seq? selector) (= 3 (count selector))) - (errors/fail :invalid-case "Expected core case's selector binding" {} (:site ctx))) - (let [[supply inner bindings] (parse-bindings supply ctx :immutable (second selector)) - [supply selector] (parse-operand supply inner (first (nnext selector))) - [supply arms] (parse-arms supply inner arms) - [supply body] (case-expression supply ctx selector arms)] - [supply (expression ctx {:hint nil :var-meta {}} {:op :let :bindings bindings :body body})]))))) - -(np/defpass parse-dispatch - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [{:keys [value spec arms]} (cases/dispatch-syntax forms) - [supply value] (parse-operand supply ctx value)] - (when-not (= :local (:op (:node value))) - (errors/fail :invalid-case "case* dispatch must reference a lexical local" {} (:site ctx))) - (let [selector (expression ctx {:hint nil :var-meta {}} {:op :dispatch :value value :spec spec}) - [supply arms] (parse-arms supply ctx arms)] - (case-expression supply ctx selector arms)))) - -(np/defpass protocol-argument - {:from [l/Bound Expr] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context l/Bound-Expr java.lang.Class] :body true} - [supply ctx direct? type arg] - (let [seed {:hint nil :var-meta {}} [supply id] (fresh "protocol-argument" supply) - local {:id id :name id :mutable? false :external? false :jvm (facts/binding-facts id false (:jvm arg))} - read (expression ctx seed {:op :local :local local}) - boxed (expression ctx seed {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [Object]} :args [read]}) - converted (if (.isPrimitive ^Class type) - (expression ctx seed {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [type]} :args [read]}) - (expression ctx seed {:op :host :operation {:op :instance :class Class :member 'cast :mode :method :tags [Object]} - :args [(expression ctx seed {:op :literal :value type}) - (expression ctx seed {:op :host :operation {:op :adapt :class type} :args [read]})]}))] - [supply (expression ctx seed {:op :let :bindings [{:local local :init arg}] - :body (expression ctx seed {:op :if :test direct? :then converted :else boxed})})])) - -(np/defpass protocol-call - {:from [l/Bound Exprs] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context j/JVM-Seed j/JVM-Protocol t/Sym] :body true} - [supply ctx seed method name args] - (let [plain {:hint nil :var-meta {}} [supply id] (fresh "protocol-receiver" supply) - receiver (expression ctx plain {:op :host :operation {:op :static :class clojure.lang.RT :member 'box :tags [Object]} :args [(nth args 0)]}) - local {:id id :name id :mutable? false :external? false :jvm (:jvm receiver)} - read (expression ctx plain {:op :local :local local}) - direct? (if-let [class (:class method)] - (expression ctx plain {:op :host :operation {:op :instanceof :class class} :args [read]}) - (expression ctx plain {:op :literal :value false})) - root (expression ctx plain {:op :host :operation {:op :instance :class clojure.lang.Var :member 'getRawRoot :mode :method :tags []} - :args [(expression ctx plain {:op :var :name name})]}) - root (expression ctx plain {:op :if :test direct? :then (expression ctx plain {:op :literal :value nil}) :else root}) - [supply args] (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Exprs])} acc ^{::t/- t/Int} index] - (let [[supply out] acc arg (nth args index) type (get (:parameters method) (dec index) Object) - [supply arg] (if (= Object type) [supply arg] (protocol-argument supply ctx direct? type arg))] - [supply (conj out arg)])) [supply [read root]] (range 1 (count args)))] - [supply (expression ctx seed {:op :let :bindings [{:local local :init receiver}] - :body (expression ctx seed {:op :host :operation {:op :protocol :method method} :args args})})])) - -(np/defpass keyword-call - {:from [l/Bound Expr] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context j/JVM-Seed t/Kw] :body true} - [supply ctx seed key target] - (let [plain {:hint nil :var-meta {}} [supply id] (fresh "keyword-site" supply) - site (expression ctx plain {:op :host :operation {:op :keyword-site :key key} :args []}) - local {:id id :name id :mutable? false :external? false :jvm (:jvm site)} - read (expression ctx plain {:op :local :local local}) - snapshot (expression ctx plain {:op :host :operation {:op :instance :class com.tailrecursion.blacktide.runtime.KeywordSite - :member 'snapshot :mode :method :tags []} :args [read]})] - ;; The first argument snapshots the cache before the target runs. - ;; An IFn call retains KeywordInvokeExpr's unknown result type. - [supply (expression ctx seed {:op :let :bindings [{:local local :init site}] - :body (expression ctx seed {:op :invoke :function read :args [snapshot target]})})])) - -(np/defpass parse-call - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [inner (assoc ctx :statement? false :tail? false) - [supply function] (parse-operand supply inner (first forms)) - arity (dec (count forms)) - direct (facts/direct-call arity function) - metadata (:var-meta (:jvm function)) - primitive (when-not direct (facts/primitive-interface arity (:arglists metadata))) - hint (or (jvm/source-tag (:form (:site ctx))) - (jvm/source-tag (facts/signature arity (:arglists metadata))) - (when (and (not direct) (not primitive) (= :global (:op (:node function)))) (:hint (:jvm function))) - (:tag metadata)) - [supply args] (parse-forms supply inner (vec (rest forms))) - class-arg (some-> args first :node) - node (cond - (and (= :global (:op (:node function))) (= 'clojure.core/instance? (:name (:node function))) - (= 2 (count args)) (= :literal (:op class-arg)) (class? (:value class-arg))) - {:op :host :operation {:op :instanceof :class (:value class-arg)} :args [(nth args 1)]} - direct {:op :host :operation direct :args args} - primitive {:op :host :operation {:op :instance :class primitive :member 'invokePrim :mode :method :tags nil} - :args (into [function] args)} - :else {:op :invoke :function function :args args})] - (let [method (when-not (or direct primitive) (facts/protocol-call arity function)) - fnode (:node function) key (:form (:site function))] - (cond - (and method (= :global (:op fnode))) - (protocol-call supply ctx {:hint hint :var-meta {}} method (:name fnode) args) - (and (= :literal (:op fnode)) (keyword? key) (= 1 arity) - (effects (nth args 0))) - (keyword-call supply ctx {:hint hint :var-meta {}} key (nth args 0)) - :else [supply (expression ctx {:hint hint :var-meta {}} node)])))) - -(np/defpass parse-host - {:from [j/JVM Syntax] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx syntax] - (let [[supply args] (parse-forms supply (assoc ctx :statement? false :tail? false) (:operands syntax))] - [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} - {:op :host :operation (:operation syntax) :args args})])) - -(np/defpass parse-assignment - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context (t/U ':setq ':psetq)] :body true} - [supply ctx kind forms] - (if (even? (count forms)) - (let [[supply pairs] - (reduce (fn [^{::t/- (t/HVec [l/Bound-Supply l/Bound-Assignments])} acc ^{::t/- (t/Seq t/Any)} pair] - (let [[supply pairs] acc target (first pair)] - (when-not (simple-symbol? target) - (errors/fail :invalid-assignment "Assignment targets must be simple symbols" {:binding target} (:site ctx))) - (let [local (or (get (:locals ctx) target) - (errors/fail :unknown-assignment "Expected a mutable loophole local" {:binding target} (:site ctx)))] - (when-not (:mutable? local) - (errors/fail :immutable-assignment "Cannot assign to an immutable local" {:binding target} (:site ctx))) - (let [[supply value] (parse-operand supply ctx (second pair))] - [supply (conj pairs {:target local :value value})])))) [supply []] (partition 2 forms))] - [supply (expression ctx {:hint (jvm/source-tag (:form (:site ctx))) :var-meta {}} - (if (= kind :setq) {:op :setq :pairs pairs} {:op :psetq :pairs pairs}))]) - (errors/fail :invalid-assignment "setq/psetq requires symbol/value pairs" {} (:site ctx)))) - -(np/defpass parse-marker - {:from [l/Bound Forms] :to [l/Bound Expr] :args [l/Bound-Context (t/U ':label ':goto)] :body true} - [ctx kind forms] - (let [name (second forms)] - (when-not (and (= 2 (count forms)) (simple-symbol? name)) - (errors/fail :invalid-marker "label/goto requires one simple symbol" {:label name} (:site ctx))) - (when-not (and (:statement? ctx) (:transfer? ctx) (or (= kind :goto) (:outer? ctx))) - (errors/fail :marker-position "Labels require the outer body; goto requires a statement outside operands and initializers" - {:operator kind :label name} (:site ctx))) - (expression ctx {:hint nil :var-meta {}} - (if (= kind :label) {:op :label :name name} - {:op :goto :name name})))) - -(np/defpass parse-list - {:from [l/Bound Forms] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx forms] - (let [op (first forms) form (:form (:site ctx)) resolved (resolve-name ctx op) - marker (when (var? resolved) (get (:markers ctx) resolved)) - _ (when (var? resolved) (errors/check-visibility resolved (:site ctx)))] - (cond - (= :loophole marker) - (errors/fail :unsupported-form "Nested loophole regions are unsupported" {:operator op} (:site ctx)) - (= :setq marker) (parse-assignment supply ctx :setq (vec (rest forms))) - (= :psetq marker) (parse-assignment supply ctx :psetq (vec (rest forms))) - (= :label marker) [supply (parse-marker ctx :label forms)] - (= :goto marker) [supply (parse-marker ctx :goto forms)] - (= op 'quote) - (if (= 2 (count forms)) - [supply (expression ctx {:hint nil :var-meta {}} {:op :literal :value (second forms)})] - (errors/fail :invalid-quote "quote requires one argument" {} (:site ctx))) - (= op 'var) - (let [name (second forms) resolved (when (symbol? name) (ns-resolve *ns* name))] - (if (and (= 2 (count forms)) (var? resolved)) - [supply (expression ctx {:hint nil :var-meta {}} - {:op :var :name (.toSymbol ^clojure.lang.Var resolved)})] - (errors/fail :invalid-var "var requires a resolvable Var name" {} (:site ctx)))) - (#{'fn* 'letfn* 'try 'set! 'monitor-enter 'monitor-exit} op) - (errors/fail :unsupported-form "Form is unsupported inside loophole; place it outside the region" {:operator op} (:site ctx)) - (#{'new '.} op) (parse-host supply ctx (or (host/syntax ctx forms) - (errors/fail :invalid-interop "Missing host syntax" {} (:site ctx)))) - (= op 'do) (parse-body supply ctx (vec (rest forms))) - (= op 'let*) (parse-scope supply ctx false forms) - (= op 'loop*) (parse-loop supply ctx forms) - (= op 'recur) (parse-recur supply ctx forms) - (= op 'case*) (parse-dispatch supply ctx forms) - (and (= resolved #'clojure.core/case) (< (count forms) 2)) - (errors/fail :invalid-case "case requires a dispatch expression" {} (:site ctx)) - (= resolved #'clojure.core/case) (parse-case supply ctx forms) - (= op 'if) - (if (<= 3 (count forms) 4) - (let [[supply test] (parse-operand supply ctx (second forms)) - [supply yes] (parse supply (assoc ctx :outer? false) (nth forms 2)) - [supply no] (parse supply (assoc ctx :outer? false) (get forms 3))] - [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :if :test test :then yes :else no})]) - (errors/fail :invalid-if "if requires a test, then, and optional else" {} (:site ctx))) - (= op 'throw) - (if (= 2 (count forms)) - (let [[supply value] (parse-operand supply ctx (second forms))] - [supply (expression ctx {:hint nil :var-meta {}} {:op :throw :value value})]) - (errors/fail :invalid-throw "throw requires one value" {} (:site ctx))) - (and (symbol? op) (special-symbol? op)) - (errors/fail :unsupported-form "Special form is not supported inside loophole" {:operator op} (:site ctx)) - (and (var? resolved) (:macro (meta resolved))) - (let [expanded (.applyTo ^clojure.lang.Var resolved (cons form (cons (:macro-env ctx) (rest forms)))) - expanded (if (instance? clojure.lang.IObj expanded) - (with-meta expanded (merge (meta expanded) (meta form))) expanded)] - (parse supply ctx expanded)) - (and (var? resolved) (ifn? (:inline (meta resolved))) - (let [arity (:inline-arities (meta resolved))] (or (nil? arity) (and (ifn? arity) (.invoke ^clojure.lang.IFn arity (dec (count forms))))))) - (let [inline (:inline (meta resolved)) - expanded (if (ifn? inline) (.applyTo ^clojure.lang.IFn inline (rest forms)) - (errors/fail :invalid-expression "Invalid inline expander" {} (:site ctx))) - expanded (if (instance? clojure.lang.IObj expanded) - (with-meta expanded (merge (meta expanded) (meta form))) expanded)] - (parse supply ctx expanded)) - :else (if-let [syntax (host/syntax ctx forms)] - (parse-host supply ctx syntax) - (parse-call supply ctx forms))))) - -(np/defpass parse-collection - {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx form] - (if (or (vector? form) (map? form) (set? form)) - (let [kind (cond (vector? form) :vector (map? form) :map :else :set) - ;; Freeze source traversal before alpha-renaming changes element hashes. - items (if (map? form) (vec (mapcat identity form)) (vec form)) - inner (assoc ctx :statement? false :tail? false) - [supply items] (parse-forms supply inner items) - [supply metadata] (if (some? (meta form)) (parse-operand supply inner (meta form)) [supply nil])] - [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} - {:op :collection :kind kind :items items :metadata metadata})]) - (errors/fail :invalid-expression "Expected a collection" {} (:site ctx)))) - -(np/defpass parse - {:from [l/Bound Raw] :to (t/HVec [l/Bound-Supply l/Bound-Expr]) - :args [l/Bound-Supply l/Bound-Context] :body true} - [supply ctx form] - (let [ctx (assoc ctx :site (errors/site (:site ctx) form))] - (cond - (symbol? form) [supply (parse-symbol ctx form)] - (and (seq? form) (seq form)) (parse-list supply ctx (vec form)) - (or (vector? form) (map? form) (set? form)) - (parse-collection supply ctx form) - :else [supply (expression ctx {:hint (jvm/source-tag form) :var-meta {}} {:op :literal :value form})]))) diff --git a/src/com/tailrecursion/blacktide/compiler/source_languages.clj b/src/com/tailrecursion/blacktide/compiler/source_languages.clj deleted file mode 100644 index fc43c9c..0000000 --- a/src/com/tailrecursion/blacktide/compiler/source_languages.clj +++ /dev/null @@ -1,93 +0,0 @@ -(ns com.tailrecursion.blacktide.compiler.source-languages - "Bound source separates executable children from source/provenance data. - Raw forms, macro environments and JVM hints are external compiler inputs." - (:require [com.tailrecursion.nanopass :as np] - [typed.clojure :as-alias t] - [com.tailrecursion.blacktide.compiler.jvm-languages :as j])) - -(np/deflanguage Bound - {Raw t/Any - Forms (t/Vec Raw) - Site (t/HMap :mandatory {:form Raw :file (t/Option t/Str) - :line (t/Option t/Int) :column (t/Option t/Int)} :complete? true) - Jvm j/JVM-Fact - Local (t/HMap :mandatory {:id t/Sym :name t/Sym :mutable? t/Bool :external? t/Bool - :jvm Jvm} :complete? true) - Binding (t/HMap :mandatory {:local Local :init Expr} :complete? true) - Bindings (t/Vec Binding) - Assignment (t/HMap :mandatory {:target Local :value Expr} :complete? true) - Assignments (t/Vec Assignment) - RecurTarget (t/HMap :mandatory {:id t/Sym :bindings (t/Vec Local)} :complete? true) - CaseSpec (t/HMap :mandatory {:shift t/Int :mask t/Int :default t/Int - :tests (t/Map t/Int (t/HVec [Raw t/Int])) - :switch (t/U ':compact ':sparse) - :test (t/U ':int ':hash-equiv ':hash-identity) - :skip (t/Option (t/Set t/Int))} :complete? true) - CaseSyntax (t/HMap :mandatory {:selector Raw :arms Forms} :complete? true) - DispatchSyntax (t/HMap :mandatory {:value Raw :spec CaseSpec :arms Forms} :complete? true) - Exprs (t/Vec Expr) - CollectionKind (t/U ':vector ':map ':set) - Expr (t/HMap :mandatory {:site Site :jvm Jvm :node Node} :complete? true) - Node (t/U (t/HMap :mandatory {:op ':literal :value Raw} :complete? true) - (t/HMap :mandatory {:op ':local :local Local} :complete? true) - (t/HMap :mandatory {:op ':global :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':var :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':host :operation j/JVM-Operation :args (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':invoke :function Expr :args (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':collection :kind CollectionKind :items Exprs - :metadata (t/Option Expr)} :complete? true) - (t/HMap :mandatory {:op ':setq :pairs Assignments} :complete? true) - (t/HMap :mandatory {:op ':psetq :pairs Assignments} :complete? true) - (t/HMap :mandatory {:op ':if :test Expr :then Expr :else Expr} :complete? true) - (t/HMap :mandatory {:op ':case :selector Expr :arms Exprs} :complete? true) - (t/HMap :mandatory {:op ':dispatch :value Expr :spec CaseSpec} :complete? true) - (t/HMap :mandatory {:op ':throw :value Expr} :complete? true) - (t/HMap :mandatory {:op ':label :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':goto :name t/Sym} :complete? true) - (t/HMap :mandatory {:op ':do :forms (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':let :bindings (t/Vec Binding) :body Expr} :complete? true) - (t/HMap :mandatory {:op ':loop :id t/Sym :bindings (t/Vec Binding) :body Expr} :complete? true) - (t/HMap :mandatory {:op ':recur :target RecurTarget :args (t/Vec Expr)} :complete? true)) - Supply (t/HMap :mandatory {:prefix t/Str :next t/Int} :complete? true) - Context (t/HMap :mandatory {:locals (t/Map t/Sym Local) :macro-env (t/Map t/Sym Raw) - :markers (t/Map t/AnyVar t/Kw) :outer? t/Bool :transfer? t/Bool - :site Site - :recur (t/Option RecurTarget) :statement? t/Bool :tail? t/Bool} :complete? true)}) - -(np/derive-language Native Bound - {:remove {Node #{:setq :psetq :label :goto}}}) - -(np/derive-language Classified Bound - {:add {Node [(t/HMap :mandatory {:op ':native :value com.tailrecursion.blacktide.compiler.source-languages/Native-Expr} - :complete? true)]}}) - -(np/derive-language Sequential Classified - {:remove {Node #{:setq}} - :add {Node [(t/HMap :mandatory {:op ':write :target Local :value Expr} :complete? true)]}}) - -(np/derive-language Assigned Sequential - {:remove {Node #{:psetq}}}) - -(np/derive-language Cases Assigned - {:remove {Node #{:case}} - :add {Node [(t/HMap :mandatory {:op ':select :selector Expr :arms Exprs} :complete? true)]}}) - -(np/derive-language Collections Cases - {:remove {Node #{:collection}} - :add {Node [(t/HMap :mandatory {:op ':build :kind CollectionKind :items Exprs :unique? t/Bool - :metadata (t/Option Expr)} :complete? true)]}}) - -(np/derive-language Prepared Collections - {:remove {Node #{:host}} - :add {Node [(t/HMap :mandatory {:op ':host-call :operation j/JVM-Operation :args (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':convert :conversion j/JVM-Conversion :value Expr} :complete? true)]}}) - -(np/derive-language Ordered Prepared - {:remove {Node #{:invoke :build :host-call :convert :recur :dispatch}} - :add {Node [(t/HMap :mandatory {:op ':convert :conversion j/JVM-Conversion :value Local} :complete? true) - (t/HMap :mandatory {:op ':dispatch :value Local :spec CaseSpec} :complete? true) - (t/HMap :mandatory {:op ':recur :target RecurTarget :args (t/Vec Local)} :complete? true) - (t/HMap :mandatory {:op ':host-call :operation j/JVM-Operation :args (t/Vec Local)} :complete? true) - (t/HMap :mandatory {:op ':invoke :function Local :args (t/Vec Local)} :complete? true) - (t/HMap :mandatory {:op ':build :kind CollectionKind :items (t/Vec Local) :unique? t/Bool} :complete? true) - (t/HMap :mandatory {:op ':metadata :value Local :metadata Local} :complete? true)]}}) diff --git a/src/com/tailrecursion/blacktide/compiler/source_values.clj b/src/com/tailrecursion/blacktide/compiler/source_values.clj deleted file mode 100644 index 52713ab..0000000 --- a/src/com/tailrecursion/blacktide/compiler/source_values.clj +++ /dev/null @@ -1,36 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.source-values - "Restore primitive source-local reads before compiler temporaries exist. - Clojure boxes each Object use of a primitive local independently; a machine - slot must not turn those reads into aliases of one box. Native trees already - preserve this behavior, and mutable locals have reference semantics." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.source-languages :as s] - [typed.clojure :as-alias t])) - -(declare expression forms binding-form pair pairs) - -(np/defpass node - {:from [s/Classified Node] :to [s/Classified Node] - :processors {Expr expression Exprs forms Binding binding-form Assignments pairs - Local clojure.core/identity Raw clojure.core/identity - CollectionKind clojure.core/identity RecurTarget clojure.core/identity CaseSpec clojure.core/identity}} - [node] - :native node) - -(np/defpass binding-form {:from [s/Classified Binding] :to [s/Classified Binding] - :processors {Expr expression Local clojure.core/identity}} [binding]) -(np/defpass pair {:from [s/Classified Assignment] :to [s/Classified Assignment] - :processors {Expr expression Local clojure.core/identity}} [assignment]) -(np/defpass pairs {:from [s/Classified Assignments] :to [s/Classified Assignments] - :processors {Assignment pair}} [assignments]) -(np/defpass forms {:from [s/Classified Exprs] :to [s/Classified Exprs] :processors {Expr expression}} [exprs]) - -(np/defpass expression {:from [s/Classified Expr] :to [s/Classified Expr] :body true} - [expr] - (let [expr (assoc expr :node (node (:node expr))) - type (:type (:jvm expr))] - (if (and (= :local (:op (:node expr))) (:primitive? (:jvm expr)) - (class? type) (.isPrimitive ^Class type) (not= Void/TYPE type)) - (assoc expr :node {:op :host :operation {:op :static :class clojure.lang.RT - :member (symbol (str (.getName ^Class type) "Cast")) :tags [type]} - :args [expr]}) expr))) diff --git a/src/com/tailrecursion/blacktide/compiler/structure.clj b/src/com/tailrecursion/blacktide/compiler/structure.clj deleted file mode 100644 index 5a98727..0000000 --- a/src/com/tailrecursion/blacktide/compiler/structure.clj +++ /dev/null @@ -1,163 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.structure - "Recover acyclic control or a single-header loop with bounded duplication. - Construction records block identities to preserve native construction and - cache sites. Dead native code keeps the dispatcher for source validation." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] - [com.tailrecursion.blacktide.compiler.structured-languages :as s] - [com.tailrecursion.blacktide.compiler.graph :as graph] - [typed.clojure :as-alias t])) - -(declare edge body) - -(np/defpass block - {:from [l/Machine Label] :to [l/Machine Block] :args [l/Machine-Program] :body true} - [machine id] - (or (get (:blocks machine) id) (throw (IllegalStateException. "Missing checked block")))) - -(np/defpass scan - {:from [l/Machine Label] :to [s/Joined Scan] - :args [l/Machine-Program (t/Set l/Machine-Label) s/Joined-Scan] :body true} - [machine path state id] - (let [[seen headers] state] - (cond (contains? path id) [seen (conj headers id)] - (contains? seen id) state - :else (reduce (fn [^{::t/- s/Joined-Scan} state ^{::t/- l/Machine-Label} next] - (scan machine (conj path id) state next)) - [(conj seen id) headers] - (graph/successors (:term (block machine id))))))) - -(np/defpass harmless-atom? {:from [l/Machine Atom] :to t/Bool} - [atom] - :local true - :literal (nil? (:value atom))) - -(np/defpass harmless-block? {:from [l/Machine Block] :to t/Bool :body true} - [block] - ;; Lowering leaves nil result copies after transfers. No host/native form or - ;; arbitrary quoted constant may disappear before Clojure validates it. - (and (every? (fn [op] (let [rhs (:value op)] - (and (= :value (:op rhs)) (harmless-atom? (:value rhs))))) (:ops block)) - (let [term (:term block)] - (case (:op term) - :jump true - (:return :throw) (harmless-atom? (:value term)) - (:branch :switch) (harmless-atom? (:test term)))))) - -(np/defpass wrap - {:from [l/Machine Block] :to [s/Joined Expr] :args [s/Joined-Expr] :body true} - [end block] - (reduce (fn [^{::t/- s/Joined-Expr} body ^{::t/- l/Machine-Operation} op] - {:op :bind :binding op :body body}) end (reverse (:ops block)))) - -(np/defpass arms - {:from [l/Machine Term] :to [s/Joined Arms] :args [s/Joined-Context t/Bool t/Int] :body true} - [ctx inside? budget term] - (if (= :switch (:op term)) - ((fn ^{::t/- [t/Int t/Int (t/Vec s/Joined-Expr) (t/Vec l/Machine-Label) :-> s/Joined-Arms]} build [index budget out blocks] - (if (= index (count (:targets term))) {:op :built :budget budget :arms out :blocks blocks} - (let [result (edge ctx inside? budget (nth (:targets term) index))] - (if (= :built (:op result)) - (recur (inc index) (:budget result) (conj out (:expr result)) (into blocks (:blocks result))) result)))) 0 budget [] []) - {:op :exhausted})) - -(np/defpass terminator - {:from [l/Machine Term] :to [s/Joined Build] :args [s/Joined-Context t/Bool t/Int]} - [ctx inside? budget term] - :jump (edge ctx inside? budget (:target term)) - :return {:op :built :budget budget :blocks [] :expr term} - :throw {:op :built :budget budget :blocks [] :expr term} - :switch (let [result (arms ctx inside? budget term)] - (if (= :built (:op result)) - {:op :built :budget (:budget result) :blocks (:blocks result) :expr {:op :switch :test (:test term) :arms (:arms result)}} result)) - :branch (let [yes (edge ctx inside? budget (:yes term))] - (if (= :exhausted (:op yes)) yes - (let [no (edge ctx inside? (:budget yes) (:no term))] - (if (= :exhausted (:op no)) no - {:op :built :budget (:budget no) :blocks (into (:blocks yes) (:blocks no)) - :expr {:op :branch :test (:test term) :yes (:expr yes) :no (:expr no)}}))))) - -(np/defpass joined - {:from [a/Planned Join] :to [s/Joined Build] :args [s/Joined-Context t/Bool t/Int l/Machine-Term] :body true} - [ctx inside? budget term join] - (let [inner (assoc ctx :stop [(:target join) (:result join)]) - result (terminator inner inside? budget term)] - (if (= :built (:op result)) - (let [tail (edge ctx inside? (:budget result) (:target join))] - (if (= :built (:op tail)) - (assoc tail :blocks (into (:blocks result) (:blocks tail)) - :expr {:op :join :target (:result join) :value (:expr result) :body (:expr tail)}) tail)) - {:op :exhausted}))) - -(np/defpass body - {:from [l/Machine Label] :to [s/Joined Build] :args [s/Joined-Context t/Bool t/Int] :body true} - [ctx inside? budget id] - (let [block (block (:machine ctx) id) - budget (- budget (inc (count (:ops block))))] - (if (neg? budget) {:op :exhausted} - (let [result (if-let [join (get (:joins ctx) id)] - (joined ctx inside? budget (:term block) join) - (terminator ctx inside? budget (:term block)))] - (if (= :built (:op result)) (assoc result :blocks (conj (:blocks result) id) :expr (wrap (:expr result) block)) result))))) - -(np/defpass edge - {:from [l/Machine Label] :to [s/Joined Build] :args [s/Joined-Context t/Bool t/Int] :body true} - [ctx inside? budget id] - (let [stop (:stop ctx)] - (cond - (and stop (= id (first stop))) - {:op :built :budget budget :blocks [] :expr {:op :return :value {:op :local :id (second stop)}}} - (= id (:header ctx)) - (if inside? (if (pos? budget) {:op :built :budget (dec budget) :blocks [] :expr {:op :recur :slots (:slots ctx)}} - {:op :exhausted}) - (let [result (body ctx true (dec budget) id)] - (if (= :built (:op result)) (assoc result :expr {:op :loop :slots (:slots ctx) :body (:expr result)}) result))) - :else (body ctx inside? budget id)))) - -(np/defpass construction-site? - {:from [l/Machine Rhs] :to t/Bool :body true} - [rhs] - (case (:op rhs) - :native true - :convert (let [conversion (:conversion rhs)] - (and (:parameter? conversion) - (.isAnnotationPresent ^Class (:type conversion) java.lang.FunctionalInterface))) - :host (let [operation (:operation rhs)] - (case (:op operation) - (:keyword-site :method-value) true - :adapt (.isAnnotationPresent ^Class (:class operation) java.lang.FunctionalInterface) - :protocol (boolean (some (fn [^Class type] (.isAnnotationPresent type java.lang.FunctionalInterface)) - (:parameters (:method operation)))) - false)) - false)) - -(np/defpass site-failure - {:from [l/Machine Program] :to (t/Option ':native-sites) - :args [(t/Vec l/Machine-Label)] :body true} - [blocks machine] - (let [copies (frequencies blocks) - repeated (mapv (fn [[id _]] (block machine id)) (filter (fn [[_ n]] (> n 1)) copies))] - (when (some (fn [block] - (some (fn [operation] (construction-site? (:value operation))) - (:ops block))) repeated) - :native-sites))) - -(np/defpass recover {:from [a/Planned Program] :to [s/Joined Decision] :body true} - [analysis] - (let [machine (:machine analysis) reachable (:reachable analysis) - [_ headers] (scan machine #{} [#{} #{}] (:entry machine)) - dead (apply dissoc (:blocks machine) reachable) - header (first headers)] - (cond - (> (count headers) 1) {:op :fallback :reason :cycles} - (not (every? harmless-block? (vals dead))) {:op :fallback :reason :validation} - :else - (let [slots (if header (vec (sort (get (:live machine) header #{}))) []) - ctx {:machine machine :header header :slots slots :joins (:joins analysis) :stop nil} - budget (* 4 (reduce + 0 (map #(inc (count (:ops %))) (vals (select-keys (:blocks machine) reachable))))) - result (edge ctx false budget (:entry machine))] - (if (= :built (:op result)) - (if-let [reason (site-failure (:blocks result) machine)] - {:op :fallback :reason reason} {:op :structured :body (:expr result)}) - {:op :fallback :reason :budget}))))) diff --git a/src/com/tailrecursion/blacktide/compiler/structured_emit.clj b/src/com/tailrecursion/blacktide/compiler/structured_emit.clj deleted file mode 100644 index c0c1e5a..0000000 --- a/src/com/tailrecursion/blacktide/compiler/structured_emit.clj +++ /dev/null @@ -1,110 +0,0 @@ -(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.structured-emit - "Emit lexical control with proven storage classes. Consumer source types stay - distinct from storage; Java argument conversions remain explicit evaluations." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.structured-languages :as s] - [com.tailrecursion.blacktide.compiler.compact-languages :as c] - [com.tailrecursion.blacktide.compiler.compact :as compact] - [com.tailrecursion.blacktide.compiler.collections :as collections] - [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [typed.clojure :as-alias t] - [com.tailrecursion.blacktide.compiler.emit :as emit] - [com.tailrecursion.blacktide.compiler.host-emit :as host] - [com.tailrecursion.blacktide.compiler.case-emit :as cases])) - -(np/defpass primitive-form - {:from [l/Machine Code] :to [l/Machine Code] :args [java.lang.Class] :body true} - [^Class type form] - ;; The parameter conversion is exactly the conversion previously performed by - ;; RT.box(type); the matching primitive cast overload itself is an identity. - (list (with-meta (symbol "clojure.lang.RT" (str (.getName type) "Cast")) {:param-tags [type]}) form)) - -(np/defpass binding-form - {:from [l/Machine Code] :to [l/Machine Code] :args [s/Stored-Types t/Sym (t/Option java.lang.Class)] :body true} - [types id actual form] - (if-let [type (get types id)] (if (= type actual) form (primitive-form type form)) (host/box-form form))) - -(declare rhs-form) -(np/defpass atom-form {:from [c/Compact Atom] :to [l/Machine Code] :args [c/Compact-Types]} - [types node] - :local (:id node) - :literal (list 'quote (:value node)) - :inline (rhs-form types (:value node))) - -(np/defpass operand-form - {:from [c/Compact Operand] :to [l/Machine Code] :args [c/Compact-Types] :body true} - [types operand] - (let [form (atom-form types (:value operand)) actual (compact/atom-type types (:value operand)) - fact (:jvm operand)] - (if (and (class? actual) (= actual (:type fact))) form - (host/typed-form (assoc fact :primitive? false) (if (class? actual) (host/box-form form) form))))) - -(np/defpass rhs-form - {:from [c/Compact Rhs] :to [l/Machine Code] :args [c/Compact-Types]} - [types rhs] - :host (host/host-form (mapv #(operand-form types %) (:args rhs)) (:operation rhs)) - :dispatch (cases/selector-form (operand-form types (:value rhs)) (:spec rhs)) - :convert (let [conversion (:conversion rhs) operand (:value rhs)] - (if (and (:parameter? conversion) (.isPrimitive ^Class (:type conversion))) - (if (= (:type conversion) (compact/atom-type types (:value operand)) (:type (:jvm operand))) - (atom-form types (:value operand)) - (primitive-form (:type conversion) (operand-form types operand))) - (host/conversion-form conversion (:jvm operand) (atom-form types (:value operand))))) - :value (atom-form types (:value rhs)) - :global (emit/rhs-form rhs) :external (emit/rhs-form rhs) :var (emit/rhs-form rhs) :native (emit/rhs-form rhs) - :construct (collections/form (mapv #(atom-form types %) (:items rhs)) (:unique? rhs) (:kind rhs)) - :metadata (let [value (gensym "value__") metadata (gensym "metadata__")] - (list 'let* [(with-meta value {:tag 'clojure.lang.IObj}) (atom-form types (:value rhs)) - (with-meta metadata {:tag 'clojure.lang.IPersistentMap}) (atom-form types (:metadata rhs))] - (list '. value 'withMeta metadata))) - :invoke (list* (atom-form types (:function rhs)) (mapv #(atom-form types %) (:args rhs)))) - -(np/defpass rhs-type - {:from [c/Compact Rhs] :to (t/Option java.lang.Class) :args [c/Compact-Types] :body true} - [types rhs] - (case (:op rhs) - :value (compact/atom-type types (:value rhs)) - :convert (when (:parameter? (:conversion rhs)) (:type (:conversion rhs))) - :host (let [type (jvm/host-type (mapv #(:type (:jvm %)) (:args rhs)) (:operation rhs))] - (when (and (class? type) (not (#{:adapt} (:op (:operation rhs))))) type)) - ;; Other producers retain an explicit storage conversion. - nil)) - -(np/defpass operation-form - {:from [c/Compact Operation] :to [l/Machine Code] :args [c/Compact-Types] :body true} - [types op] - (binding-form types (:target op) (rhs-type types (:value op)) (rhs-form types (:value op)))) - -(declare expression) -(np/defpass let-form {:from [c/Compact Expr] :to [l/Machine Code] :args [c/Compact-Types] :body true} - [types node] - ((fn ^{::t/- [(t/Vec t/Any) c/Compact-Expr :-> l/Machine-Code]} bindings [pairs node] - (case (:op node) - :bind (let [op (:binding node)] - (recur (conj pairs (:target op) (operation-form types op)) (:body node))) - :join (recur (conj pairs (:target node) - (binding-form types (:target node) nil (expression types (:value node)))) (:body node)) - (list 'let* pairs (expression types node)))) [] node)) - -(np/defpass expression {:from [c/Compact Expr] :to [l/Machine Code] :args [c/Compact-Types]} - [types node] - :bind (let-form types node) - :effect (let [binding (:binding node) rhs (:value binding) type (get types (:target binding)) - form (rhs-form types rhs)] - (list 'do (if (and type (not= type (rhs-type types rhs))) (primitive-form type form) form) - (expression types (:body node)))) - :join (let-form types node) - :branch (list 'if (atom-form types (:test node)) (expression types (:yes node)) (expression types (:no node))) - :switch (cases/arms-form (mapv #(expression types %) (:arms node)) (atom-form types (:test node))) - :loop (list 'loop* (vec (mapcat (fn [binding] - [(:target binding) (binding-form types (:target binding) - (compact/atom-type types (:value binding)) - (atom-form types (:value binding)))]) (:bindings node))) - (expression types (:body node))) - :recur (list* 'recur (mapv #(atom-form types %) (:args node))) - :return (let [value (:value node) form (atom-form types value)] - ;; Tail compaction must retain the region's opaque result type. - (if (and (= :inline (:op value)) (= :native (:op (:value value)))) - (host/box-form form) form)) - :throw (list 'throw (atom-form types (:value node)))) diff --git a/src/com/tailrecursion/blacktide/compiler/structured_languages.clj b/src/com/tailrecursion/blacktide/compiler/structured_languages.clj deleted file mode 100644 index 9fbdf88..0000000 --- a/src/com/tailrecursion/blacktide/compiler/structured_languages.clj +++ /dev/null @@ -1,34 +0,0 @@ -(ns com.tailrecursion.blacktide.compiler.structured-languages - "Lexical control trees contain at most one loop target. General graph shapes remain - machines; bounded construction returns an explicit fallback, never partial code." - (:require [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.languages :as l] - [com.tailrecursion.blacktide.compiler.analysis-languages :as a] - [typed.clojure :as-alias t])) - -(np/deflanguage Structured - {Expr (t/U (t/HMap :mandatory {:op ':bind :binding l/Machine-Operation :body Expr} :complete? true) - (t/HMap :mandatory {:op ':branch :test l/Machine-Atom :yes Expr :no Expr} :complete? true) - (t/HMap :mandatory {:op ':switch :test l/Machine-Atom :arms (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':loop :slots (t/Vec t/Sym) :body Expr} :complete? true) - (t/HMap :mandatory {:op ':recur :slots (t/Vec t/Sym)} :complete? true) - (t/HMap :mandatory {:op ':return :value l/Machine-Atom} :complete? true) - (t/HMap :mandatory {:op ':throw :value l/Machine-Atom} :complete? true)) - Build (t/U (t/HMap :mandatory {:op ':built :budget t/Int :blocks (t/Vec l/Machine-Label) :expr Expr} :complete? true) - (t/HMap :mandatory {:op ':exhausted} :complete? true)) - Arms (t/U (t/HMap :mandatory {:op ':built :budget t/Int :blocks (t/Vec l/Machine-Label) :arms (t/Vec Expr)} :complete? true) - (t/HMap :mandatory {:op ':exhausted} :complete? true)) - Scan (t/HVec [(t/Set l/Machine-Label) (t/Set l/Machine-Label)]) - Fallback (t/U ':cycles ':validation ':budget ':native-sites) - Decision (t/U (t/HMap :mandatory {:op ':structured :body Expr} :complete? true) - (t/HMap :mandatory {:op ':fallback :reason Fallback} :complete? true))}) - -(np/derive-language Joined Structured - {:add {Expr [(t/HMap :mandatory {:op ':join :target t/Sym :value Expr :body Expr} :complete? true)]} - :replace {Context (t/HMap :mandatory {:machine l/Machine-Program :header (t/Option l/Machine-Label) - :slots (t/Vec t/Sym) :joins a/Planned-Joins - :stop (t/Option (t/HVec [l/Machine-Label t/Sym]))} :complete? true)}}) - -(np/derive-language Stored Joined - {:replace {Types (t/Map t/Sym java.lang.Class) - Program (t/HMap :mandatory {:body Expr :types Types} :complete? true)}}) diff --git a/src/com/tailrecursion/nanopass.clj b/src/com/tailrecursion/nanopass.clj index 58ff10c..1c609db 100644 --- a/src/com/tailrecursion/nanopass.clj +++ b/src/com/tailrecursion/nanopass.clj @@ -41,12 +41,8 @@ [& languages] `(do ~@(language/type-forms (map description languages)))) -(defmacro defpass - "Define a checked pass. :from and :to identify [language nonterminal]; :to may - instead be a result type. :args types the leading context parameters. Supply - tag/expression clauses; unchanged productions get structural recursion through - :processors (nonterminal -> function). :body true accepts one explicit body." - [name {:keys [from to args processors body] :as options} arguments & clauses] +(defn- pass-definition + [locals form name {:keys [from to args processors body] :as options} arguments clauses] (when-not (and (simple-symbol? name) (vector? from) (= 2 (count from)) (symbol? (first from)) (simple-symbol? (second from)) (contains? options :to) @@ -60,19 +56,50 @@ (= (count arguments) (count (set arguments))) (= (count arguments) (inc (count args))) (every? #{:from :to :args :processors :body} (keys options))) - (language/fail :invalid-pass &form "Invalid pass name, signature, or options")) + (language/fail :invalid-pass form "Invalid pass name, signature, or options")) (let [source (description (first from)) target (when (vector? to) (description (first to))) result-type (if target (language/type-name target (second to)) (language/qualify *ns* #{} to))] (with-meta (pass/definition - {:name (symbol (str (ns-name *ns*)) (str name)) + {:name (if (locals name) name (symbol (str (ns-name *ns*)) (str name))) :source source :target target :from (second from) :to (when target (second to)) :params (mapv #(language/qualify *ns* #{} %) args) :arguments arguments :result-type result-type - :processors (into {} (map (fn [[nt processor]] [nt (language/qualify *ns* #{} processor)])) processors) + :processors (into {} (map (fn [[nt processor]] [nt (if (locals processor) processor (language/qualify *ns* #{} processor))])) processors) :body? body} - clauses) (meta &form)))) + clauses) (meta form)))) + +(defmacro defpass + "Define a checked pass. :from and :to identify [language nonterminal]; :to may + instead be a result type. :args types the leading context parameters. Supply + tag/expression clauses; unchanged productions get structural recursion through + :processors (nonterminal -> function). :body true accepts one explicit body." + [name options arguments & clauses] + (pass-definition #{} &form name options arguments clauses)) + +(defmacro defpasses + "Define mutually recursive processors as one lexical letfn group. Each entry + has the defpass syntax without its macro name. All functions retain their + individual checker signatures; recursion uses locals, not forward Vars." + [& definitions] + (let [names (map first definitions) locals (set names)] + (when-not (and (seq definitions) (every? simple-symbol? names) + (= (count names) (count locals))) + (language/fail :invalid-pass &form "Expected distinct processor names")) + ;; Typed Clojure 1.3 collects letfn bindings in a map before matching its + ;; positional annotations. Emit that same order, including groups over eight. + (let [functions (vals (into {} (map (fn [[name options arguments & clauses :as form]] + [name (pass-definition locals form name options arguments clauses)])) definitions)) + signatures (mapv #(second (::signature (nth % 2))) functions)] + `(letfn [~@(map (fn [[_ name _ args body :as form]] + (with-meta (list name args body) (meta form))) functions)] + ;; Typed Clojure's letfn annotation is an inert quoted vector, in + ;; binding order. It imposes no checker dependency at runtime. + '~signatures + ~@(map (fn [[_ name attributes args :as form]] + `(def ~(with-meta name (assoc (merge (meta form) attributes) + :arglists (list 'quote (list args)))) ~name)) functions))))) (defmacro ann-passes "Emit checker signatures stored by defpass. The generated function bodies diff --git a/src/com/tailrecursion/nanopass/pass.clj b/src/com/tailrecursion/nanopass/pass.clj index c09ce38..fe84a5b 100644 --- a/src/com/tailrecursion/nanopass/pass.clj +++ b/src/com/tailrecursion/nanopass/pass.clj @@ -11,8 +11,6 @@ `((fn [~(typed bad 'typed.clojure/Nothing)] (throw (ex-info "Unexpected nanopass production" {:pass '~pass :node ~bad}))) ~value))) -(declare walk dispatch) - (defn- preserve-meta [{:keys [target]} type value result] (let [type (l/instantiate target type) id (gensym "result__") @@ -23,118 +21,117 @@ (~attach ~id (meta ~value)) ~id)))) -(defn- mapper [{:keys [source] :as context} in out] - (let [item (gensym "item__")] - `(fn [~(typed item (l/instantiate source in))] ~(walk context in out item)))) - -(defn- map-fields [{:keys [source target] :as context} in out value] - (let [a (l/hmap-options in) b (l/hmap-options out)] - (when-not (every? #(= (set (keys (% a))) (set (keys (% b)))) [:mandatory :optional]) - (l/fail :explicit-clause-required in "Changed map fields need an explicit clause")) - (let [changed (filter (fn [[key type]] - (or (not= type (get-in b [:optional key])) - (seq (l/references (set (keys (:types source))) type)))) - (sort-by (comp str key) (:optional a))) - base (if (seq changed) `(dissoc ~value ~@(map first changed)) value) - fields (mapcat (fn [[key type]] - (let [before `(~key ~value) - after (walk context type (get-in b [:mandatory key]) before)] - (when (not= before after) [key after]))) (:mandatory a)) - required (if (seq fields) `(assoc ~base ~@fields) base)] - ;; assoc/dissoc preserve metadata. Retain unchanged fields and reconstruct - ;; only recursive or changed fields; no metadata wrapper is needed. - (reduce (fn [result [key type]] - (let [acc (gensym "record__") child (gensym "child__") - other-keys (remove #{key} (concat (keys (:mandatory a)) (keys (:optional a))))] - ;; Project a zero/one-entry map. Its value type excludes - ;; missing-key nil while retaining an explicitly present nil. - `(reduce-kv - (fn [~(typed acc (l/instantiate target out)) ~'_ ~(typed child (l/instantiate source type))] - (assoc ~acc ~key ~(walk context type (get-in b [:optional key]) child))) - ~result (dissoc ~value ~@other-keys)))) - required changed)))) +(defn definition + [{:keys [name source target from to params arguments result-type processors body?]} forms] + (letfn [(mapper [{:keys [source] :as context} in out] + (let [item (gensym "item__")] + `(fn [~(typed item (l/instantiate source in))] ~(walk context in out item)))) -(defn- optional [context in out value] - (let [id (gensym "optional__")] - `((fn [~(typed id (list 'typed.clojure/Option (l/instantiate (:source context) in)))] - (if (nil? ~id) nil ~(walk context in out id))) ~value))) + (map-fields [{:keys [source target] :as context} in out value] + (let [a (l/hmap-options in) b (l/hmap-options out)] + (when-not (every? #(= (set (keys (% a))) (set (keys (% b)))) [:mandatory :optional]) + (l/fail :explicit-clause-required in "Changed map fields need an explicit clause")) + (let [changed (filter (fn [[key type]] + (or (not= type (get-in b [:optional key])) + (seq (l/references (set (keys (:types source))) type)))) + (sort-by (comp str key) (:optional a))) + base (if (seq changed) `(dissoc ~value ~@(map first changed)) value) + fields (mapcat (fn [[key type]] + (let [before `(~key ~value) + after (walk context type (get-in b [:mandatory key]) before)] + (when (not= before after) [key after]))) (:mandatory a)) + required (if (seq fields) `(assoc ~base ~@fields) base)] + ;; assoc/dissoc preserve metadata. Retain unchanged fields and reconstruct + ;; only recursive or changed fields; no metadata wrapper is needed. + (reduce (fn [result [key type]] + (let [acc (gensym "record__") child (gensym "child__") + other-keys (remove #{key} (concat (keys (:mandatory a)) (keys (:optional a))))] + ;; Project a zero/one-entry map. Its value type excludes + ;; missing-key nil while retaining an explicitly present nil. + `(reduce-kv + (fn [~(typed acc (l/instantiate target out)) ~'_ ~(typed child (l/instantiate source type))] + (assoc ~acc ~key ~(walk context type (get-in b [:optional key]) child))) + ~result (dissoc ~value ~@other-keys)))) + required changed)))) -(defn- walk [{:keys [source target processors params] :as context} in out value] - (cond - (and (symbol? in) (contains? (:types source) in)) - (if-let [processor (and (contains? (:types target) out) (get processors in))] - `(~processor ~@params ~value) - (l/fail :missing-processor in "Recursive field needs a processor" {:target out})) + (optional [context in out value] + (let [id (gensym "optional__")] + `((fn [~(typed id (list 'typed.clojure/Option (l/instantiate (:source context) in)))] + (if (nil? ~id) nil ~(walk context in out id))) ~value))) - (and (= in out) (empty? (l/references (set (keys (:types source))) in))) value + (walk [{:keys [source target processors params] :as context} in out value] + (cond + (and (symbol? in) (contains? (:types source) in)) + (if-let [processor (and (contains? (:types target) out) (get processors in))] + `(~processor ~@params ~value) + (l/fail :missing-processor in "Recursive field needs a processor" {:target out})) - (not (and (seq? in) (seq? out) (= (first in) (first out)))) - (l/fail :explicit-clause-required in "Changed field types need an explicit clause" {:target out}) + (and (= in out) (empty? (l/references (set (keys (:types source))) in))) value - :else - (case (first in) - typed.clojure/HMap (map-fields context in out value) - typed.clojure/Vec (preserve-meta context out value `(mapv ~(mapper context (second in) (second out)) ~value)) - typed.clojure/Set (preserve-meta context out value `(into #{} (map ~(mapper context (second in) (second out))) ~value)) - typed.clojure/HVec - (do (when-not (= (count (second in)) (count (second out))) - (l/fail :explicit-clause-required in "Changed tuple arity needs an explicit clause")) - (preserve-meta context out value - (mapv (fn [i a b] (walk context a b `(nth ~value ~i))) - (range) (second in) (second out)))) - typed.clojure/Map - (let [[_ ik iv] in [_ ok ov] out - acc (gensym "map__") k (gensym "key__") v (gensym "val__")] - (preserve-meta context out value - `(reduce-kv (fn [~(typed acc (l/instantiate target out)) - ~(typed k (l/instantiate source ik)) - ~(typed v (l/instantiate source iv))] - (assoc ~acc ~(walk context ik ok k) ~(walk context iv ov v))) {} ~value))) - typed.clojure/Option (optional context (second in) (second out) value) - typed.clojure/U - (let [a (l/alternatives in) b (l/alternatives out)] - (if (and (= 2 (count a) (count b)) (some nil? a) (some nil? b)) - (optional context (first (remove nil? a)) (first (remove nil? b)) value) - (dispatch context in out value {}))) - (l/fail :explicit-clause-required in "Cannot derive structural traversal for this type")))) + (not (and (seq? in) (seq? out) (= (first in) (first out)))) + (l/fail :explicit-clause-required in "Changed field types need an explicit clause" {:target out}) -(defn- dispatch [{:keys [name] :as context} in out value clauses] - (let [variants (l/alternatives in) - tags (set (map l/tag variants)) - targets (when out (into {} (map (juxt l/tag identity)) (l/alternatives out)))] - (when (contains? tags nil) - (l/fail :ambiguous-dispatch in "Dispatch needs tagged map alternatives; use :body true")) - (when-not (every? tags (keys clauses)) - (l/fail :unknown-production clauses "Pass clause is absent from the input language")) - `(case (:op ~value) - ~@(mapcat (fn [variant] - (let [tag (l/tag variant)] - [tag (if (contains? clauses tag) (get clauses tag) - (if-let [other (get targets tag)] - (walk context variant other value) - (l/fail :missing-clause variant "Removed production needs a clause" - {:pass name :production tag})))])) variants) - ~(unexpected name value)))) + :else + (case (first in) + typed.clojure/HMap (map-fields context in out value) + typed.clojure/Vec (preserve-meta context out value `(mapv ~(mapper context (second in) (second out)) ~value)) + typed.clojure/Set (preserve-meta context out value `(into #{} (map ~(mapper context (second in) (second out))) ~value)) + typed.clojure/HVec + (do (when-not (= (count (second in)) (count (second out))) + (l/fail :explicit-clause-required in "Changed tuple arity needs an explicit clause")) + (preserve-meta context out value + (mapv (fn [i a b] (walk context a b `(nth ~value ~i))) + (range) (second in) (second out)))) + typed.clojure/Map + (let [[_ ik iv] in [_ ok ov] out + acc (gensym "map__") k (gensym "key__") v (gensym "val__")] + (preserve-meta context out value + `(reduce-kv (fn [~(typed acc (l/instantiate target out)) + ~(typed k (l/instantiate source ik)) + ~(typed v (l/instantiate source iv))] + (assoc ~acc ~(walk context ik ok k) ~(walk context iv ov v))) {} ~value))) + typed.clojure/Option (optional context (second in) (second out) value) + typed.clojure/U + (let [a (l/alternatives in) b (l/alternatives out)] + (if (and (= 2 (count a) (count b)) (some nil? a) (some nil? b)) + (optional context (first (remove nil? a)) (first (remove nil? b)) value) + (dispatch context in out value {}))) + (l/fail :explicit-clause-required in "Cannot derive structural traversal for this type")))) -(defn definition - [{:keys [name source target from to params arguments result-type processors body?]} forms] - (let [node (last arguments) - input-type (l/type-name source from) - signature (vec (concat params [input-type :-> result-type])) - context {:name name :source source :target target - :processors (merge {from name} processors) :params (vec (butlast arguments))} - in (get-in source [:types from]) out (get-in target [:types to]) - body (if body? - (do (when-not (= 1 (count forms)) - (l/fail :invalid-pass forms "A whole-body pass needs one expression")) - (first forms)) - (do (when (or (odd? (count forms)) - (not= (count (take-nth 2 forms)) (count (set (take-nth 2 forms))))) - (l/fail :invalid-pass forms "Pass clauses must be distinct tag/expression pairs")) - (if (and (empty? forms) (not-every? l/tag (l/alternatives in))) - (walk context in out node) - (dispatch context in out node (apply hash-map forms))))) - local-name (symbol (clojure.core/name name))] - `(defn ~local-name - {:com.tailrecursion.nanopass/signature '~signature} - ~arguments ~body))) + (dispatch [{:keys [name] :as context} in out value clauses] + (let [variants (l/alternatives in) + tags (set (map l/tag variants)) + targets (when out (into {} (map (juxt l/tag identity)) (l/alternatives out)))] + (when (contains? tags nil) + (l/fail :ambiguous-dispatch in "Dispatch needs tagged map alternatives; use :body true")) + (when-not (every? tags (keys clauses)) + (l/fail :unknown-production clauses "Pass clause is absent from the input language")) + `(case (:op ~value) + ~@(mapcat (fn [variant] + (let [tag (l/tag variant)] + [tag (if (contains? clauses tag) (get clauses tag) + (if-let [other (get targets tag)] + (walk context variant other value) + (l/fail :missing-clause variant "Removed production needs a clause" + {:pass name :production tag})))])) variants) + ~(unexpected name value))))] + (let [node (last arguments) + input-type (l/type-name source from) + signature (vec (concat params [input-type :-> result-type])) + context {:name name :source source :target target + :processors (merge {from name} processors) :params (vec (butlast arguments))} + in (get-in source [:types from]) out (get-in target [:types to]) + body (if body? + (do (when-not (= 1 (count forms)) + (l/fail :invalid-pass forms "A whole-body pass needs one expression")) + (first forms)) + (do (when (or (odd? (count forms)) + (not= (count (take-nth 2 forms)) (count (set (take-nth 2 forms))))) + (l/fail :invalid-pass forms "Pass clauses must be distinct tag/expression pairs")) + (if (and (empty? forms) (not-every? l/tag (l/alternatives in))) + (walk context in out node) + (dispatch context in out node (apply hash-map forms))))) + local-name (symbol (clojure.core/name name))] + `(defn ~local-name + {:com.tailrecursion.nanopass/signature '~signature} + ~arguments ~body)))) diff --git a/test/com/tailrecursion/blacktide/annotations.clj b/test/com/tailrecursion/blacktide/annotations.clj index 6166da3..9810341 100644 --- a/test/com/tailrecursion/blacktide/annotations.clj +++ b/test/com/tailrecursion/blacktide/annotations.clj @@ -1,47 +1,30 @@ (ns com.tailrecursion.blacktide.annotations - "Checking-only aliases and signatures for every production compiler pass." - (:require [typed.clojure] + "Discover compiler namespaces and register their language and pass contracts. + New source files participate in checking without a second handwritten list." + (:require [clojure.java.io :as io] + [typed.clojure] [clojure.core.typed :as ct] - [com.tailrecursion.nanopass :as np] - [com.tailrecursion.blacktide.compiler.assignment :as assignment] - [com.tailrecursion.blacktide.compiler.backend :as backend] - [com.tailrecursion.blacktide.compiler.boundaries :as boundaries] - [com.tailrecursion.blacktide.compiler.box-origins :as box-origins] - [com.tailrecursion.blacktide.compiler.case-emit :as case-emit] - [com.tailrecursion.blacktide.compiler.cases :as cases] - [com.tailrecursion.blacktide.compiler.collections :as collections] - [com.tailrecursion.blacktide.compiler.compact :as compact] - [com.tailrecursion.blacktide.compiler.compact-languages :as compact-languages] - [com.tailrecursion.blacktide.compiler.effects :as effects] - [com.tailrecursion.blacktide.compiler.emit :as emit] - [com.tailrecursion.blacktide.compiler.errors :as errors] - [com.tailrecursion.blacktide.compiler.feasible :as feasible] - [com.tailrecursion.blacktide.compiler.graph :as graph] - [com.tailrecursion.blacktide.compiler.host-emit :as host-emit] - [com.tailrecursion.blacktide.compiler.invariants :as invariants] - [com.tailrecursion.blacktide.compiler.joins :as joins] - [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [com.tailrecursion.blacktide.compiler.jvm-languages :as jvm-languages] - [com.tailrecursion.blacktide.compiler.languages :as languages] - [com.tailrecursion.blacktide.compiler.loop-facts :as loop-facts] - [com.tailrecursion.blacktide.compiler.lower :as lower] - [com.tailrecursion.blacktide.compiler.native :as native] - [com.tailrecursion.blacktide.compiler.analysis-languages :as analysis-languages] - [com.tailrecursion.blacktide.compiler.order :as order] - [com.tailrecursion.blacktide.compiler.prepare :as prepare] - [com.tailrecursion.blacktide.compiler.source :as source] - [com.tailrecursion.blacktide.compiler.source-case :as source-case] - [com.tailrecursion.blacktide.compiler.source-host :as source-host] - [com.tailrecursion.blacktide.compiler.source-jvm :as source-jvm] - [com.tailrecursion.blacktide.compiler.source-languages :as source-languages] - [com.tailrecursion.blacktide.compiler.source-values :as source-values] - [com.tailrecursion.blacktide.compiler.storage :as storage] - [com.tailrecursion.blacktide.compiler.structure :as structure] - [com.tailrecursion.blacktide.compiler.structured-emit :as structured-emit] - [com.tailrecursion.blacktide.compiler.structured-languages :as structured-languages] - [com.tailrecursion.blacktide.compiler.transfers :as transfers] - [com.tailrecursion.blacktide.compiler.value-facts :as value-facts] - [com.tailrecursion.blacktide.compiler :as compiler])) + [com.tailrecursion.nanopass :as np])) + +(def compiler-namespaces + (->> (cons (io/file "src/com/tailrecursion/blacktide/compiler.clj") + (file-seq (io/file "src/com/tailrecursion/blacktide/compiler"))) + (filter #(.endsWith (.getName ^java.io.File %) ".clj")) + (map (fn [file] + (with-open [reader (java.io.PushbackReader. (io/reader file))] + (binding [*read-eval* false] (second (read reader)))))) + sort vec)) + +(doseq [namespace compiler-namespaces] (require namespace)) + +(defmacro annotate-compiler [] + (let [vars (mapcat (comp vals ns-interns) compiler-namespaces) + names (fn [key] + (->> vars (filter #(key (meta %))) (map #(symbol %)) sort))] + `(do (np/ann-languages ~@(names ::np/language)) + (np/ann-passes ~@(names ::np/signature))))) + +(annotate-compiler) ;; Var.toSymbol delegates to Symbol.create; its successful result is non-null. (ct/non-nil-return clojure.lang.Var/toSymbol :all) @@ -57,82 +40,3 @@ (ct/non-nil-return java.lang.reflect.Method/getReturnType :all) (ct/non-nil-return java.lang.reflect.Method/getDeclaringClass :all) (ct/non-nil-return java.lang.reflect.Method/getParameterTypes :all) - -(np/ann-languages compact-languages/Compact) -(np/ann-languages jvm-languages/JVM) -(np/ann-languages languages/Machine languages/CFG languages/Symbolic) -(np/ann-languages analysis-languages/Analysis analysis-languages/Planned) -(np/ann-languages source-languages/Bound source-languages/Native source-languages/Classified - source-languages/Sequential source-languages/Assigned source-languages/Cases - source-languages/Collections source-languages/Prepared source-languages/Ordered) -(np/ann-languages structured-languages/Structured structured-languages/Joined structured-languages/Stored) -(np/ann-passes assignment/sequential-pair assignment/sequential-pairs assignment/sequential-forms - assignment/sequential-node assignment/sequential-binding assignment/sequential - assignment/parallel-forms assignment/parallel-bindings assignment/save-parallel-values - assignment/parallel-node assignment/parallel) -(np/ann-passes backend/compile-program) -(np/ann-passes boundaries/native-node boundaries/native-binding boundaries/native-forms boundaries/native - boundaries/classify-node boundaries/classify-pair boundaries/classify-pairs - boundaries/classify-binding boundaries/classify-forms boundaries/classify) -(np/ann-passes box-origins/copy-source box-origins/reboxable? box-origins/reboxable-values) -(np/ann-passes case-emit/selector-form case-emit/arms-form) -(np/ann-passes cases/normalize-node cases/normalize-binding cases/normalize-forms cases/normalize) -(np/ann-passes collections/form collections/normalize-forms collections/literal-values collections/constant - collections/normalize-node collections/normalize-binding collections/normalize) -(np/ann-passes compact/introduce compact/atom-reads compact/rhs-reads compact/reads compact/stable? - compact/substitute-atom compact/substitute-operand compact/substitute-rhs - compact/substitute-operation compact/substitute-binding compact/substitute compact/atom-type - compact/movable? compact/replacement compact/discardable? compact/simplify compact/program) -(np/ann-passes effects/effect-node effects/effects) -(np/ann-passes emit/atom-form emit/rhs-form emit/operation-form emit/edge-form emit/term-form emit/block-form - emit/program-form) -(np/ann-passes errors/site errors/fail errors/check-visibility) -(np/ann-passes feasible/terminator feasible/block feasible/program) -(np/ann-passes graph/atom-reads graph/rhs-reads graph/term-reads graph/successors graph/block-facts - graph/collect-facts graph/fact-at graph/validate-edges graph/reachable graph/predecessors - graph/initialized graph/validate-initialization graph/liveness graph/allocate graph/analyze) -(np/ann-passes host-emit/global-form host-emit/box-form host-emit/typed-form host-emit/conversion-form - host-emit/host-form) -(np/ann-passes invariants/assigned invariants/expression) -(np/ann-passes joins/postdominators joins/region joins/candidate joins/plan) -(np/ann-passes jvm/resolved-class jvm/source-tag jvm/tag-class jvm/fact jvm/value-type jvm/hint-conflict - jvm/visible-class jvm/literal-type jvm/join-type jvm/methods jvm/static-method? jvm/return-type - jvm/constructors jvm/parameters jvm/select-method jvm/public-member jvm/host-type - jvm/prepare-call) -(np/ann-passes loop-facts/children loop-facts/primitive loop-facts/inferred loop-facts/converted - loop-facts/boxed loop-facts/initializer loop-facts/bindings loop-facts/expression - loop-facts/recurs loop-facts/compatible? loop-facts/solve loop-facts/recur-error - loop-facts/recurrence loop-facts/materialize loop-facts/lower) -(np/ann-passes lower/fresh-block lower/start lower/seal lower/evaluate lower/lower-forms lower/lower-bindings - lower/lower-node lower/lower lower/compile-graph) -(np/ann-passes native/hinted-form native/reads-node native/boxed-reads native/reads native/literal-values - native/bindings-form native/node-form native/form) -(np/ann-passes order/save order/operands order/normalize-forms order/normalize-bindings order/normalize-node - order/normalize) -(np/ann-passes prepare/prepare-forms prepare/conversion prepare/prepare-node prepare/prepare-binding - prepare/prepare) -(np/ann-passes source/fresh source/expression source/resolve-name source/parse-symbol source/parse-operand - source/parse-forms source/parse-body source/bind-local source/parse-bindings source/parse-scope - source/parse-loop source/parse-recur source/parse-arms source/case-expression source/parse-case - source/parse-dispatch source/protocol-argument source/protocol-call source/keyword-call - source/parse-call source/parse-host source/parse-assignment source/parse-marker - source/parse-list source/parse-collection source/parse) -(np/ann-passes source-case/expand-case source-case/int32 source-case/dispatch-syntax) -(np/ann-passes source-host/tags source-host/qualified source-host/syntax) -(np/ann-passes source-jvm/signature source-jvm/primitive-interface source-jvm/direct-call - source-jvm/protocol-class source-jvm/protocol-call source-jvm/infer source-jvm/binding-facts - source-jvm/caller-facts) -(np/ann-passes source-values/node source-values/binding-form source-values/pair source-values/pairs - source-values/forms source-values/expression) -(np/ann-passes storage/loop-slots storage/cyclic? storage/operand-boxes storage/operation-boxes - storage/observed-values storage/term-boxes storage/select-storage) -(np/ann-passes structure/block structure/scan structure/harmless-atom? structure/harmless-block? - structure/wrap structure/arms structure/terminator structure/joined structure/body - structure/edge structure/construction-site? structure/site-failure structure/recover) -(np/ann-passes structured-emit/primitive-form structured-emit/binding-form structured-emit/atom-form - structured-emit/operand-form structured-emit/rhs-form structured-emit/rhs-type - structured-emit/operation-form structured-emit/let-form structured-emit/expression) -(np/ann-passes transfers/resolve-term transfers/resolve-block transfers/resolve-program) -(np/ann-passes value-facts/primitive value-facts/join value-facts/atom-type value-facts/rhs-type - value-facts/definitions value-facts/solve value-facts/analyze) -(np/ann-passes compiler/compile-tree compiler/caller-locals compiler/compile-form) diff --git a/test/com/tailrecursion/blacktide/backend_test.clj b/test/com/tailrecursion/blacktide/backend_test.clj index b24281b..19e0309 100644 --- a/test/com/tailrecursion/blacktide/backend_test.clj +++ b/test/com/tailrecursion/blacktide/backend_test.clj @@ -5,24 +5,24 @@ [clojure.test.check.generators :as gen] [clojure.test.check.properties :as prop] [com.tailrecursion.blacktide :refer [loophole label goto setq psetq]] - [com.tailrecursion.blacktide.compiler.backend :as backend] - [com.tailrecursion.blacktide.compiler.emit :as emit] - [com.tailrecursion.blacktide.compiler.structure :as structure] - [com.tailrecursion.blacktide.compiler.storage :as storage] - [com.tailrecursion.blacktide.compiler.compact :as compact] - [com.tailrecursion.blacktide.compiler.value-facts :as facts] - [com.tailrecursion.blacktide.compiler.box-origins :as origins] - [com.tailrecursion.blacktide.compiler.graph :as graph] + [com.tailrecursion.blacktide.compiler :as compiler] + [com.tailrecursion.blacktide.compiler.passes.emit :as emit] + [com.tailrecursion.blacktide.compiler.passes.structure :as structure] + [com.tailrecursion.blacktide.compiler.passes.storage :as storage] + [com.tailrecursion.blacktide.compiler.passes.compact :as compact] + [com.tailrecursion.blacktide.compiler.passes.value-facts :as facts] + [com.tailrecursion.blacktide.compiler.passes.box-origins :as origins] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [com.tailrecursion.blacktide.compiler.prepare :as prepare] - [com.tailrecursion.blacktide.compiler.lower :as lower] + [com.tailrecursion.blacktide.compiler.passes.prepare :as prepare] + [com.tailrecursion.blacktide.compiler.passes.lower :as lower] [com.tailrecursion.blacktide.machine-test :as m])) (defn compile-program [form mode] (binding [*ns* (the-ns 'com.tailrecursion.blacktide.backend-test)] - (with-redefs [backend/compile-program (if (= mode :dispatcher) + (with-redefs [compiler/compile-program (if (= mode :dispatcher) emit/program-form - backend/compile-program)] + compiler/compile-program)] (eval (list 'fn '[events] form))))) (defn selection [form] diff --git a/test/com/tailrecursion/blacktide/check.clj b/test/com/tailrecursion/blacktide/check.clj index 50f3b29..7779b34 100644 --- a/test/com/tailrecursion/blacktide/check.clj +++ b/test/com/tailrecursion/blacktide/check.clj @@ -3,75 +3,45 @@ (:require [clojure.test :refer [deftest is]] [typed.clojure :as t] [com.tailrecursion.nanopass.check :as probes] - [com.tailrecursion.blacktide.annotations])) + [com.tailrecursion.blacktide.annotations :as annotations])) (deftest compiler-passes - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.errors)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.feasible)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.value-facts)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.box-origins)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.invariants)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.structure)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.joins)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.storage)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.compact)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.structured-emit)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.backend)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.jvm)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.source-host)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.source-jvm)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.source-case)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.cases)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.case-emit)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.host-emit)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.emit)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.graph)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.source)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.loop-facts)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.effects)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.boundaries)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.native)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.assignment)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.source-values)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.collections)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.prepare)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.order)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.lower)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler.transfers)) - (is (t/check-ns-clj 'com.tailrecursion.blacktide.compiler))) + (doseq [namespace annotations/compiler-namespaces + :when (not= namespace 'com.tailrecursion.blacktide.compiler.languages)] + (is (t/check-ns-clj namespace) (str namespace)))) (deftest assignment-stage-exclusions (let [expr (fn [node] {:site {:form nil :file nil :line nil :column nil} :jvm {:hint nil :var-meta {} :type :unknown :primitive? false :hint-error nil} :node node}) batch {:op :psetq :pairs []} - assigned 'com.tailrecursion.blacktide.compiler.source-languages/Assigned-Expr] - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Bound-Node {:op :setq :pairs []} true) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Sequential-Node {:op :setq :pairs []} false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Sequential-Node batch true) + assigned 'com.tailrecursion.blacktide.compiler.languages/Assigned-Expr] + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Bound-Node {:op :setq :pairs []} true) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Sequential-Node {:op :setq :pairs []} false) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Sequential-Node batch true) (probes/accepts? assigned (expr batch) false) (probes/accepts? assigned (expr {:op :do :forms [(expr batch)]}) false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Native-Expr + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Native-Expr (expr {:op :do :forms [(expr batch)]}) false) (probes/accepts? assigned (expr {:op :literal :value batch}) true))) (deftest native-and-recur-boundaries - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Native-Node {:op :setq :pairs []} false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Native-Node + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Native-Node {:op :setq :pairs []} false) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Native-Node {:op :goto :name 'done} false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Ordered-Node + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Ordered-Node {:op :recur :target {:id 'loop :bindings []} :args []} true) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Ordered-Node + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Ordered-Node {:op :recur :target {:id 'loop :bindings []} :args [{:op :literal :value 1}]} false)) (deftest host-preparation-boundary (let [operation {:op :method-value :name 'String/.length} unprepared {:op :host :operation operation :args []} prepared {:op :host-call :operation operation :args []}] - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Collections-Node unprepared true) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Prepared-Node unprepared false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Prepared-Node prepared true) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Ordered-Node + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Collections-Node unprepared true) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Prepared-Node unprepared false) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Prepared-Node prepared true) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Ordered-Node (assoc prepared :args [{:op :literal :value nil}]) false))) (deftest case-selection-boundary @@ -79,8 +49,8 @@ :jvm {:hint nil :var-meta {} :type Long/TYPE :primitive? true :hint-error nil} :node {:op :literal :value 0}} node {:op :case :selector expr :arms [expr]}] - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Assigned-Node node true) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Cases-Node node false) - (probes/accepts? 'com.tailrecursion.blacktide.compiler.source-languages/Cases-Node (assoc node :op :select) true) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Assigned-Node node true) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Cases-Node node false) + (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/Cases-Node (assoc node :op :select) true) (probes/accepts? 'com.tailrecursion.blacktide.compiler.languages/CFG-Term {:op :switch :test {:op :literal :value 0} :targets [1 2]} true))) diff --git a/test/com/tailrecursion/blacktide/collections_test.clj b/test/com/tailrecursion/blacktide/collections_test.clj index ac55e22..95eb0c0 100644 --- a/test/com/tailrecursion/blacktide/collections_test.clj +++ b/test/com/tailrecursion/blacktide/collections_test.clj @@ -5,8 +5,8 @@ [clojure.test.check.generators :as gen] [clojure.test.check.properties :as prop] [com.tailrecursion.blacktide :refer [loophole]] - [com.tailrecursion.blacktide.compiler.backend :as backend] - [com.tailrecursion.blacktide.compiler.emit :as emit])) + [com.tailrecursion.blacktide.compiler :as compiler] + [com.tailrecursion.blacktide.compiler.passes.emit :as emit])) (defn record! [events tag value] (swap! events conj tag) value) (defmacro key-a [] :a) @@ -88,9 +88,9 @@ (deftest vector-construction-boundaries (doseq [mode [:default :dispatcher]] (binding [*ns* (the-ns 'com.tailrecursion.blacktide.collections-test)] - (with-redefs [backend/compile-program (if (= mode :dispatcher) + (with-redefs [compiler/compile-program (if (= mode :dispatcher) emit/program-form - backend/compile-program)] + compiler/compile-program)] (doseq [size [0 1 6 7 32 33 63] metadata [nil {} {:note :yes}]] (let [body (with-meta (mapv #(list 'record! 'events % %) (range size)) metadata) wrap identity diff --git a/test/com/tailrecursion/blacktide/generated_test.clj b/test/com/tailrecursion/blacktide/generated_test.clj index 7327ae6..823bad3 100644 --- a/test/com/tailrecursion/blacktide/generated_test.clj +++ b/test/com/tailrecursion/blacktide/generated_test.clj @@ -8,8 +8,8 @@ [clojure.walk :as walk] [com.tailrecursion.blacktide :refer [loophole label goto setq psetq]] [com.tailrecursion.blacktide.backend-test :as backend] - [com.tailrecursion.blacktide.compiler.structure :as structure] - [com.tailrecursion.blacktide.compiler.storage :as storage])) + [com.tailrecursion.blacktide.compiler.passes.structure :as structure] + [com.tailrecursion.blacktide.compiler.passes.storage :as storage])) (defn three-steps [x k] (+ x (* 3 k))) diff --git a/test/com/tailrecursion/blacktide/graph_test.clj b/test/com/tailrecursion/blacktide/graph_test.clj index f0d93b5..653c6c0 100644 --- a/test/com/tailrecursion/blacktide/graph_test.clj +++ b/test/com/tailrecursion/blacktide/graph_test.clj @@ -4,7 +4,7 @@ [clojure.test.check.clojure-test :refer [defspec]] [clojure.test.check.generators :as gen] [clojure.test.check.properties :as prop] - [com.tailrecursion.blacktide.compiler.graph :as graph] + [com.tailrecursion.blacktide.compiler.passes.graph :as graph] [com.tailrecursion.blacktide.machine-test :as m])) (defn jump [target] {:op :jump :target target}) diff --git a/test/com/tailrecursion/blacktide/machine_test.clj b/test/com/tailrecursion/blacktide/machine_test.clj index 128393a..f003755 100644 --- a/test/com/tailrecursion/blacktide/machine_test.clj +++ b/test/com/tailrecursion/blacktide/machine_test.clj @@ -2,7 +2,7 @@ "Compile allocated machines with the real Clojure compiler and execute them." (:require [clojure.test :refer [deftest is]] [com.tailrecursion.blacktide.compiler.jvm :as jvm] - [com.tailrecursion.blacktide.compiler.emit :as emit])) + [com.tailrecursion.blacktide.compiler.passes.emit :as emit])) (defn literal [value] {:op :literal :value value}) (defn local [id] {:op :local :id id}) diff --git a/test/com/tailrecursion/blacktide/source_loops_test.clj b/test/com/tailrecursion/blacktide/source_loops_test.clj index 4a88103..c5c64dc 100644 --- a/test/com/tailrecursion/blacktide/source_loops_test.clj +++ b/test/com/tailrecursion/blacktide/source_loops_test.clj @@ -7,8 +7,8 @@ [clojure.test.check.properties :as prop] [clojure.walk :as walk] [com.tailrecursion.blacktide :refer [loophole]] - [com.tailrecursion.blacktide.compiler.backend :as backend] - [com.tailrecursion.blacktide.compiler.emit :as emit] + [com.tailrecursion.blacktide.compiler :as compiler] + [com.tailrecursion.blacktide.compiler.passes.emit :as emit] [com.tailrecursion.blacktide.jvm-test :as jvm])) (defn root-error [e] (if-let [cause (ex-cause e)] (recur cause) e)) @@ -40,9 +40,9 @@ (defn compile-body [body mode] (try (binding [*ns* (the-ns 'com.tailrecursion.blacktide.source-loops-test)] - (with-redefs [backend/compile-program (if (= mode :dispatcher) + (with-redefs [compiler/compile-program (if (= mode :dispatcher) emit/program-form - backend/compile-program)] + compiler/compile-program)] (eval (list 'fn '[events input n] (enclose body mode))))) (catch Throwable e {:compile (class (root-error e))}))) (defn outcome [f input n] diff --git a/test/com/tailrecursion/nanopass/check.clj b/test/com/tailrecursion/nanopass/check.clj index 7c3befa..8c8149a 100644 --- a/test/com/tailrecursion/nanopass/check.clj +++ b/test/com/tailrecursion/nanopass/check.clj @@ -87,6 +87,28 @@ (is (or (nil? ex) (= :top-level-error (:type-error (ex-data ex)))) (str ex)) (is (= accepted (empty? errors)) (pr-str [form errors])))))) +(deftest recursive-processor-bodies-are-checked + (doseq [[accepted value] [[true '(:value node)] [false 'node]]] + (let [definitions + (concat + [(list 'expression '{:from [f/Source Binding] :to [f/Core Binding] :body true} + '[node] '(binding node)) + (list 'binding '{:from [f/Source Binding] :to [f/Core Binding]} + '[node] :binding {:op :binding :name '(:name node) :value (list 'p/lower value)})] + ;; Distinct contracts across the array-map/hash-map boundary catch + ;; positional annotations attached to the wrong letfn processor. + (for [i (range 9)] + (list (symbol (str "constant" i)) {:from '[f/Source Expr] :to (list 't/Val i) :body true} + '[node] i))) + [head bindings signatures] + (binding [*ns* (the-ns 'com.tailrecursion.nanopass.check)] + (macroexpand-1 (cons 'np/defpasses definitions))) + form (list head bindings signatures 'expression) + {:keys [ex delayed-errors]} (check-form form '[f/Source-Binding :-> f/Core-Binding]) + errors (concat delayed-errors (:errors (ex-data ex)))] + (is (or (nil? ex) (= :top-level-error (:type-error (ex-data ex)))) (str ex)) + (is (= accepted (empty? errors)) (pr-str errors))))) + (deftest assignment-contract-probes (doseq [form '[(fn [node] node) diff --git a/test/com/tailrecursion/nanopass/clients/assignment.clj b/test/com/tailrecursion/nanopass/clients/assignment.clj index 1090a9f..e090ffb 100644 --- a/test/com/tailrecursion/nanopass/clients/assignment.clj +++ b/test/com/tailrecursion/nanopass/clients/assignment.clj @@ -22,26 +22,25 @@ (t/HMap :mandatory {:op ':temp :id t/Sym} :complete? true) (t/HMap :mandatory {:op ':bind :id t/Sym :value Expr :body Expr} :complete? true)]}}) -(declare lower) +(np/defpasses + (sequential-writes {:from [Source Writes] :to [Core Expr] :body true} + [writes] + {:op :do :forms (mapv (fn [[target rhs]] {:op :write :binding target :value (lower rhs)}) writes)}) -(np/defpass sequential-writes {:from [Source Writes] :to [Core Expr] :body true} - [writes] - {:op :do :forms (mapv (fn [[target rhs]] {:op :write :binding target :value (lower rhs)}) writes)}) + (parallel-writes {:from [Source Writes] :to [Core Expr] :body true} + [writes] + (let [names (mapv (fn [_] (gensym "rhs__")) writes) + body {:op :do + :forms (conj (mapv (fn [[target _] id] + {:op :write :binding target :value {:op :temp :id id}}) + writes names) + {:op :const :value nil})}] + (reduce (fn [^{::t/- Core-Expr} body ^{::t/- (t/HVec [t/Sym Source-Expr])} entry] + (let [[id rhs] entry] + {:op :bind :id id :value (lower rhs) :body body})) + body (reverse (mapv (fn [id [_ rhs]] [id rhs]) names writes))))) -(np/defpass parallel-writes {:from [Source Writes] :to [Core Expr] :body true} - [writes] - (let [names (mapv (fn [_] (gensym "rhs__")) writes) - body {:op :do - :forms (conj (mapv (fn [[target _] id] - {:op :write :binding target :value {:op :temp :id id}}) - writes names) - {:op :const :value nil})}] - (reduce (fn [^{::t/- Core-Expr} body ^{::t/- (t/HVec [t/Sym Source-Expr])} entry] - (let [[id rhs] entry] - {:op :bind :id id :value (lower rhs) :body body})) - body (reverse (mapv (fn [id [_ rhs]] [id rhs]) names writes))))) - -(np/defpass lower {:from [Source Expr] :to [Core Expr]} - [node] - :setq (sequential-writes (:writes node)) - :psetq (parallel-writes (:writes node))) + (lower {:from [Source Expr] :to [Core Expr]} + [node] + :setq (sequential-writes (:writes node)) + :psetq (parallel-writes (:writes node)))) diff --git a/test/com/tailrecursion/nanopass/clients/flow.clj b/test/com/tailrecursion/nanopass/clients/flow.clj index 6d5b683..582b71e 100644 --- a/test/com/tailrecursion/nanopass/clients/flow.clj +++ b/test/com/tailrecursion/nanopass/clients/flow.clj @@ -5,47 +5,46 @@ [com.tailrecursion.nanopass.clients.flow-languages :as l] [typed.clojure :as-alias t])) -(declare bind-expr bind-exprs bind-local lower lower-exprs) +(np/defpasses + (bind-exprs + {:from [l/Syntax Exprs] :to (t/HVec [t/Int (t/Vec l/Bound-Expr)]) + :args [(t/Map t/Sym t/Sym) t/Int] :body true} + [env supply forms] + (reduce (fn [^{::t/- (t/HVec [t/Int (t/Vec l/Bound-Expr)])} acc ^{::t/- l/Syntax-Expr} form] + (let [[supply out] acc [supply form] (bind-expr env supply form)] + [supply (conj out form)])) [supply []] forms)) -(np/defpass bind-exprs - {:from [l/Syntax Exprs] :to (t/HVec [t/Int (t/Vec l/Bound-Expr)]) - :args [(t/Map t/Sym t/Sym) t/Int] :body true} - [env supply forms] - (reduce (fn [^{::t/- (t/HVec [t/Int (t/Vec l/Bound-Expr)])} acc ^{::t/- l/Syntax-Expr} form] - (let [[supply out] acc [supply form] (bind-expr env supply form)] - [supply (conj out form)])) [supply []] forms)) + (bind-local + {:from [l/Syntax Binding] :to (t/HVec [t/Int l/Bound-Binding]) + :args [(t/Map t/Sym t/Sym) t/Int] :body true} + [env supply node] + (let [[supply init] (bind-expr env supply (:value node))] + [(inc supply) {:op :binding :id (symbol (str "local__" supply)) :value init}])) -(np/defpass bind-local - {:from [l/Syntax Binding] :to (t/HVec [t/Int l/Bound-Binding]) - :args [(t/Map t/Sym t/Sym) t/Int] :body true} - [env supply node] - (let [[supply init] (bind-expr env supply (:value node))] - [(inc supply) {:op :binding :id (symbol (str "local__" supply)) :value init}])) - -(np/defpass bind-expr - {:from [l/Syntax Expr] :to (t/HVec [t/Int l/Bound-Expr]) :args [(t/Map t/Sym t/Sym) t/Int]} - [env supply node] - :int [supply node] - :read [supply {:op :ref :id (or (get env (:name node)) (throw (ex-info "Unbound name" {:name (:name node)})))}] - :set (let [id (or (get env (:name node)) (throw (ex-info "Unbound name" {:name (:name node)}))) - [supply value] (bind-expr env supply (:value node))] - [supply {:op :write :id id :value value}]) - :plus (let [[supply left] (bind-expr env supply (:left node)) - [supply right] (bind-expr env supply (:right node))] - [supply {:op :plus :left left :right right}]) - :let (let [[supply local] (bind-local env supply (:binding node)) - env (assoc env (:name (:binding node)) (:id local)) - [supply body] (bind-expr env supply (:body node))] - [supply {:op :let :binding local :body body}]) - :if (let [[supply test] (bind-expr env supply (:test node)) - [supply yes] (bind-expr env supply (:then node)) - [supply no] (bind-expr env supply (:else node))] - [supply {:op :if :test test :then yes :else no}]) - :while (let [[supply test] (bind-expr env supply (:test node)) + (bind-expr + {:from [l/Syntax Expr] :to (t/HVec [t/Int l/Bound-Expr]) :args [(t/Map t/Sym t/Sym) t/Int]} + [env supply node] + :int [supply node] + :read [supply {:op :ref :id (or (get env (:name node)) (throw (ex-info "Unbound name" {:name (:name node)})))}] + :set (let [id (or (get env (:name node)) (throw (ex-info "Unbound name" {:name (:name node)}))) + [supply value] (bind-expr env supply (:value node))] + [supply {:op :write :id id :value value}]) + :plus (let [[supply left] (bind-expr env supply (:left node)) + [supply right] (bind-expr env supply (:right node))] + [supply {:op :plus :left left :right right}]) + :let (let [[supply local] (bind-local env supply (:binding node)) + env (assoc env (:name (:binding node)) (:id local)) [supply body] (bind-expr env supply (:body node))] - [supply {:op :while :test test :body body}]) - :do (let [[supply forms] (bind-exprs env supply (:forms node))] - [supply {:op :do :forms forms}])) + [supply {:op :let :binding local :body body}]) + :if (let [[supply test] (bind-expr env supply (:test node)) + [supply yes] (bind-expr env supply (:then node)) + [supply no] (bind-expr env supply (:else node))] + [supply {:op :if :test test :then yes :else no}]) + :while (let [[supply test] (bind-expr env supply (:test node)) + [supply body] (bind-expr env supply (:body node))] + [supply {:op :while :test test :body body}]) + :do (let [[supply forms] (bind-exprs env supply (:forms node))] + [supply {:op :do :forms forms}]))) (np/defpass fresh-block {:from [l/Machine Builder] :to (t/HVec [l/Machine-Builder t/Int]) :body true} [state] @@ -67,43 +66,44 @@ [id state] (assoc state :current id :ops [])) -(np/defpass lower-exprs - {:from [l/Bound Exprs] :to (t/HVec [l/Machine-Builder l/Machine-Operand]) :args [l/Machine-Builder] :body true} - [state forms] - (reduce (fn [^{::t/- (t/HVec [l/Machine-Builder l/Machine-Operand])} acc ^{::t/- l/Bound-Expr} form] - (lower (first acc) form)) [state {:op :literal :value 0}] forms)) +(np/defpasses + (lower-exprs + {:from [l/Bound Exprs] :to (t/HVec [l/Machine-Builder l/Machine-Operand]) :args [l/Machine-Builder] :body true} + [state forms] + (reduce (fn [^{::t/- (t/HVec [l/Machine-Builder l/Machine-Operand])} acc ^{::t/- l/Bound-Expr} form] + (lower (first acc) form)) [state {:op :literal :value 0}] forms)) -(np/defpass lower - {:from [l/Bound Expr] :to (t/HVec [l/Machine-Builder l/Machine-Operand]) :args [l/Machine-Builder]} - [state node] - :int [state {:op :literal :value (:value node)}] - :ref (let [[state id] (fresh-value state)] - [(emit-op state {:op :put :id id :value {:op :register :id (:id node)}}) {:op :register :id id}]) - :write (let [[state value] (lower state (:value node))] - [(emit-op state {:op :put :id (:id node) :value value}) value]) - :plus (let [[state left] (lower state (:left node)) - [state right] (lower state (:right node)) - [state id] (fresh-value state)] - [(emit-op state {:op :add :id id :left left :right right}) {:op :register :id id}]) - :let (let [local (:binding node) [state init] (lower state (:value local))] - (lower (emit-op state {:op :put :id (:id local) :value init}) (:body node))) - :do (lower-exprs state (:forms node)) - :if (let [[state test] (lower state (:test node)) - [state yes] (fresh-block state) [state no] (fresh-block state) [state join] (fresh-block state) - [state result] (fresh-value state) - state (seal state {:op :branch :test test :yes yes :no no}) - [state a] (lower (start yes state) (:then node)) - state (seal (emit-op state {:op :put :id result :value a}) {:op :jump :target join}) - [state b] (lower (start no state) (:else node)) - state (seal (emit-op state {:op :put :id result :value b}) {:op :jump :target join})] - [(start join state) {:op :register :id result}]) - :while (let [[state header] (fresh-block state) [state body] (fresh-block state) [state exit] (fresh-block state) - state (seal state {:op :jump :target header}) - [state test] (lower (start header state) (:test node)) - state (seal state {:op :branch :test test :yes body :no exit}) - [state _] (lower (start body state) (:body node)) - state (seal state {:op :jump :target header})] - [(start exit state) {:op :literal :value 0}])) + (lower + {:from [l/Bound Expr] :to (t/HVec [l/Machine-Builder l/Machine-Operand]) :args [l/Machine-Builder]} + [state node] + :int [state {:op :literal :value (:value node)}] + :ref (let [[state id] (fresh-value state)] + [(emit-op state {:op :put :id id :value {:op :register :id (:id node)}}) {:op :register :id id}]) + :write (let [[state value] (lower state (:value node))] + [(emit-op state {:op :put :id (:id node) :value value}) value]) + :plus (let [[state left] (lower state (:left node)) + [state right] (lower state (:right node)) + [state id] (fresh-value state)] + [(emit-op state {:op :add :id id :left left :right right}) {:op :register :id id}]) + :let (let [local (:binding node) [state init] (lower state (:value local))] + (lower (emit-op state {:op :put :id (:id local) :value init}) (:body node))) + :do (lower-exprs state (:forms node)) + :if (let [[state test] (lower state (:test node)) + [state yes] (fresh-block state) [state no] (fresh-block state) [state join] (fresh-block state) + [state result] (fresh-value state) + state (seal state {:op :branch :test test :yes yes :no no}) + [state a] (lower (start yes state) (:then node)) + state (seal (emit-op state {:op :put :id result :value a}) {:op :jump :target join}) + [state b] (lower (start no state) (:else node)) + state (seal (emit-op state {:op :put :id result :value b}) {:op :jump :target join})] + [(start join state) {:op :register :id result}]) + :while (let [[state header] (fresh-block state) [state body] (fresh-block state) [state exit] (fresh-block state) + state (seal state {:op :jump :target header}) + [state test] (lower (start header state) (:test node)) + state (seal state {:op :branch :test test :yes body :no exit}) + [state _] (lower (start body state) (:body node)) + state (seal state {:op :jump :target header})] + [(start exit state) {:op :literal :value 0}]))) (np/defpass compile-program {:from [l/Syntax Expr] :to [l/Machine Graph] :body true} [source] diff --git a/test/com/tailrecursion/nanopass/pass_test.clj b/test/com/tailrecursion/nanopass/pass_test.clj index 56d9de2..2a835da 100644 --- a/test/com/tailrecursion/nanopass/pass_test.clj +++ b/test/com/tailrecursion/nanopass/pass_test.clj @@ -48,7 +48,10 @@ [node] node node)] [:invalid-pass (np/defpass broken {:from [f/Core Expr] :to [f/Core Expr] :body :yes} [node] node)] - [:invalid-pass (np/defpass broken {:from [f/Core Expr]} [node] node)]]] + [:invalid-pass (np/defpass broken {:from [f/Core Expr]} [node] node)] + [:invalid-pass (np/defpasses)] + [:invalid-pass (np/defpasses (same {:from [f/Core Expr] :to [f/Core Expr]} [node]) + (same {:from [f/Core Expr] :to [f/Core Expr]} [node]))]]] (is (= kind (binding [*ns* (the-ns 'com.tailrecursion.nanopass.passes)] (try (macroexpand-1 form) nil (catch Exception e (-> (or (ex-cause e) e) ex-data :kind))))) diff --git a/test/com/tailrecursion/nanopass/passes.clj b/test/com/tailrecursion/nanopass/passes.clj index e6eebf8..9272bcb 100644 --- a/test/com/tailrecursion/nanopass/passes.clj +++ b/test/com/tailrecursion/nanopass/passes.clj @@ -4,31 +4,29 @@ [com.tailrecursion.nanopass.fixtures :as f] [typed.clojure :as-alias t])) -(declare lower lower-binding) - -(np/defpass lower - {:from [f/Source Expr] :to [f/Core Expr] :processors {Binding lower-binding}} - [node] - :assign {:op :write :name (:name (:binding node)) :value (lower (:value node))}) - -(np/defpass lower-binding {:from [f/Source Binding] :to [f/Core Binding] - :processors {Expr lower}} - [node]) - -(declare rename rename-binding) - -(np/defpass rename - {:from [f/Core Expr] :to [f/Core Expr] :args [(t/Map t/Sym t/Sym)] - :processors {Binding rename-binding}} - [env node] - :read {:op :read :name (get env (:name node) (:name node))} - :write {:op :write :name (get env (:name node) (:name node)) :value (rename env (:value node))}) - -(np/defpass rename-binding - {:from [f/Core Binding] :to [f/Core Binding] :args [(t/Map t/Sym t/Sym)] - :processors {Expr rename}} - [env node] - :binding {:op :binding :name (get env (:name node) (:name node)) :value (rename env (:value node))}) +(np/defpasses + (lower + {:from [f/Source Expr] :to [f/Core Expr] :processors {Binding lower-binding}} + [node] + :assign {:op :write :name (:name (:binding node)) :value (lower (:value node))}) + + (lower-binding {:from [f/Source Binding] :to [f/Core Binding] + :processors {Expr lower}} + [node])) + +(np/defpasses + (rename + {:from [f/Core Expr] :to [f/Core Expr] :args [(t/Map t/Sym t/Sym)] + :processors {Binding rename-binding}} + [env node] + :read {:op :read :name (get env (:name node) (:name node))} + :write {:op :write :name (get env (:name node) (:name node)) :value (rename env (:value node))}) + + (rename-binding + {:from [f/Core Binding] :to [f/Core Binding] :args [(t/Map t/Sym t/Sym)] + :processors {Expr rename}} + [env node] + :binding {:op :binding :name (get env (:name node) (:name node)) :value (rename env (:value node))})) (np/defpass count-step {:from [f/Core Expr] :to (t/HVec [t/Int f/Core-Expr]) :args [t/Int] :body true}