Login
7 branches 0 tags
Ben (X13/Arch) Optimized lopGetVAL/lopSetVal opcodes e66b4c1 2 years ago 1068 Commits
nujel / stdlib / compiler / backend_bytecode / assembler.nuj
a;;; Nujel - Copyright (C) 2020-2021 - Benjamin Vincent Schulenburg
;;; This project uses the MIT license, a copy should be included under /LICENSE
;;;
;;; Contains the bytecode assembler, so we don't have to write opcode literals
;;; and emits. Additionally it calculates branch offsets using the labels provided.

(defn $nop ()
      "() -> () --- Do Nothing"
      :cat :bytecode
      :internal
      :inline
      '(#x00))

(defn $ret ()
      "(a) -> () --- Return top of value stack"
      :cat :bytecode
      :internal
      :inline
      '(#x01))

(defn $add/int ()
      "(a b) -> (result) --- Adds the two topmost values and pushes the result"
      :cat :bytecode
      :internal
      :inline
      '(#x03))

(defn $ref ()
      "(col key) -> (result) --- Looks up key in col"
      :cat :bytecode
      :internal
      :inline
      '(#x2B))

(defn $dup ()
      "(a) -> (a a) --- Duplicates the value that is on the top of the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x0C))

(defn $drop ()
      "(a) -> () --- Drop whatever is on top of the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x0D))

(defn $closure/push ()
      "() -> (closure) --- Push the current closure as a λ on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x13))

(defn $let ()
      "() -> () --- Create a new let closure and switch to it"
      :cat :bytecode
      :internal
      :inline
      '(#x15))

(defn $closure/pop ()
      "() -> () --- Leave the current closure and return to the parent one"
      :cat :bytecode
      :internal
      :inline
      '(#x16))

(defn $< ()
      "(a b) -> (bool) --- Compare A and B and push the result on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x1E))

(defn $<= ()
      "(a b) -> (bool) --- Compare A and B and push the result on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x1F))

(defn $= ()
      "(a b) -> (bool) --- Compare A and B and push the result on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x20))

(defn $>= ()
      "(a b) -> (bool) --- Compare A and B and push the result on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x21))

(defn $> ()
      "(a b) -> (bool) --- Compare A and B and push the result on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x22))

(defn $push/nil ()
      "() -> (nil) --- Push a #nil on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x24))

(defn $car ()
      "(l) -> (car) --- Replace L with its car"
      :cat :bytecode
      :internal
      :inline
      '(#x11))

(defn $cdr ()
      "(l) -> (cdr) --- Replace L with its cdr"
      :cat :bytecode
      :internal
      :inline
      '(#x12))

(defn $cadr ()
      "(l) -> (cadr) --- Replace L with its cadr"
      :cat :bytecode
      :internal
      :inline
      '(#x2C))

(defn $cons ()
      "(car cdr) -> (pair) --- Cons CAR and CDR together and put it on the stack"
      :cat :bytecode
      :internal
      :inline
      '(#x14))

(defn $fn/new ()
      "(args meta body) -> (λ) --- Create a new λ"
      :cat :bytecode
      :internal
      :inline
      '(#x17))

(defn $macro/new ()
      "(args meta body) -> (μ) --- Create a new μ"
      :cat :bytecode
      :internal
      :inline
      '(#x18))

(defn $fn/dynamic ()
      "(name args docs body) -> (λ) --- Create a new λ"
      :cat :bytecode
      :internal
      :inline
      '(#x2F))

(defn $macro/dynamic ()
      "(name args docs body) -> (μ) --- Create a new μ"
      :cat :bytecode
      :internal
      :inline
      '(#x30))

(defn $add ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x25))

(defn $sub ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x26))

(defn $mul ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x27))

(defn $div ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x28))

(defn $rem ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x29))

(defn $push/true ()
      "() -> (#t)"
      :cat :bytecode
      :internal
      :inline
      '(#x1B))

(defn $push/false ()
      "() -> (#f)"
      :cat :bytecode
      :internal
      :inline
      '(#x1C))

(defn $eval ()
      "(bc env) -> (return-val)"
      :cat :bytecode
      :internal
      :inline
      '(#x1D))

(defn $mutable-eval ()
      "(bc env) -> (return-val)"
      :cat :bytecode
      :internal
      :inline
      '(#x2D))

(defn $zero? ()
      "(a) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x2A))

(defn $inc/int ()
      "(a) -> (result) --- Adds 1 to the topmost int"
      :cat :bytecode
      :internal
      :inline
      '(#x23))

(defn $bit-shift-left ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x31))

(defn $bit-shift-right ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x32))

(defn $bit-and ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x33))

(defn $bit-or ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x34))

(defn $bit-xor ()
      "(a b) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x35))

(defn $bit-not ()
      "(a) -> (result)"
      :cat :bytecode
      :internal
      :inline
      '(#x36))

(defn int-fit-in-byte? (a)
      (and (<= a 127) (>= a -128)))

(defn $push/int (a)
      "() -> (a) --- Push A on the stack as an :int"
      :cat :bytecode
      :internal
      (if (int-fit-in-byte? a)
          ($push/int/byte a)
          ($push/val a)))

(defn $push/int/byte (a)
      "() -> (a) --- Push A on the stack as an :int that fits within a byte"
      :cat :bytecode
      :internal
      (when-not (int-fit-in-byte? a)
                (exception :invalid-bc-op "$push/int/byte can only push a signed 8-bit value" a ))
      (list #x2 a))

(defn $apply/optimized (arg-count fun)
      "arguments -> (result) --- Read arg-count arguments from the stack, apply the to fun and push the result on the stack"
      :cat :bytecode
      :internal
      (when (= fun list)
        (return ($list arg-count)))
      (case arg-count
            (1 (case fun
                     (car ($car))
                     (cdr ($cdr))
                     (cadr ($cadr))
                     (zero? ($zero?))
                     (inc/int ($inc/int))
                     (throw ($throw))
                     (bit-not ($bit-not))
                     (otherwise #nil)))
            (2 (case fun
                     (add/int ($add/int))
                     (ref ($ref))
                     (+ ($add))
                     (- ($sub))
                     (* ($mul))
                     (/ ($div))
                     (rem ($rem))
                     (cons ($cons))
                     (< ($<))
                     (<= ($<=))
                     (= ($=))
                     (>= ($>=))
                     (> ($>))
                     (bytecode-eval* ($eval))
                     (mutable-eval* ($mutable-eval))
                     (bit-shift-left ($bit-shift-left))
                     (bit-shift-right ($bit-shift-right))
                     (bit-and ($bit-and))
                     (bit-or ($bit-or))
                     (bit-xor ($bit-xor))
                     (otherwise #nil)))
            (otherwise #nil)))

(defn $apply (arg-count fun)
      "arguments -> (result) --- Read arg-count arguments from the stack, apply the to fun and push the result on the stack"
      :cat :bytecode
      :internal
      (when (> arg-count 255)
        (exception :arity-error "Functions can only take up to 255 arguments directly, try to pass a list/array/map instead" arg-count))
      (list #x4 arg-count))

(defn $apply/collection ()
      "(arguments function) -> (result) --- Read arg-count arguments from the stack, apply the to fun and push the result on the stack"
      :cat :bytecode
      :internal
      (list #x30))

(defn $list (count)
      "arguments -> (result) --- Read count arguments from the stack and put a list of those on the stack instead."
      :cat :bytecode
      :internal
      (when (> count 255)
        (exception :arity-error "$list can only generate lists with up to 255 arguments in one go, try and use cons instead" count))
      (list #x2E count))

(defn $try (target)
      "() -> () --- Try something, jumping to target if an exception occurs"
      :cat :bytecode
      :internal
      (list #x19 (list :relocate target 8 0 0)
            (list :relocate target 0 1 0)))

(defn $throw ()
      "(v) -> () --- Throw an exception"
      :cat :bytecode
      :internal
      #x2F)

(defn $jmp (target)
      :cat :bytecode
      :internal
      (list #x9
            (list :relocate target 8 0 0)
            (list :relocate target 0 1 0)))

(defn $push/val (v)
      "() -> (v) --- Pushes v onto the stack"
      :cat :bytecode
      :internal
      (when (nil? v)
            (exception :invalid-bc-op "Can't push #nil as a normal lVal" v ))
      (list #x1A
            (list :literal v)))

(defn $get/val (v)
      "() -> (v) --- Resolve V and get the associated value on the stack"
      :cat :bytecode
      :internal
      (when-not (symbol? v)
            (exception :invalid-bc-op "Can only get symbol" v ))
      (list #xE
            (list :literal v)))

(defn $set/val (v)
      "(v) -> (v) --- Resolve V and get the associated value on the stack"
      :cat :bytecode
      :internal
      (when-not (symbol? v)
            (exception :invalid-bc-op "Can only get symbol" v ))
      (list #x5
            (list :literal v)))

(defn $set/gen ()
      "(v v v) -> (v) --- Resolve V and get the associated value on the stack"
      :cat :bytecode
      :internal
      (list #x37))

(defn $def/val (v)
      "(v) -> (v) --- Resolve V and get the associated value on the stack"
      :cat :bytecode
      :internal
      (when-not (symbol? v)
            (exception :invalid-bc-op "Can only get symbol" v ))
      (list #x7
            (list :literal v)))

(defn $jt (target)
      :cat :bytecode
      :internal
      (list #xA
            (list :relocate target 8 0 0)
            (list :relocate target 0 1 0)))

(defn $jf (target)
      :cat :bytecode
      :internal
      (list #xB
            (list :relocate target 8 0 0)
            (list :relocate target 0 1 0)))

(defn $fn (name args docs body)
      :cat :bytecode
      :internal
      (list #x25
            (val->bytecode-op name)
            (val->bytecode-op args)
            (val->bytecode-op docs)
            (val->bytecode-op body)))

(defn $macro* (name args docs body)
      :cat :bytecode
      :internal
      (list #x26
            (val->bytecode-op name)
            (val->bytecode-op args)
            (val->bytecode-op docs)
            (val->bytecode-op body)))

(def assemble* (let*
                 (defn assemble/build-sym-map (code sym-map pos)
                       (while code
                         (case (:type-name (car code))
                               (:int (set! sym-map :last-op (inc! pos)))
                               (:keyword (case (car code)
                                               (:label (set! sym-map (cadr code) pos)
                                                       (return pos))
                                               (:relocate (set! sym-map :last-op (inc! pos))
                                                          (return pos))
                                               (:literal (return pos))))
                               (:pair        (set! pos (assemble/build-sym-map (car code) sym-map pos))))
                         (cdr! code))
                       (return pos))

                 (defn assemble/relocate-op (code sym-map pos out)
                       (def target (ref sym-map (cadr code)))
                       (def off (- (+ target (cadddr code)) pos))
                       (set! out (inc! pos) (bit-and (bit-shift-right off (caddr code)) #xFF))
                       (return pos))

                 (defn assemble/emit-relocated-ops (code sym-map pos out)
                       (if (= (car code) :relocate)
                           (set! pos (assemble/relocate-op code sym-map pos out))
                           (doseq (op code pos)
                                  (case (:type-name op)
                                        (:int  (set! out (inc! pos) op))
                                        (:pair (set! pos (assemble/emit-relocated-ops op sym-map pos out)))))))

                 (defn literal-index->bytecode-op (i)
                       (if (< i 256)
                           i
                           (list (bit-and (bit-shift-right i 8) #xFF)
                                 (bit-and i #xFF))))

                 (def *max-literal-arr* 0)
                 (defn assemble/add-literal (lit lit-arr)
                       (when (>= (:length lit-arr) 65535)
                         (exception :out-of-bounds "Can only store up to 255 literals per bytecode-arr" code))
                       (when (not= (:type-name lit) :pair)
                         (dotimes (i (:length lit-arr))
                           (when (equal? (ref lit-arr i) lit)
                             (return (literal-index->bytecode-op i)))))
                       (:length! lit-arr (+ 1 (:length lit-arr)))
                       (set! lit-arr (- (:length lit-arr) 1) lit)
                       (literal-index->bytecode-op (- (:length lit-arr) 1)))

                 (defn assemble/build-lit-arr (code lit-arr)
                       (when-not code (return #nil))
                       (cond ((pair? (car code))
                              (cons (assemble/build-lit-arr (car code) lit-arr)
                                    (assemble/build-lit-arr (cdr code) lit-arr)))
                             ((and (= (car code) #x1A)
                                   (= (car (cadr code)) :literal))
                              (cdr! code)  ; Skip the literal, especially important for :bytecode-op literals
                              (def index-op (assemble/add-literal (cadar code) lit-arr))
                              (cons (if (pair? index-op)
                                        #x6
                                        #x1A)
                                    (cons index-op
                                          (assemble/build-lit-arr (cdr code) lit-arr))))
                             ((and (= (car code) #xE)
                                   (= (car (cadr code)) :literal))
                              (cdr! code)  ; Skip the literal, especially important for :bytecode-op literals
                              (def index-op (assemble/add-literal (cadar code) lit-arr))
                              (cons (if (pair? index-op)
                                        #xF
                                        #xE)
                                    (cons index-op
                                          (assemble/build-lit-arr (cdr code) lit-arr))))
                             ((and (= (car code) #x5)
                                   (= (car (cadr code)) :literal))
                              (cdr! code)  ; Skip the literal, especially important for :bytecode-op literals
                              (def index-op (assemble/add-literal (cadar code) lit-arr))
                              (cons (if (pair? index-op)
                                        #x10
                                        #x5)
                                    (cons index-op
                                          (assemble/build-lit-arr (cdr code) lit-arr))))
                             ((and (= (car code) #x7)
                                   (= (car (cadr code)) :literal))
                              (cdr! code)  ; Skip the literal, especially important for :bytecode-op literals
                              (def index-op (assemble/add-literal (cadar code) lit-arr))
                              (cons (if (pair? index-op)
                                        #x8
                                        #x7)
                                    (cons index-op
                                          (assemble/build-lit-arr (cdr code) lit-arr))))
                             ((= :literal (car code))
                              (cdr! code)  ; Skip the literal, especially important for :bytecode-op literals
                              (cons (assemble/add-literal (car code) lit-arr)
                                    (assemble/build-lit-arr (cdr code) lit-arr)))
                             (otherwise (cons (car code)
                                              (assemble/build-lit-arr (cdr code) lit-arr)))))

                 (defn assemble/flatten (code ret)
                       (when-not code (return ret))
                       (when-not (pair? code) (return ret))
                       (set! ret (assemble/flatten (cdr code) ret))
                       (if (and (pair? (car code))
                                (not (keyword? (caar code))))
                           (assemble/flatten (car code) ret)
                           (cons (car code) ret)))

                 (defn assemble* (code)
                       "Assemble all arguments into a single :bytecode-array"
                       :cat :bytecode
                       :internal
                       (def sym-map {})
                       (def lit-arr [])
                       (def tmp (-> (assemble/flatten code)
                                    (assemble/build-lit-arr lit-arr)))
                       (assemble/build-sym-map tmp sym-map 0)
                       (def out (array/allocate (ref sym-map :last-op)))
                       (assemble/emit-relocated-ops tmp sym-map -1 out)
                       (:bytecode-array out lit-arr))))

(defn assemble l
      "Assemble all arguments into a single :bytecode-array"
      :cat :bytecode
      :internal
      (assemble* l))

(defmacro asmrun ops
          "Assemble and evaluate all bytecode arguments"
          :cat :bytecode
          :internal
          `(bytecode-eval* (assemble ~@ops) (current-closure)))