190 lines
8.2 KiB
Fennel
190 lines
8.2 KiB
Fennel
; ssc: the sufficiently simple compiler
|
|
|
|
; The goal of ssc is to allow simple prefix expressions to be compiled into 65816 code that
|
|
; would run at least as fast or faster than the equivalent threaded Forth code. Complex
|
|
; optimizations are a non-goal; if you want to tune the generated code, go ahead and write
|
|
; the assembly directly.
|
|
|
|
; * All values are 16-bit integers, like Forth or BCPL.
|
|
; * Data and return addresses are mixed on one stack, unlike Forth.
|
|
; * Function calls take a fixed number of arguments, and return 0 or 1 results. The compiler enforces arity checking.
|
|
; * To call a function taking arguments [arg1 arg2 arg3], all 3 arguments should be pushed to the stack before calling.
|
|
; When the function takes control, the stack should look like this:
|
|
; arg1 arg2 arg3 return-address
|
|
; * The caller is responsible for removing all arguments from the stack once the function returns.
|
|
; * The caller is responsible for preserving the A, X and Y registers, if this is desirable.
|
|
; * If the function returns a value, it is stored in the A register.
|
|
; * If a function returns no result, it is not obliged to preserve the A register.
|
|
; * Multitasking is achieved by overlapping the D and S registers on the same 256-byte page of memory.
|
|
; Yielding to a new task involves saving the S register, setting the D register to the new task's page,
|
|
; then setting the S register to the saved value in the old task.
|
|
; * Useful task-local "registers" are kept at the beginning of the page, and the stack grows down from the end of the page.
|
|
; * DP register list:
|
|
; * bank-local address (16 bits)
|
|
; * long address (24 bits)
|
|
; * Last suspended value of S
|
|
; * Mailbox
|
|
; * Pointer to next task
|
|
|
|
; Compiler notes:
|
|
; Expressions are of the form [:function arg1 arg2 arg3]
|
|
; args are either strings (symbols) or numbers
|
|
|
|
; TODO:
|
|
; * implement read / write (pointers)
|
|
; * implement write (locals)
|
|
; * implement loops
|
|
; * implement "getters" (subroutine that runs when referenced by name without an explicit call)
|
|
|
|
(local Object (require :core.object))
|
|
(local lume (require :lib.lume))
|
|
(local Ssc (Object:extend))
|
|
(local Prg (require :asm.asm))
|
|
|
|
(fn Ssc.new [self ?opts]
|
|
(local opts (or ?opts {}))
|
|
(set self.prg (or opts.prg (Prg.new nil :65816)))
|
|
(set self.forms (lume.clone (or opts.forms self.__index.forms)))
|
|
(set self.functions {})
|
|
(set self.locals []))
|
|
|
|
(fn Ssc.push [self ?local]
|
|
(table.insert self.locals (or ?local [:tmp]))
|
|
[:pha])
|
|
(fn Ssc.drop [self ?local]
|
|
(match (. self.locals (length self.locals))
|
|
[:tmp] (when (not= ?local nil) (error (.. "Internal stack error: expected " ?local ", got temporary")))
|
|
loc (when (not= ?local loc) (error (.. "Internal stack error: expected " (or ?local "temporary") " temporary, got " loc))))
|
|
(tset self.locals (length self.locals) nil)
|
|
[:ply])
|
|
|
|
(fn Ssc.defining? [self] (> (length self.locals) 0))
|
|
|
|
(fn countiter [minmax ?max]
|
|
(let [min (if ?max minmax 1)
|
|
max (or ?max minmax)]
|
|
(fn [_ iprev]
|
|
(let [i (if iprev (+ iprev 1) min)]
|
|
(when (<= i max) i)))))
|
|
|
|
(fn pairoff [l]
|
|
(fn [_ iprev] (let [i (if iprev (+ iprev 2) 1)]
|
|
(when (< i (length l)) (values i (. l i) (. l (+ i 1)))))))
|
|
|
|
(fn boolop [self left right branch]
|
|
(let [compiled-left (self:compile-expr left)
|
|
push-left (when (not= (type right) :number) (self:push))
|
|
compiled-compare (if (not push-left) [:cmp right]
|
|
[:block push-left (self:compile-expr right) [:cmp 1 :s]])
|
|
drop-left (when push-left (self:drop))]
|
|
[:block
|
|
compiled-left
|
|
compiled-compare
|
|
[branch :-true-]
|
|
[:lda 0]
|
|
[:bra :-finished-]
|
|
:-true-
|
|
[:lda 0xffff]
|
|
:-finished-
|
|
drop-left]))
|
|
|
|
(set Ssc.forms
|
|
{:asm (fn [self ...]
|
|
(if (self:defining?) [:block ...]
|
|
(self.org:append ...)))
|
|
:org (lambda [self org] (set self.org (self.prg:org org)))
|
|
:do (fn [self ...] (lume.concat [:block] (icollect [i (countiter (select :# ...))] (self:compile-expr (select i ...)))))
|
|
:let (fn [self bindings ...]
|
|
(lume.concat [:block]
|
|
(icollect [_ symbol expr (pairoff bindings)]
|
|
[:block (self:compile-expr expr) (self:push symbol)])
|
|
[(self:compile-expr [:do ...])]
|
|
(icollect [i-half (countiter (/ (length bindings) 2))]
|
|
(self:drop (. bindings (- (length bindings) (* i-half 2) -1))))))
|
|
:+ (lambda [self first ...]
|
|
(local block [:block (self:compile-expr first)])
|
|
(for [i 1 (select :# ...)]
|
|
(let [val (select i ...)]
|
|
(table.insert block
|
|
(match val
|
|
1 [:inc] 2 [:block [:inc] [:inc]]
|
|
-1 [:dec] -2 [:block [:dec] [:dec]]
|
|
(where val (= (type val) :number)) [:block [:clc] [:adc val]]
|
|
(where sym (= (type sym) :string) (self:local-offset sym)) [:block [:clc] [:adc (self:local-offset sym) :s]]
|
|
_ [:block (self:push) (self:compile-expr val) [:clc] [:adc 1 :s] (self:drop)]))))
|
|
block)
|
|
:- (lambda [self first ...]
|
|
(let [block [:block (self:compile-expr first)]
|
|
nargs (select :# ...)]
|
|
(if (= nargs 0) (table.insert block [:block [:eor 0xffff] [:inc]]) ; negate with two's complement
|
|
(for [i 1 nargs]
|
|
(let [val (select i ...)]
|
|
(table.insert block
|
|
(match val
|
|
1 [:dec] 2 [:block [:dec] [:dec]]
|
|
-1 [:inc] -2 [:block [:inc] [:inc]]
|
|
(where val (= (type val) :number)) [:block [:sec] [:sbc val]]
|
|
(where sym (= (type sym) :string) (self:local-offset sym)) [:block [:sec] [:sbc (self:local-offset sym) :s]]
|
|
_ [:block (self:push) (self:compile-expr val) [:sec] [:sbc 1 :s] (self:drop)])))))
|
|
block))
|
|
:= (lambda [self lhs rhs] (boolop self lhs rhs :beq))
|
|
:not= (lambda [self lhs rhs] (boolop self lhs rhs :bne))
|
|
:< (lambda [self lhs rhs] (boolop self lhs rhs :bmi))
|
|
:> (lambda [self lhs rhs] (boolop self rhs lhs :bmi))
|
|
:>= (lambda [self lhs rhs] (boolop self lhs rhs :bpl))
|
|
:<= (lambda [self lhs rhs] (boolop self rhs lhs :bpl))
|
|
:not (lambda [self bool] (self:compile-expr [:if bool 0 0xffff]))
|
|
:if (lambda [self test iftrue ?iffalse]
|
|
(lume.concat
|
|
[:block
|
|
(self:compile-expr test)
|
|
[:cmp 0]
|
|
[:beq :-elseblock-]
|
|
(self:compile-expr iftrue)]
|
|
(when ?iffalse [[:bra :-finished-]])
|
|
[:-elseblock-]
|
|
(when ?iffalse [(self:compile-expr ?iffalse)])
|
|
[:-finished-]))
|
|
:fn (lambda [self name args ...]
|
|
(assert (not (self:defining?)))
|
|
(set self.locals (lume.concat args [[:tmp]]))
|
|
(tset self.functions name {:arity (length args) : args :org self.org})
|
|
; todo: maybe handle mutually recursive functions? (compile-expr only has access to currently-defined functions)
|
|
(self.org:append name (self:compile-expr (lume.concat [:do ...] [[:asm [:rts]]])))
|
|
(assert (= (length self.locals) (+ (length args) 1)))
|
|
(set self.locals []))
|
|
:form (lambda [self name func] (tset self.forms name func))
|
|
})
|
|
|
|
(fn Ssc.local-offset [self symbol]
|
|
(var offset nil)
|
|
(for [i 1 (length self.locals)]
|
|
(when (= (. self.locals i) symbol)
|
|
(set offset (+ 1 (* 2 (- (length self.locals) i))))))
|
|
offset)
|
|
|
|
(fn Ssc.compile-expr [self expr]
|
|
(match expr
|
|
(where num (= (type num) :number)) [:lda num]
|
|
(where loc (= (type loc) :string) (self:local-offset loc)) [:lda (self:local-offset loc) :s]
|
|
; TODO: Global scope
|
|
(where [func & args] (= (?. self.functions func :arity) (length args)))
|
|
(let [pre (icollect [_ arg (ipairs args)] [:block (self:compile-expr arg) (self:push)])
|
|
post (icollect [_ (countiter (length args))] (self:drop))]
|
|
(lume.concat [:block] pre [[:jsr func]] post))
|
|
(where [form & args] (. self.forms form))
|
|
((. self.forms form) self (table.unpack args))
|
|
nil [:block]
|
|
_ (error (.. "Unrecognized expression " (fv expr)))))
|
|
|
|
(fn Ssc.compile [self ...]
|
|
(for [i 1 (select :# ...)]
|
|
(self:compile-expr (select i ...)))
|
|
self)
|
|
|
|
(fn Ssc.assemble [self]
|
|
(self.prg:assemble)
|
|
self.prg)
|
|
|
|
Ssc
|