- sole type i16
- stack machine
- local variables (by index)
- global variables
- threads? desirable. (fixed number)
- code paging?
- ok, relocate at load. but some globals should be shared etc... not so simple
- or just add module_index wherever relevant?
- but still need to resolve globals
- OR JUST OMIT IT NOW FOR SIMPLICITY
- arrays: allocated on heap; user code responsible for freeing
e.g.
my_arr = new_array(30)
print(my_arr) ;; prints numeric index of newly allocated array
my_arr[5] = 10
resize_array(my_arr, 10)
del_array(my_arr) ;; index is made available again
(set my-arr (new_array 30))
(print my-arr)
(set (get my-arr 5) 10)
(resize_array my-arr 10)
(del_array my-arr)
- alt: all arrays declared statically
we can allow resizing, or even not
array my_arr[30]
print(my_arr) ;; prints numeric index
my_arr[5] = 10
resize_array(my_array, 10)
resize_array(my_array, 0) ;; effectively free the held memory
- where do pre-initialized arrays fit in?
- strings: same as array but uint8 elements (oof?)
str = "hello"
prints(str)
new_str = strcat(str, " world")
prints(new_str)
del_str(new_str)
- alt: force all strings to use pre-allocated buffers
str my_str[30]
sprintf(my_str, "hello %s", "world")
prints(my_str)
(#str my-str 30)
(sprintf my-str "hello %s" "world")
(prints my-str)
(const 11) ;; my_str
(const 15) ;; "hello %s"
(const 16) ;; "world"
(call.ext sprintf 3)
(const 11) ;; my_str
(call.ext prints 1)
I/O:
read-only set of files adressed by name (or maybe not in compiled BC?)
open, readat, close
read-write storage by slot index (like Java RMI?)
save_array(index, array)
load_array(index, array) ;; returns real length
### explicit non-goals
- useful as native instruction set
- self-hosting compiler
- systems programming (loading other programs etc.)
## compilation model
- core module (any number of units)
- loadable modules (use functions & globals from core + add their own)
- how to call into them? do they export functions by name?
- what if single module, transparently paged?
## how to build compiler
### outline
- collect functions & globals across compilation units
- assign IDs
- link together
- save map to compile loadable modules against
### compilation step
- parse constants.json
- parse S-expr via hy.load
- unit = Unit(globals={}, functions={})
- for TLF in top-level forms:
- match (define <name> <integer-literal>)
- match (define <prototype> <body> ...)
- create function = dict(name, argc, constants, locals, body)
- parse body[]
compile function body:
- for TLF in forms:
- match (set! <name> <value>)
- compile expression
- declared global?
- yes -> emit `setglobal` by name
- no -> find/assign variable name, emit `setlocal` by index
- else compile expression
- and drop result from stack -- unless last expression in the function
- if last form was a statement, push dummy return value on stack
compile expression:
- match literal
- find/assign constant index
- emit `getconst`
- match name
- is builtin constant?
- handle as a constant
- declared global?
- yes -> emit `getglobal` by name
- no -> find variable name, emit `getlocal` by index
- match (<callee> <args> ...)
- evaluate arguments
- emit `call`
### linking step
- parse builtins.json
- assign indexes to functions
- assign indexes to globals (+ resolve initial value which must be unique)
- resolve calls to `call:func`, `call:ext`
- resolve `getglobal`/`setglobal` to indexes
- in principle could also check arity of function calls
- concatenate function bytecode
## simplification opportunities
- validate arity at compile time, keep no trace of it at runtime
- built-in operators are actually externals (no problem if single data type)
- a function's constants spilled as locals on function entry
- it actually complicates function prologue & data strcturure. the only simplification is removal of `getconst`
# code
## builtins.json
{
"fill-rect": {id: 0, argc: 5},
"pause-frames": {id: 1, argc: 1},
}
## constants.json
{
"COLOR:BLACK": 0,
"COLOR:WHITE": 15,
"W": 320,
"H": 240
}
## test1.scm: tests function calls, constants, external calls
(define (draw-splash)
(fill-rect COLOR:WHITE 0 0 W H)
)
(define (main)
(draw-splash)
(pause-frames 100)
)
## test1.unit
(globals)
(functions
(function "draw-splash"
(argc 0)
(constants 15 0 320 240)
(locals 0)
(body
(getconst 0)
(getconst 1)
(getconst 1)
(getconst 2)
(getconst 3)
(call "fill-rect" 5)
)
)
(function "main"
(argc 0)
(constants 50)
(locals 0)
(body
(call "draw-splash" 0)
(getconst 0)
(call "pause-frames" 1)
)
)
)
## test1.prog
(bytecode
(getconst 0)
(getconst 1)
(getconst 1)
(getconst 2)
(getconst 3)
(call.ext 100 5)
(ret.void)
(call.func 0 0)
(getconst 0)
(call.ext 101 1)
(ret.void)
)
(constants
15 0 320 240
50
)
(globals) ;; init values for globals go here
(functions
(defun ;; draw-splash
(argc 0)
(locals 0)
(bytecode-offset ...)
(constants-offset 0)
)
(defun ;; main
(argc 0)
(locals 0)
(bytecode-offset ...)
(constants-offset 4)
)
)
(main-function 1)
## test2.scm: tests globals
(define *color* 0)
(define (draw-splash)
(fill-rect *color* 0 0 W H)
)
(define (main)
(draw-splash)
(pause-frames 10)
(set! *color* 1)
(draw-splash)
(pause-frames 10)
(set! *color* 2)
(draw-splash)
(pause-frames 10)
(set! *color* 3)
(draw-splash)
(pause-frames 10)
(set! *color* 4)
)
## test3.scm: tests locals, loops
(define (draw-splash color)
(fill-rect color 0 0 W H)
)
(define (main)
(define color 0)
(while (<= color 15)
(draw-splash)
(pause-frames 10)
(set! color (+ color 1))
)
)
## test4.scm
(define (main)
(prints "hello world")
)
## test-graphics.scm
(define res-logo 999)
(embed logo "logo.png")
;; might be problematic because palette must match
;; can enforce in code: https://stackoverflow.com/a/9833540
;; or require ahead-of-time conversion which will add a custom header
(define (main)
(set-video-mode W H)
(draw 0 0 logo)
;; resource is uncompressed image with custom header?
;; should we implement strings/buffers first?
;; Q: why not just (include "logo.png") ?
(set! res-logo (load-resource "logo"))
)
{
embeds: ["image:32:32:uhfifh3489hr8r3hohewh823heoddnuweh298309jdjnds-0ood0o;09jf"],
}
in code: embed index is known at compile, but must be relocated on link. let's call it `getembed`