Login
7 branches 0 tags
Ben (X13/Arch) Mostly docs 8fd636f 3 years ago 858 Commits
nujel / stdlib / compiler / backend_bytecode / assembler.nuj
;; 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 opcodes directly

[defn int-fit-in-byte? [a]
      [and [<= a 127] [>= a -128]]]

[defn $push/int [a]
      "[] -> [a]"
      "Push A on the stack as an :int"
      [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"
      [when-not [int-fit-in-byte? a]
                [throw [list :invalid-bc-op "$push/int/byte can only push a signed 8-bit value" a [current-lambda]]]]
      [list #$2 [int->bytecode-op a]]]

[defn $apply/optimize? [fun arg-count]
      [case fun
            [[+ - * / rem] [= arg-count 2]]
            [[zero? car cdr add/int cons < <= = >= >] #t]]]

[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"
      [case arg-count
            [1 [case fun
                     [car [$car]]
                     [cdr [$cdr]]
                     [zero? [$zero?]]
                     [otherwise [exception :arity-error "Wrong number of arguments for that function" fun]]]]
            [2 [case fun
                     [add/int [$add/int]]
                     [+ [$add]]
                     [- [$sub]]
                     [* [$mul]]
                     [/ [$div]]
                     [rem [$rem]]
                     [cons [$cons]]
                     [< [$<]]
                     [<= [$<=]]
                     [= [$=]]
                     [>= [$>=]]
                     [> [$>]]
                     [otherwise [exception :arity-error "Wrong number of arguments for that function" fun]]]]
            [otherwise [exception :arity-error "Wrong number of arguments for that function" fun]]]]

[defn $apply [arg-count fun]
      [when [> arg-count 255]
        [exception :arity-error "Functions can only take up to 255 arguments directly, you can use [apply] instead though" arg-count]]
      [list #$4
            [int->bytecode-op arg-count]]]

[defn $try [target]
      "[] -> []"
      "Try something, jumping to target if an exception occurs"
      [list #$19 [list :relocate target 8 0 [int->bytecode-op 0]]
            [list :relocate target 0 1 [int->bytecode-op 0]]]]

[defn $jmp [target]
      [list #$9
            [list :relocate target 8 0 [int->bytecode-op 0]]
            [list :relocate target 0 1 [int->bytecode-op 0]]]]

[defn $push/val [v]
      "[] -> [v]"
      "Pushes v onto the stack"
      [when [nil? v]
            [throw [list :invalid-bc-op "Can't push #nil as a normal lVal" v [current-lambda]]]]
      [list #$1A
            [list :literal v]]]
[def $push/val/ext $push/val]

[defn $get/val [v]
      "[] -> [v]"
      "Resolve V and get the associated value on the stack"
      [when-not [symbol? v]
            [throw [list :invalid-bc-op "Can only get symbol" v [current-lambda]]]]
      [list #$E
            [list :literal v]]]

[defn $set/val [v]
      "[v] -> [v]"
      "Resolve V and get the associated value on the stack"
      [when-not [symbol? v]
            [throw [list :invalid-bc-op "Can only get symbol" v [current-lambda]]]]
      [list #$5
            [list :literal v]]]

[defn $def/val [v]
      "[v] -> [v]"
      "Resolve V and get the associated value on the stack"
      [when-not [symbol? v]
            [throw [list :invalid-bc-op "Can only get symbol" v [current-lambda]]]]
      [list #$7
            [list :literal v]]]

[defn $jt [target]
      [list #$A
            [list :relocate target 8 0 [int->bytecode-op 0]]
            [list :relocate target 0 1 [int->bytecode-op 0]]]]

[defn $jf [target]
      [list #$B
            [list :relocate target 8 0 [int->bytecode-op 0]]
            [list :relocate target 0 1 [int->bytecode-op 0]]]]

[defn $fn [name args docs body]
      [list #$25
            [val->bytecode-op name]
            [val->bytecode-op args]
            [val->bytecode-op docs]
            [val->bytecode-op body]]]

[defn $macro* [name args docs body]
      [list #$26
            [val->bytecode-op name]
            [val->bytecode-op args]
            [val->bytecode-op docs]
            [val->bytecode-op body]]]

[defn assemble/build-sym-map [code sym-map pos]
      [while code
             [case [type-of [car code]]
                   [:bytecode-op [tree/set! sym-map :last-op [inc! pos]]]
                   [:keyword [case [car code]
                                   [:label [tree/set! sym-map [cadr code] pos]
                                           [cdr! code]]
                                   [:literal [cdr! code]]]]
                   [: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 [tree/ref sym-map [cadr code]]]
      [def off [- [+ target [cadddr code]] pos]]
      [array/set! out [inc! pos] [int->bytecode-op [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-of op]
                       [:bytecode-op [array/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]
          [int->bytecode-op i]
          [list [int->bytecode-op [bit-and [bit-shift-right i 8] #xFF]]
                [int->bytecode-op [bit-and i #xFF]]]]]

[def *max-literal-arr* 0]
[defn assemble/add-literal [lit lit-arr]
      [when [>= [array/length lit-arr] 65535]
            [exception :out-of-bounds "Can only store up to 255 literals per bytecode-arr" code]]
      [when [not= [type-of lit] :pair]
            [dotimes [i [array/length lit-arr]]
                     [when [equal? [array/ref lit-arr i] lit]
                           [return [literal-index->bytecode-op i]]]]]
      [array/length! lit-arr [+ 1 [array/length lit-arr]]]
      [array/set! lit-arr [- [array/length lit-arr] 1] lit]
      #;[when [> [array/length lit-arr] *max-literal-arr*]
            [set! *max-literal-arr* [array/length lit-arr]]
            [pfmtln "Max: {*max-literal-arr*}"]]
      [literal-index->bytecode-op [- [array/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] #$1A]
                  [= [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]
                       #$6
                       #$1A]
                   [cons index-op
                         [assemble/build-lit-arr [cdr code] lit-arr]]]]
            [[and [= [car code] #$E]
                  [= [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]
                       #$F
                       #$E]
                   [cons index-op
                         [assemble/build-lit-arr [cdr code] lit-arr]]]]
            [[and [= [car code] #$5]
                  [= [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]
                       #$10
                       #$5]
                   [cons index-op
                         [assemble/build-lit-arr [cdr code] lit-arr]]]]
           [[and [= [car code] #$7]
                  [= [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]
                       #$8
                       #$7]
                   [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"
      [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 [tree/ref sym-map :last-op]]]
      [assemble/emit-relocated-ops tmp sym-map -1 out]
      [arr->bytecode-arr out lit-arr]]

[defn assemble l
      "Assemble all arguments into a single :bytecode-array"
      [assemble* l]]

[defmacro asmrun ops
          "Assemble and evaluate all bytecode arguments"
          `[bytecode-eval* [assemble ~@ops] [current-closure]]]

[defn asmdebug ops
      "Assemble and evaluate all bytecode arguments"
      [def bc [apply assemble ops]]
      [disassemble bc]
      [def v [bytecode-eval* bc [current-closure]]]
      [pfmtln "Result: {:?}{}" v [type-of v]]]