;; what happens when our virtual machine starts up (= initialization-fns* (queue)) (def reset () (each f (as cons initialization-fns*) (f))) (mac on-init body `(enq (fn () ,@body) initialization-fns*)) ;; persisting and checking traces for each test (= traces* (queue)) (= trace-dir* ".traces/") (ensure-dir trace-dir*) (= curr-trace-file* nil) (on-init (awhen curr-trace-file* (tofile (+ trace-dir* it) (each (label trace) (as cons traces*) (pr label ": " trace)))) (= curr-trace-file* nil) (= traces* (queue))) (def new-trace (filename) (prn "== @filename") ;? ) (= curr-trace-file* filename)) (= dump-trace* nil) (def trace (label . args) (when (or (is dump-trace* t) (and dump-trace* (is label "-")) (and dump-trace* (pos label dump-trace*!whitelist)) (and dump-trace* (no dump-trace*!whitelist) (~pos label dump-trace*!blacklist))) (apply prn label ": " args)) (enq (list label (apply tostring:prn args)) traces*)) (redef tr args ; why am I still returning to prn when debugging? Will this help? (do1 nil (apply trace "-" args))) (def tr2 (msg arg) (tr msg arg) arg) (def check-trace-contents (msg expected-contents) (unless (trace-contents-match expected-contents) (prn "F - " msg) (prn " trace contents") (print-trace-contents-mismatch expected-contents))) (def trace-contents-match (expected-contents) (each (label msg) (as cons traces*) (when (and expected-contents (is label expected-contents.0.0) (posmatch expected-contents.0.1 msg)) (pop expected-contents))) (no expected-contents)) (def print-trace-contents-mismatch (expected-contents) (each (label msg) (as cons traces*) (whenlet (expected-label expected-msg) expected-contents.0 (if (and (is label expected-label) (posmatch expected-msg msg)) (do (pr " * ") (pop expected-contents)) (pr " ")) (pr label ": " msg))) (prn " couldn't find") (each (expected-label expected-msg) expected-contents (prn " ! " expected-label ": " expected-msg))) ;; virtual machine state ; things that a future assembler will need separate memory for: ; code; types; args channel (def clear () (= type* (table)) (= memory* (table)) (= function* (table)) ) (enq clear initialization-fns*) (on-init (= type* (obj ; Each type must be scalar or array, sum or product or primitive type (obj size 1) ; implicitly scalar and primitive type-address (obj size 1 address t elem '(type)) type-array (obj array t elem '(type)) type-array-address (obj size 1 address t elem '(type-array)) location (obj size 1 address t elem '(location)) ; assume it points to an atom integer (obj size 1) boolean (obj size 1) boolean-address (obj size 1 address t elem '(boolean)) byte (obj size 1) byte-address (obj size 1 address t elem '(byte)) string (obj array t elem '(byte)) ; inspired by Go string-address (obj size 1 address t elem '(string)) string-address-address (obj size 1 address t elem '(string-address)) string-address-array (obj array t elem '(string-address)) string-address-array-address (obj size 1 address t elem '(string-address-array)) character (obj size 1) ; int32 like a Go rune character-address (obj size 1 address t elem '(character)) ; isolating function calls scope (obj array t elem '(location)) ; by convention index 0 points to outer scope scope-address (obj size 1 address t elem '(scope)) ; arrays consist of an integer length followed by the right number of elems integer-array (obj array t elem '(integer)) integer-array-address (obj size 1 address t elem '(integer-array)) integer-array-address-address (obj size 1 address t elem '(integer-array-address)) integer-address (obj size 1 address t elem '(integer)) ; pointer to int integer-address-address (obj size 1 address t elem '(integer-address)) ; and-records consist of a series of elems, corresponding to a list of types integer-boolean-pair (obj size 2 and-record t elems '((integer) (boolean)) fields '(int bool)) integer-boolean-pair-address (obj size 1 address t elem '(integer-boolean-pair)) integer-boolean-pair-array (obj array t elem '(integer-boolean-pair)) integer-boolean-pair-array-address (obj size 1 address t elem '(integer-boolean-pair-array)) integer-integer-pair (obj size 2 and-record t elems '((integer) (integer))) integer-point-pair (obj size 2 and-record t elems '((integer) (integer-integer-pair))) integer-point-pair-address (obj size 1 address t elem '(integer-point-pair)) integer-point-pair-address-address (obj size 1 address t elem '(integer-point-pair-address)) ; tagged-values are the foundation of dynamic types tagged-value (obj size 2 and-record t elems '((type) (location)) fields '(type payload)) tagged-value-address (obj size 1 address t elem '(tagged-value)) tagged-value-array (obj array t elem '(tagged-value)) tagged-value-array-address (obj size 1 address t elem '(tagged-value-array)) tagged-value-array-address-address (obj size 1 address t elem '(tagged-value-array-address)) ; heterogeneous lists list (obj size 2 and-record t elems '((tagged-value) (list-address)) fields '(car cdr)) list-address (obj size 1 address t elem '(list)) list-address-address (obj size 1 address t elem '(list-address)) ; parallel routines use channels to synchronize channel (obj size 3 and-record t elems '((integer) (integer) (tagged-value-array-address)) fields '(first-full first-free circular-buffer)) channel-address (obj size 1 address t elem '(channel)) ; editor line (obj array t elem '(character)) line-address (obj size 1 address t elem '(line)) line-address-address (obj size 1 address t elem '(line-address)) screen (obj array t elem '(line-address)) screen-address (obj size 1 address t elem '(screen)) ))) ;; managing concurrent routines ; routine = runtime state for a serial thread of execution (def make-routine (fn-name . args) (annotate 'routine (obj alloc 1000 call-stack (list (obj fn-name fn-name pc 0 args args caller-arg-idx 0))))) (defextend empty (x) (isa x 'routine) (no rep.x!call-stack)) (def stack (routine) ((rep routine) 'call-stack)) (mac push-stack (routine op) `(push (obj fn-name ,op pc 0 caller-arg-idx 0) ((rep ,routine) 'call-stack))) (mac pop-stack (routine) `(pop ((rep ,routine) 'call-stack))) (def top (routine) stack.routine.0) (def body (routine (o idx 0)) (function* stack.routine.idx!fn-name)) (mac pc (routine (o idx 0)) ; assignable `((((rep ,routine) 'call-stack) ,idx) 'pc)) (mac caller-arg-idx (routine (o idx 0)) ; assignable `((((rep ,routine) 'call-stack) ,idx) 'caller-arg-idx)) (mac caller-args (routine) ; assignable `((((rep ,routine) 'call-stack) 0) 'args)) (mac caller-operands (routine) ; assignable `((((rep ,routine) 'call-stack) 0) 'caller-operands)) (mac caller-results (routine) ; assignable `((((rep ,routine) 'call-stack) 0) 'caller-results)) (mac results (routine) ; assignable `((((rep ,routine) 'call-stack) 0) 'results)) (def waiting-for-exact-cycle? (routine) (is 'literal rep.routine!sleep.1)) (def ready-to-wake-up (routine) (assert no.routine*) (if (is 'literal rep.routine!sleep.1) (> curr-cycle* rep.routine!sleep.0) (~is rep.routine!sleep.1 (memory* rep.routine!sleep.0)))) (on-init (= running-routines* (queue)) ; simple round-robin scheduler ; set of sleeping routines; don't modify routines while they're in this table (= sleeping-routines* (table)) (= completed-routines* nil) ; audit trail (= routine* nil) (= abort-routine* (parameter nil)) (= curr-cycle* 0) (= scheduling-interval* 500) (= scheduler-switch-table* nil) ; hook into scheduler for tests ) ; like arc's 'point' but you can also call ((abort-routine*)) in nested calls (mac routine-mark body (w/uniq (g p) `(ccc (fn (,g) (parameterize abort-routine* (fn ((o ,p)) (,g ,p)) ,@body))))) (def run fn-names (freeze-functions) (= traces* (queue)) (each it fn-names (enq make-routine.it running-routines*)) (while (~empty running-routines*) (= routine* deq.running-routines*) (trace "schedule" top.routine*!fn-name) (routine-mark (run-for-time-slice scheduling-interval*)) (update-scheduler-state) ;? (tr "after run iter " running-routines*) ;? (tr "after run iter " empty.running-routines*) )) ; prepare next iteration of round-robin scheduler ; ; state before: routine* running-routines* sleeping-routines* ; state after: running-routines* (with next routine to run at head) sleeping-routines* ; ; responsibilities: ; add routine* to either running-routines* or sleeping-routines* or completed-routines* ; wake up any necessary sleeping routines (either by time or on a location) ; detect deadlock: kill all sleeping routines when none can be woken (def update-scheduler-state () ;? (trace "schedule" curr-cycle*) (when routine* (if rep.routine*!sleep (do (trace "schedule" "pushing " top.routine*!fn-name " to sleep queue") (set sleeping-routines*.routine*)) (~empty routine*) (do (trace "schedule" "scheduling " top.routine*!fn-name " for further processing") (enq routine* running-routines*)) :else (do (trace "schedule" "done with routine") (push routine* completed-routines*))) (= routine* nil)) ;? (tr 111) (each (routine _) canon.sleeping-routines* (when (ready-to-wake-up routine) (trace "schedule" "waking up " top.routine!fn-name) (wipe sleeping-routines*.routine) ; do this before modifying routine (wipe rep.routine!sleep) (++ pc.routine) (enq routine running-routines*))) ;? (tr 112) (when (empty running-routines*) (whenlet exact-sleeping-routines (keep waiting-for-exact-cycle? keys.sleeping-routines*) (let next-wakeup-cycle (apply min (map [rep._!sleep 0] exact-sleeping-routines)) (= curr-cycle* (+ 1 next-wakeup-cycle)) (trace "schedule" "skipping to cycle " curr-cycle*) (update-scheduler-state)))) ;? (tr 113) (detect-deadlock) ;? (tr 114) ) (def detect-deadlock () (when (and (empty running-routines*) (~empty sleeping-routines*) (~some 'literal (map (fn(_) rep._!sleep.1) keys.sleeping-routines*))) (each (routine _) sleeping-routines* (wipe sleeping-routines*.routine) (= rep.routine!error "deadlock detected") (push routine completed-routines*)))) (def die (msg) (tr "die: " msg) (= rep.routine*!error msg) (= rep.routine*!stack-trace rep.routine*!call-stack) (wipe rep.routine*!call-stack) (iflet abort-continuation (abort-routine*) (abort-continuation))) ;; running a single routine ; value of an arg or oarg, stripping away all metadata ; wish I could have this flag an error when arg is incorrectly formed (mac v (operand) ; for value `((,operand 0) 0)) ; routines consist of instrs ; instrs consist of oargs, op and args (def parse-instr (instr) (iflet delim (pos '<- instr) (list (cut instr 0 delim) ; oargs (v (instr (+ delim 1))) ; op (cut instr (+ delim 2))) ; args (list nil (v car.instr) cdr.instr))) (def metadata (operand) cdr.operand) (def ty (operand) (cdr operand.0)) (def typeinfo (operand) (or (type* ty.operand.0) (err "unknown type @(tostring prn.operand)"))) ; operand accessors (def nondummy (operand) ; precondition for helpers below (~is '_ operand)) ; just for convenience, 'new' instruction sometimes takes a raw string and ; allocates just enough space to store it (def not-raw-string (operand) (~isa operand 'string)) (def address? (operand) (or (is ty.operand.0 'location) typeinfo.operand!address)) ($:require "charterm/main.rkt") ($:require graphics/graphics) (= Viewport nil) ; run instructions from 'routine*' for 'time-slice' (def run-for-time-slice (time-slice) (point return (for ninstrs 0 (< ninstrs time-slice) (++ ninstrs) (if (empty body.routine*) (err "@stack.routine*.0!fn-name not defined")) ; falling out of end of function = implicit reply (while (>= pc.routine* (len body.routine*)) (pop-stack routine*) (if empty.routine* (return ninstrs)) (++ pc.routine*)) (++ curr-cycle*) (trace "run" "-- " int-canon.memory*) (trace "run" curr-cycle* " " top.routine*!fn-name " " pc.routine* ": " (body.routine* pc.routine*)) ;? (trace "run" routine*) (when (atom (body.routine* pc.routine*)) ; label (when (aand scheduler-switch-table* (alref it (body.routine* pc.routine*))) (++ pc.routine*) (trace "run" "context-switch forced " abort-routine*) ((abort-routine*))) (++ pc.routine*) (continue)) (let (oarg op arg) (parse-instr (body.routine* pc.routine*)) (let results (case op ; arithmetic add (do (trace "add" (m arg.0) " " (m arg.1)) (+ (m arg.0) (m arg.1)) ) subtract (- (m arg.0) (m arg.1)) multiply (* (m arg.0) (m arg.1)) divide (/ (real (m arg.0)) (m arg.1)) divide-with-remainder (list (trunc:/ (m arg.0) (m arg.1)) (mod (m arg.0) (m arg.1))) ; boolean and (and (m arg.0) (m arg.1)) or (or (m arg.0) (m arg.1)) not (not (m arg.0)) ; comparison equal (is (m arg.0) (m arg.1)) not-equal (do (trace "neq" (m arg.0) " " (m arg.1)) (~is (m arg.0) (m arg.1)) ) less-than (< (m arg.0) (m arg.1)) greater-than (> (m arg.0) (m arg.1)) lesser-or-equal (<= (m arg.0) (m arg.1)) greater-or-equal (>= (m arg.0) (m arg.1)) ; control flow jump (do (= pc.routine* (+ 1 pc.routine* (v arg.0))) (trace "jump" "jumping to " pc.routine*) (continue)) jump-if (let flag (m arg.0) (trace "jump" "checking that " flag " is t") (when (is t flag) (= pc.routine* (+ 1 pc.routine* (v arg.1))) (trace "jump" "jumping to " pc.routine*) (continue))) jump-unless ; convenient helper (let flag (m arg.0) (trace "jump" "checking that " flag " is not t") (unless (is t flag) (= pc.routine* (+ 1 pc.routine* (v arg.1))) (trace "jump" "jumping to " pc.routine*) (continue))) ; data management: scalars, arrays, and-records (structs) copy (m arg.0) get (with (operand (canonize arg.0) idx (v arg.1)) (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") (m `((,(apply + v.operand (map (fn(x) (sizeof `((_ ,@x)))) (firstn idx typeinfo.operand!elems))) ,@typeinfo.operand!elems.idx) (global)))) get-address (with (operand (canonize arg.0) idx (v arg.1)) (assert (iso '(offset) (ty arg.1)) "record index @arg.1 must have type 'offset'") (assert (< -1 idx (len typeinfo.operand!elems)) "@idx is out of bounds of record @operand") (apply + v.operand (map (fn(x) (sizeof `((_ ,@x)))) (firstn idx typeinfo.operand!elems)))) index (withs (operand (canonize arg.0) elemtype typeinfo.operand!elem idx (m arg.1)) (unless (< -1 idx array-len.operand) (die "@idx is out of bounds of array @operand")) (m `((,(+ v.operand 1 ; for array size (* idx (sizeof `((_ ,@elemtype))))) ,@elemtype) (global)))) index-address (withs (operand (canonize arg.0) elemtype typeinfo.operand!elem idx (m arg.1)) (unless (< -1 idx array-len.operand) (die "@idx is out of bounds of array @operand")) (+ v.operand 1 ; for array size (* idx (sizeof `((_ ,@elemtype)))))) new (if (isa arg.0 'string) ; special-case: allocate space for a literal string (new-string arg.0) (let type (v arg.0) (assert (iso '(literal) (ty arg.0)) "new: second arg @arg.0 must be literal") (if (no type*.type) (err "no such type @type")) ; todo: initialize memory. currently racket does it for us (if type*.type!array (new-array type (m arg.1)) (new-scalar type)))) sizeof (sizeof `((_ ,(m arg.0)))) length (let base arg.0 (if (or typeinfo.base!array address?.base) array-len.base -1)) ; tagged-values require one primitive save-type (annotate 'record `(,((ty arg.0) 0) ,(m arg.0))) ; multiprocessing run (run (v arg.0)) fork (let routine (apply make-routine (v car.arg) (map m cdr.arg)) ;? (tr "before: " rep.routine*!alloc) (= rep.routine!alloc rep.routine*!alloc) (++ rep.routine*!alloc 1000) ;? (tr "after: " rep.routine*!alloc " " rep.routine!alloc) (enq routine running-routines*)) ; todo: errors should stall a process and let its parent ; inspect it assert (assert (m arg.0)) sleep (let operand arg.0 ;? (tr "sleep " operand) ; store sleep as either ( literal) or ( ) (if (is ty.operand.0 'literal) (let delay v.operand (trace "run" "sleeping until " (+ curr-cycle* delay)) (= rep.routine*!sleep `(,(+ curr-cycle* delay) literal))) (do ;? (tr "blocking on " operand " -> " (addr operand)) (= rep.routine*!sleep `(,addr.operand ,m.operand)))) ((abort-routine*))) ; text interaction clear-screen (do1 nil ($.charterm-clear-screen)) clear-line (do1 nil ($.charterm-clear-line)) cursor (do1 nil ($.charterm-cursor (m arg.0) (m arg.1))) print-primitive (do1 nil ((if ($.current-charterm) $.charterm-display pr) (m arg.0))) read-key (and ($.charterm-byte-ready?) ($.charterm-read-key)) bold-mode (do1 nil ($.charterm-bold)) non-bold-mode (do1 nil ($.charterm-normal)) console-on (do1 nil (if (no ($.current-charterm)) ($.open-charterm))) console-off (do1 nil (if ($.current-charterm) ($.close-charterm))) ; graphics graphics-on (do1 nil ($.open-graphics) (= Viewport ($.open-viewport "practice" 300 300))) graphics-off (do1 nil ($.close-graphics) (= Viewport nil)) mouse-position (aif ($.ready-mouse-click Viewport) (let posn ($.mouse-click-posn it) (list (annotate 'record (list ($.posn-x posn) ($.posn-y posn))) t)) (list nil nil)) ; user-defined functions next-input (let idx caller-arg-idx.routine* (++ caller-arg-idx.routine*) (trace "arg" arg " " idx " " caller-args.routine*) (if (len> caller-args.routine* idx) (list caller-args.routine*.idx t) (list nil nil))) input (do (assert (iso '(literal) (ty arg.0))) (= caller-arg-idx.routine* (v arg.0)) (let idx caller-arg-idx.routine* (++ caller-arg-idx.routine*) (trace "arg" arg " " idx " " caller-args.routine*) (if (len> caller-args.routine* idx) (list caller-args.routine*.idx t) (list nil nil)))) ; type and otype won't always easily compile. be careful. type (ty (caller-operands.routine* (v arg.0))) otype (ty (caller-results.routine* (v arg.0))) prepare-reply (prepare-reply arg) reply (do (when arg (prepare-reply arg)) (let results results.routine* (pop-stack routine*) (if empty.routine* (return ninstrs)) (let (caller-oargs _ _) (parse-instr (body.routine* pc.routine*)) (trace "reply" arg " " caller-oargs) (each (dest val) (zip caller-oargs results) (when nondummy.dest (trace "reply" val " => " dest) (setm dest val)))) (++ pc.routine*) (while (>= pc.routine* (len body.routine*)) (pop-stack routine*) (when empty.routine* (return ninstrs)) (++ pc.routine*)) (continue))) ; else try to call as a user-defined function (do (if function*.op (with (callee-args (accum yield (each a arg (yield (m a)))) callee-operands (accum yield (each a arg (yield a))) callee-results (accum yield (each a oarg (yield a)))) (push-stack routine* op) (= caller-args.routine* callee-args) (= caller-operands.routine* callee-operands) (= caller-results.routine* callee-results)) (err "no such op @op")) (continue)) ) ; opcode generated some 'results' ; copy to output args (if (acons results) (each (dest val) (zip oarg results) (unless (is dest '_) (trace "run" val " => " dest) (setm dest val))) (when oarg ; must be a list (trace "run" results " => " oarg.0) (setm oarg.0 results))) ) (++ pc.routine*))) (return time-slice))) (def prepare-reply (args) (= results.routine* (accum yield (each a args (yield (m a)))))) ; helpers for memory access respecting ; immediate addressing - 'literal' and 'offset' ; direct addressing - default ; indirect addressing - 'deref' ; relative addressing - if routine* has 'default-scope' (def m (loc) ; read memory, respecting metadata (point return (if (in ty.loc.0 'literal 'offset) (return v.loc)) (when (is v.loc 'default-scope) (return rep.routine*!call-stack.0!default-scope)) (trace "m" loc) (assert (isa v.loc 'int) "addresses must be numeric (problem in convert-names?) @loc") (with (n sizeof.loc addr addr.loc) ;? (trace "m" "reading " n " locations starting at " addr) (if (is 1 n) memory*.addr :else (annotate 'record (map memory* (addrs addr n))))))) (def setm (loc val) ; set memory, respecting metadata (point return (when (is v.loc 'default-scope) (assert (is 1 sizeof.loc) "can't store compounds in default-scope @loc") (= rep.routine*!call-stack.0!default-scope val) (return)) (assert (isa v.loc 'int) "can't store to non-numeric address (problem in convert-names?)") (trace "setm" loc " <= " val) (with (n (if (isa val 'record) (len rep.val) 1) addr addr.loc typ typeof.loc) (trace "setm" "size of " loc " is " n) (assert n "setm: can't compute type of @loc") (assert addr "setm: null pointer @loc") (if (is 1 n) (do (assert (~isa val 'record) "setm: record of size 1 @(tostring prn.val)") (trace "setm" loc ": setting " addr " to " val) (= memory*.addr val)) (do (if type*.typ!array ; size check for arrays (when (~is n (+ 1 ; array length (* rep.val.0 (sizeof `((_ ,@type*.typ!elem)))))) (die "writing invalid array @(tostring prn.val)")) ; size check for non-arrays (when (~is sizeof.loc n) (die "writing to incorrect size @(tostring prn.val) => @loc"))) (let addrs (addrs addr n) (each (dest src) (zip addrs rep.val) (trace "setm" loc ": setting " dest " to " src) (= memory*.dest src)))))))) (def typeof (operand) (let loc absolutize.operand (while (pos '(deref) metadata.loc) (zap deref loc)) ty.loc.0)) (def addr (operand) ;? (prn 211 " " operand) (let loc absolutize.operand ;? (prn 212 " " loc) (while (pos '(deref) metadata.loc) ;? (prn 213 " " loc) (zap deref loc)) ;? (prn 214 " " loc) v.loc)) (def addrs (n sz) (accum yield (repeat sz (yield n) (++ n)))) (def canonize (operand) (ret operand (zap absolutize operand) (while (pos '(deref) metadata.operand) (zap deref operand)))) (def array-len (operand) (trace "array-len" operand) (zap canonize operand) (if typeinfo.operand!array (m `((,v.operand integer) ,@metadata.operand)) :else (err "can't take len of non-array @operand"))) (def sizeof (x) (trace "sizeof" x) (assert acons.x) (zap canonize x) (point return ;? (tr "sizeof: checking @x for array") (when typeinfo.x!array ;? (tr "sizeof: @x is an array") (assert (~is '_ v.x) "sizeof: arrays require a specific variable") (return (+ 1 (* array-len.x (sizeof `((_ ,@typeinfo.x!elem))))))) ;? (tr "sizeof: not an array") (when typeinfo.x!and-record ;? (tr "sizeof: @x is an and-record") (return (sum idfn (accum yield (each elem typeinfo.x!elems (yield (sizeof `((_ ,@elem))))))))) ;? (tr "sizeof: @x is a primitive") (return typeinfo.x!size))) (def absolutize (operand) (if (no routine*) operand (is '_ v.operand) operand (pos '(global) metadata.operand) operand :else (iflet base rep.routine*!call-stack.0!default-scope ;? (do (prn 313 " " operand " " base) (if (< v.operand memory*.base) `((,(+ v.operand base) ,@(cdr operand.0)) ,@metadata.operand (global)) (die "no room for var @operand in routine of size @memory*.base")) ;? ) operand))) (def deref (operand) (assert (pos '(deref) metadata.operand)) (assert address?.operand) (cons `(,(memory* v.operand) ,@typeinfo.operand!elem) (drop-one '(deref) metadata.operand))) (def drop-one (f x) (when acons.x ; proper lists only (if (testify.f car.x) cdr.x (cons car.x (drop-one f cdr.x))))) ; memory allocation (def new-scalar (type) ;? (tr "new scalar: @type") (ret result rep.routine*!alloc (++ rep.routine*!alloc (sizeof `((_ ,type)))))) (def new-array (type size) ;? (tr "new array: @type @size") (ret result rep.routine*!alloc (++ rep.routine*!alloc (+ 1 (* (sizeof `((_ ,@type*.type!elem))) size))) (= memory*.result size))) (def new-string (literal-string) ;? (tr "new string: @literal-string") (ret result rep.routine*!alloc (= (memory* rep.routine*!alloc) len.literal-string) (++ rep.routine*!alloc) (each c literal-string (= (memory* rep.routine*!alloc) c) (++ rep.routine*!alloc)))) ;; desugar structured assembly based on blocks (def convert-braces (instrs) ;? (prn "convert-braces " instrs) (let locs () ; list of information on each brace: (open/close pc) (let pc 0 (loop (instrs instrs) (each instr instrs ;? (tr instr) (if (or atom.instr (~is 'begin instr.0)) ; label or regular instruction (do (trace "c{0" pc " " instr " -- " locs) (++ pc)) ; hack: racket replaces braces with parens, so we need the ; keyword 'begin' to delimit blocks. ; ultimately there'll be no nesting and braces will just be ; in an instr by themselves. :else ; brace (do (push `(open ,pc) locs) (recur cdr.instr) (push `(close ,pc) locs)))))) (zap rev locs) ;? (tr "-") (with (pc 0 stack ()) ; elems are pcs (accum yield (loop (instrs instrs) (each instr instrs ;? (tr "- " instr) (point continue (when (atom instr) ; label (yield instr) (++ pc) (continue)) (when (is car.instr 'begin) (push pc stack) (recur cdr.instr) (pop stack) (continue)) (let (oarg op arg) (parse-instr instr) (trace "c{1" pc " " op " " oarg) (case op break (do (assert (is oarg nil) "break: can't take oarg in @instr") (yield `(((jump)) ((,(close-offset pc locs (and arg (v arg.0))) offset))))) break-if (do (assert (is oarg nil) "break-if: can't take oarg in @instr") (yield `(((jump-if)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset))))) break-unless (do (assert (is oarg nil) "break-unless: can't take oarg in @instr") (yield `(((jump-unless)) ,arg.0 ((,(close-offset pc locs (and cdr.arg (v arg.1))) offset))))) loop (do (assert (is oarg nil) "loop: can't take oarg in @instr") (yield `(((jump)) ((,(open-offset pc stack (and arg (v arg.0))) offset))))) loop-if (do (trace "cvt0" "loop-if: " instr " => " (- stack.0 1)) (assert (is oarg nil) "loop-if: can't take oarg in @instr") (yield `(((jump-if)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset))))) loop-unless (do (trace "cvt0" "loop-if: " instr " => " (- stack.0 1)) (assert (is oarg nil) "loop-unless: can't take oarg in @instr") (yield `(((jump-unless)) ,arg.0 ((,(open-offset pc stack (and cdr.arg (v arg.1))) offset))))) ;else (yield instr))) (++ pc)))))))) (def close-offset (pc locs nblocks) (or= nblocks 1) ;? (tr nblocks) (point return ;? (tr "close " pc " " locs) (let stacksize 0 (each (state loc) locs (point continue ;? (tr stacksize "/" done " " state " " loc) (when (<= loc pc) (continue)) ;? (tr "process " stacksize loc) (if (is 'open state) (++ stacksize) (-- stacksize)) ; last time ;? (tr "process2 " stacksize loc) (when (is stacksize (* -1 nblocks)) ;? (tr "close now " loc) (return (- loc pc 1)))))))) (def open-offset (pc stack nblocks) (or= nblocks 1) (- (stack (- nblocks 1)) 1 pc)) ;; convert jump targets to offsets (def convert-labels (instrs) ;? (tr "convert-labels " instrs) (let labels (table) (let pc 0 (each instr instrs (when (~acons instr) ;? (tr "label " pc) (= labels.instr pc)) (++ pc))) (let pc 0 (each instr instrs (when (and acons.instr (acons car.instr) (in (v car.instr) 'jump 'jump-if 'jump-unless)) (each arg cdr.instr ;? (tr "trying " arg " " ty.arg ": " v.arg " => " (labels v.arg)) (when (and (is ty.arg.0 'offset) (isa v.arg 'sym) (labels v.arg)) (= v.arg (- (labels v.arg) pc 1))))) (++ pc)))) instrs) ;; convert symbolic names to raw memory locations (def convert-names (instrs) ;? (tr "convert-names " instrs) (with (location (table) isa-field (table)) (let idx 1 (each instr instrs (point continue (when atom.instr (continue)) (trace "cn0" instr " " canon.location " " canon.isa-field) (let (oargs op args) (parse-instr instr) ;? (tr "about to rename args") ; rename args (if (in op 'get 'get-address) (with (basetype (typeof args.0) field (v args.1)) (assert type*.basetype!and-record "get on non-record @args.0") (trace "cn0" "field-access @field in @args.0 of type @basetype") (when (isa field 'sym) (assert (or (~location field) isa-field.field) "field @args.1 is also a variable") (when (~location field) (trace "cn0" "new field; computing location") ;? (tr "aa " type*.basetype) (assert type*.basetype!fields "no field names available for @instr") ;? (tr "bb") (iflet idx (pos field type*.basetype!fields) (do (set isa-field.field) (trace "cn0" "field location @idx") (= location.field idx)) (assert nil "couldn't find field in @instr"))))) (each arg args (when (and nondummy.arg not-raw-string.arg) (assert (~isa-field v.arg) "arg @arg is also a field name") (when (maybe-add arg location idx) (err "use before set: @arg"))))) ;? (tr "about to rename oargs") ; rename oargs (each arg oargs (trace "cn0" "checking " arg) (when (and nondummy.arg not-raw-string.arg) (assert (~isa-field v.arg) "oarg @arg is also a field name") (when (maybe-add arg location idx) (trace "cn0" "location for arg " arg ": " idx) ; todo: can't allocate arrays on the stack (++ idx (sizeof `((_ ,@ty.arg))))))))))) (trace "cn1" "update names " canon.location " " canon.isa-field) (each instr instrs (when (acons instr) (let (oargs op args) (parse-instr instr) (each arg args (when (and nondummy.arg not-raw-string.arg (location v.arg)) (zap location v.arg))) (each arg oargs (when (and nondummy.arg not-raw-string.arg (location v.arg)) (zap location v.arg)))))) instrs)) (def maybe-add (arg location idx) (trace "maybe-add" arg) (when (and nondummy.arg (~in ty.arg.0 'literal 'offset 'fn) (~location v.arg) (isa v.arg 'sym) (~in v.arg 'nil 'default-scope) (~pos '(global) metadata.arg)) (= (location v.arg) idx))) ;; literate tangling system for reordering code (def convert-quotes (instrs) (let deferred (queue) (each instr instrs (when (acons instr) (case instr.0 defer (let (q qinstrs) instr.1 (assert (is 'make-br-fn q) "defer: first arg must be [quoted]") (each qinstr qinstrs (enq qinstr deferred)))))) (accum yield (each instr instrs (if atom.instr ; label (yield instr) (is instr.0 'defer) nil ; skip (is instr.0 'reply) (do (when cdr.instr ; return values (= instr.0 'prepare-reply) (yield instr)) (each instr (as cons deferred) (yield instr)) (yield '(reply))) :else (yield instr))) (each instr (as cons deferred) (yield instr))))) (on-init (= before* (table)) ; label -> queue of fragments (= after* (table))) ; label -> list of fragments ; see add-code below for adding to before* and after* (def insert-code (instrs (o name)) ;? (tr "insert-code " instrs) (loop (instrs instrs) (accum yield (each instr instrs (if (and (acons instr) (~is 'begin car.instr)) ; simple instruction (yield instr) (and (acons instr) (is 'begin car.instr)) ; block (yield `{begin ,@(recur cdr.instr)}) (atom instr) ; label (do ;? (prn "tangling " instr) (each fragment (as cons (or (and name (before* (sym:string name '/ instr))) before*.instr)) (each instr fragment (yield instr))) (yield instr) (each fragment (or (and name (after* (sym:string name '/ instr))) after*.instr) (each instr fragment (yield instr))))))))) ;; loading code into the virtual machine (def add-code (forms) (each (op . rest) forms (case op ; function [ ] ; don't apply our lightweight tools just yet function! (let (name (_make-br-fn body)) rest (assert (is 'make-br-fn _make-br-fn)) (= function*.name body)) function (let (name (_make-br-fn body)) rest (assert (is 'make-br-fn _make-br-fn)) (= function*.name (join body function*.name))) ; and-record [ ] and-record (let (name (_make-br-fn fields)) rest (assert (is 'make-br-fn _make-br-fn)) (let fields (map tokenize-arg fields) (= type*.name (obj size len.fields and-record t ; dump all metadata for now except field name and type elems (map cdar fields) fields (map caar fields))))) ; before