git » blacktide.git » master » tree

[master] / src / com / tailrecursion / blacktide / compiler / passes / numeric.clj

(ns ^:typed.clojure com.tailrecursion.blacktide.compiler.passes.numeric
  "Recognize numeric runtime calls and freeze their original signatures.
  Value inference and representation selection consult the same semantic rules;
  calls without a proof retain their original overload and operand facts."
  (:require [com.tailrecursion.nanopass :as np]
            [com.tailrecursion.blacktide.compiler.languages :as l]
            [com.tailrecursion.blacktide.compiler.jvm :as jvm]
            [com.tailrecursion.blacktide.compiler.passes.value-facts :as facts]
            [com.tailrecursion.blacktide.compiler.passes.numeric-rules :as rules]
            [typed.clojure :as-alias t]))

(np/defpass conversion-call {:from [l/Machine Rhs] :to [l/Numeric Rhs] :body true}
  [rhs]
  (if (and (= :convert (:op rhs)) (:parameter? (:conversion rhs)))
    (let [type (:type (:conversion rhs))
          fact (:jvm (:value rhs)) source (:type fact)
          float-narrow? (and (:primitive? fact) (identical? source Double/TYPE) (identical? type Float/TYPE))
          ;; Preserve Compiler.emitTypedArgs' direct primitive conversions,
          ;; including unchecked double-to-float even under checked math.
          ;; Only its box/check-Number/runtime-cast path needs lowering.
          direct? (and (:primitive? fact)
                       (or (= type source) float-narrow?
                           (and (identical? source Integer/TYPE) (identical? type Long/TYPE))
                           (and (identical? source Long/TYPE) (identical? type Integer/TYPE))
                           (and (identical? source Float/TYPE) (identical? type Double/TYPE))))
          names (get {Byte/TYPE '[byteCast uncheckedByteCast] Short/TYPE '[shortCast uncheckedShortCast]
                      Integer/TYPE '[intCast uncheckedIntCast] Long/TYPE '[longCast uncheckedLongCast]
                      Float/TYPE '[floatCast uncheckedFloatCast] Double/TYPE '[doubleCast uncheckedDoubleCast]} type)]
      (if (and names (not direct?))
        {:op :numeric :args [(:value rhs)] :parameters [Object] :return type :conversion (:conversion rhs)
         :operation {:op :static :class clojure.lang.RT
                     :member (nth names (if *unchecked-math* 1 0)) :tags [Object]}}
        rhs)) rhs))

(np/defpass rhs {:from [l/Machine Rhs] :to [l/Numeric Rhs] :body true}
  [rhs]
  (if (and (= :host (:op rhs)) (= :static (:op (:operation rhs)))
           (rules/family (:operation rhs)))
    (let [operation (:operation rhs)
          method (jvm/select-method (mapv #(:type (:jvm %)) (:args rhs)) (:tags operation)
                                    (jvm/methods (:class operation) (count (:args rhs)) true (:member operation)))]
      (if method (assoc rhs :op :numeric :parameters (jvm/parameters method) :return (jvm/return-type method)) rhs))
    (conversion-call rhs)))

(np/defpass operation {:from [l/Machine Operation] :to [l/Numeric Operation] :body true}
  [operation] (assoc operation :value (rhs (:value operation))))

(np/defpass block {:from [l/Machine Block] :to [l/Numeric Block] :body true}
  [block] (update block :ops #(mapv operation %)))

(np/defpass program {:from [l/Machine Program] :to [l/Numeric Program] :body true}
  [program] (update program :blocks update-vals block))

(np/defpass source-call {:from [l/Numeric Rhs] :to [l/Machine Rhs] :body true}
  [rhs]
  (if (= :numeric (:op rhs))
    (if-let [conversion (:conversion rhs)]
      {:op :convert :conversion conversion :value (nth (:args rhs) 0)}
      (assoc (dissoc rhs :parameters :return) :op :host)) rhs))

(np/defpass plan
  {:from [l/Numeric Rhs] :to (t/Option l/Numeric-Plan) :args [l/Analysis-Types] :body true}
  [types rhs]
  (when (= :numeric (:op rhs))
    (let [arguments (mapv (fn [arg]
                            (let [type (facts/atom-type types (:value arg))]
                              ;; Outside value inference, an absent storage fact
                              ;; means boxed, not the solver's unknown bottom.
                              (if (= :bottom type) :boxed type))) (:args rhs))]
      ;; JVM numeric parameter conversion casts to Number before calling RT.
      ;; An unknown input (including Character) must retain that check.
      (when (or (nil? (:conversion rhs)) (every? rules/number-type arguments))
        (rules/plan arguments (:parameters rhs) (:return rhs) (:operation rhs))))))

(np/defpass origin-values
  {:from [l/Numeric Rhs] :to (t/Vec l/Machine-Atom) :args [l/Numeric-Plan] :body true}
  [plan rhs]
  (if (= :numeric (:op rhs)) (mapv #(:value (nth (:args rhs) %)) (:origins plan)) []))

(np/defpass applicable?
  {:from [l/Numeric Rhs] :to t/Bool :args [l/Represented-Types t/Sym l/Numeric-Plan] :body true}
  [types target plan rhs]
  ;; A boxed result may alias an input only if that input keeps its box.
  (or (contains? types target)
      (every? #(not (class? (facts/atom-type types %))) (origin-values plan rhs))))