; Mu: An exploration on making the global structure of programs more accessible.
;
; "Is it a language, or an operating system, or a virtual machine? Mu."
; (with apologies to Robert Pirsig: http://en.wikipedia.org/wiki/Mu_%28negative%29#In_popular_culture)
;
;; Motivation
;
; I want to live in a world where I can have an itch to tweak a program, clone
; its open-source repository, orient myself on how it's organized, and make
; the simple change I envisioned, all in an afternoon. This codebase tries to
; make this possible for its readers. (More details: http://akkartik.name/about)
;
; What helps comprehend the global structure of programs? For starters, let's
; enumerate what doesn't: idiomatic code, adherence to a style guide or naming
; convention, consistent indentation, API documentation for each class, etc.
; These conventional considerations improve matters in the small, but don't
; help understand global organization. They help existing programmers manage
; day-to-day operations, but they can't turn outsider programmers into
; insiders. (Elaboration: http://akkartik.name/post/readable-bad)
;
; In my experience, two things have improved matters so far: version control
; and automated tests. Version control lets me rewind back to earlier, simpler
; times when the codebase was simpler, when its core skeleton was easier to
; ascertain. Indeed, arguably what came first is by definition the skeleton of
; a program, modulo major rewrites. Once you understand the skeleton, it
; becomes tractable to 'play back' later major features one by one. (Previous
; project that fleshed out this idea: http://akkartik.name/post/wart-layers)
;
; The second and biggest boost to comprehension comes from tests. Tests are
; good for writers for well-understood reasons: they avoid regressions, and
; they can influence code to be more decoupled and easier to change. In
; addition, tests are also good for the outsider reader because they permit
; active reading. If you can't build a program and run its tests it can't help
; you understand it. It hangs limp at best, and might even be actively
; misleading. If you can run its tests, however, it comes alive. You can step
; through scenarios in a debugger. You can add logging and scan logs to make
; sense of them. You can run what-if scenarios: "why is this line not written
; like this?" Make a change, rerun tests: "Oh, that's why." (Elaboration:
; http://akkartik.name/post/literate-programming)
;
; However, tests are only useful to the extent that they exist. Think back to
; your most recent codebase. Do you feel comfortable releasing a new version
; just because the tests pass? I'm not aware of any such project. There's just
; too many situations envisaged by the authors that were never encoded in a
; test. Even disciplined authors can't test for performance or race conditions
; or fault tolerance. If a line is phrased just so because of some subtle
; performance consideration, it's hard to communicate to newcomers.
;
; This isn't an arcane problem, and it isn't just a matter of altruism. As
; more and more such implicit considerations proliferate, and as the original
; authors are replaced by latecomers for day-to-day operations, knowledge is
; actively forgotten and lost. The once-pristine codebase turns into legacy
; code that is hard to modify without expensive and stress-inducing
; regressions.
;
; How to write tests for performance, fault tolerance, race conditions, etc.?
; How can we state and verify that a codepath doesn't ever perform memory
; allocation, or write to disk? It requires better, more observable primitives
; than we currently have. Modern operating systems have their roots in the
; 70s. Their interfaces were not designed to be testable. They provide no way
; to simulate a full disk, or a specific sequence of writes from different
; threads. We need something better.
;
; This project tries to move, groping, towards that 'something better', a
; platform that is both thoroughly tested and allows programs written for it
; to be thoroughly tested. It tries to answer the question:
;
; If Denis Ritchie and Ken Thompson were to set out today to co-design unix
; and C, knowing what we know about automated tests, what would they do
; differently?
;
; To try to impose *some* constraints on this gigantic yak-shave, we'll try to
; keep both language and OS as simple as possible, focused entirely on
; permitting more kinds of tests, on first *collecting* all the information
; about implicit considerations in some form so that readers and tools can
; have at least some hope of making sense of it.
;
; The initial language will be just assembly. We'll try to make it convenient
; to program in with some simple localized rewrite rules inspired by lisp
; macros and literate programming. Programmers will have to do their own
; memory management and register allocation, but we'll provide libraries to
; help with them.
;
; The initial OS will provide just memory management and concurrency
; primitives. No users or permissions (we don't live on mainframes anymore),
; no kernel- vs user-mode, no virtual memory or process abstraction, all
; threads sharing a single address space (use VMs for security and
; sandboxing). The only use case we care about is getting a test harness to
; run some code, feed it data through blocking channels, stop it and observe
; its internals. The code under test is expected to cooperate in such testing,
; by logging important events for the test harness to observe. (More info:
; http://akkartik.name/post/tracing-tests)
;
; The common thread here is elimination of abstractions, and it's not an
; accident. Abstractions help insiders manage the evolution of a codebase, but
; they actively hinder outsiders in understanding it from scratch. This
; matters, because the funnel to turn outsiders into insiders is critical to
; the long-term life of a codebase. Perhaps authors should raise their
; estimation of the costs of abstraction, and go against their instincts for
; introducing it. That's what I'll be trying to do: question every abstraction
; before I introduce it. We'll see how it goes.
; ---
;; Getting started
;
; Mu is currently built atop Racket and Arc, but this is temporary and
; contingent. We want to keep our options open, whether to port to a different
; host language, and easy to rewrite to native code for any platform. So we'll
; try to avoid 'cheating': relying on the host platform for advanced
; functionality.
;
; Other than that, we'll say no more about the code, and focus in the rest of
; this file on the scenarios the code cares about.
(selective-load "mu.arc" section-level)
(ero "running tests in mu.ar.c.t (takes ~30s)")
;? (quit)
(set allow-raw-addresses*)
(section 20
; Our language is assembly-like in that functions consist of series of
; statements, and statements consist of an operation and its arguments (input
; and output).
;
; oarg1, oarg2, ... <- op arg1, arg2, ...
;
; Args must be atomic, like an integer or a memory address, they can't be
; expressions doing arithmetic or function calls. But we can have any number
; of them.
;
; Since we're building on lisp, our code samples won't look quite like the
; idealized syntax above. For now they will look like this:
;
; (function f [
; (oarg1 oarg2 ... <- op arg1 arg2 ...)
; ...
; ...
; ])
;
; Each arg/oarg can contain metadata separated by slashes and colons. In this
; first example below, the only metadata is types: 'integer' for a memory
; location containing an integer, and 'literal' for a value included directly
; in code. (Assembly languages traditionally call them 'immediate' operands.)
; In the future a simple tool will check that the types line up as expected in
; each op. A different tool might add types where they aren't provided.
; Instead of a monolithic compiler I want to build simple, lightweight tools
; that can be combined in various ways, say for using different typecheckers
; in different subsystems.
;
; In our tests we'll define such mu functions using a call to 'add-code', so
; look for it when reading the code examples. Everything outside 'add-code' is
; just test-harness details that can be skipped at first.
(reset)
;? (set dump-trace*)
(new-trace "literal")
(add-code
'((function main [
(1:integer <- copy 23:literal)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~is memory*.1 23)
(prn "F - 'copy' writes its lone 'arg' after the instruction name to its lone 'oarg' or output arg before the arrow. After this test, the value 23 is stored in memory address 1."))
;? (reset) ;? 2
;? (quit) ;? 2
; Our basic arithmetic ops can operate on memory locations or literals.
; (Ignore hardware details like registers for now.)
(reset)
(new-trace "add")
(add-code
'((function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(3:integer <- add 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4))
(prn "F - 'add' operates on two addresses"))
;? (reset) ;? 1
;? (quit) ;? 1
(reset)
(new-trace "add-literal")
(add-code
'((function main [
(1:integer <- add 2:literal 3:literal)
])))
(run 'main)
(when (~is memory*.1 5)
(prn "F - ops can take 'literal' operands (but not return them)"))
(reset)
(new-trace "sub-literal")
(add-code
'((function main [
(1:integer <- subtract 1:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 -2)
(prn "F - 'subtract'"))
(reset)
(new-trace "mul-literal")
(add-code
'((function main [
(1:integer <- multiply 2:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 6)
(prn "F - 'multiply'"))
(reset)
(new-trace "div-literal")
(add-code
'((function main [
(1:integer <- divide 8:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 (/ real.8 3))
(prn "F - 'divide'"))
(reset)
(new-trace "idiv-literal")
(add-code
'((function main [
(1:integer 2:integer <- divide-with-remainder 23:literal 6:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 3 2 5))
(prn "F - 'divide-with-remainder' performs integer division"))
(reset)
(new-trace "dummy-oarg")
;? (set dump-trace*)
(add-code
'((function main [
(_ 2:integer <- divide-with-remainder 23:literal 6:literal)
])))
(run 'main)
(when (~iso memory* (obj 2 5))
(prn "F - '_' oarg can ignore some results"))
;? (quit)
; Basic boolean operations: and, or, not
; There are easy ways to encode booleans in binary, but we'll skip past those
; details for now.
(reset)
(new-trace "and-literal")
(add-code
'((function main [
(1:boolean <- and t:literal nil:literal)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~is memory*.1 nil)
(prn "F - logical 'and' for booleans"))
; Basic comparison operations
(reset)
(new-trace "lt-literal")
(add-code
'((function main [
(1:boolean <- less-than 4:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 nil)
(prn "F - 'less-than' inequality operator"))
(reset)
(new-trace "le-literal-false")
(add-code
'((function main [
(1:boolean <- lesser-or-equal 4:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 nil)
(prn "F - 'lesser-or-equal'"))
(reset)
(new-trace "le-literal-true")
(add-code
'((function main [
(1:boolean <- lesser-or-equal 4:literal 4:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 t)
(prn "F - 'lesser-or-equal' returns true for equal operands"))
(reset)
(new-trace "le-literal-true-2")
(add-code
'((function main [
(1:boolean <- lesser-or-equal 4:literal 5:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 t)
(prn "F - 'lesser-or-equal' - 2"))
; Control flow operations: jump, jump-if, jump-unless
; These introduce a new type -- 'offset' -- for literals that refer to memory
; locations relative to the current location.
(reset)
(new-trace "jump-skip")
(add-code
'((function main [
(1:integer <- copy 8:literal)
(jump 1:offset)
(2:integer <- copy 3:literal) ; should be skipped
(reply)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 8))
(prn "F - 'jump' skips some instructions"))
;? (quit)
(reset)
(new-trace "jump-target")
(add-code
'((function main [
(1:integer <- copy 8:literal)
(jump 1:offset)
(2:integer <- copy 3:literal) ; should be skipped
(reply)
(3:integer <- copy 34:literal)
]))) ; never reached
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 8))
(prn "F - 'jump' doesn't skip too many instructions"))
;? (quit)
(reset)
(new-trace "jump-if-skip")
(add-code
'((function main [
(2:integer <- copy 1:literal)
(1:boolean <- equal 1:literal 2:integer)
(jump-if 1:boolean 1:offset)
(2:integer <- copy 3:literal)
(reply)
(3:integer <- copy 34:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 t 2 1))
(prn "F - 'jump-if' is a conditional 'jump'"))
(reset)
(new-trace "jump-if-fallthrough")
(add-code
'((function main [
(1:boolean <- equal 1:literal 2:literal)
(jump-if 3:boolean 1:offset)
(2:integer <- copy 3:literal)
(reply)
(3:integer <- copy 34:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 nil 2 3))
(prn "F - if 'jump-if's first arg is false, it doesn't skip any instructions"))
(reset)
(new-trace "jump-if-backward")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 1:literal)
; loop
(2:integer <- add 2:integer 2:integer)
(3:boolean <- equal 1:integer 2:integer)
(jump-if 3:boolean -3:offset) ; to loop
(4:integer <- copy 3:literal)
(reply)
(3:integer <- copy 34:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 4 3 nil 4 3))
(prn "F - 'jump-if' can take a negative offset to make backward jumps"))
(reset)
(new-trace "jump-label")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 1:literal)
loop
(2:integer <- add 2:integer 2:integer)
(3:boolean <- equal 1:integer 2:integer)
(jump-if 3:boolean loop:offset)
(4:integer <- copy 3:literal)
(reply)
(3:integer <- copy 34:literal)
])))
;? (set dump-trace*)
;? (= dump-trace* (obj whitelist '("-")))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 4 3 nil 4 3))
(prn "F - 'jump-if' can take a negative offset to make backward jumps"))
;? (quit)
; Data movement relies on addressing modes:
; 'direct' - refers to a memory location; default for most types.
; 'literal' - directly encoded in the code; implicit for some types like 'offset'.
(reset)
(new-trace "direct-addressing")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:integer <- copy 1:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 34))
(prn "F - 'copy' performs direct addressing"))
; 'Indirect' addressing refers to an address stored in a memory location.
; Indicated by the metadata '/deref'. Usually requires an address type.
; In the test below, the memory location 1 contains '2', so an indirect read
; of location 1 returns the value of location 2.
(reset)
(new-trace "indirect-addressing")
(add-code
'((function main [
(1:integer-address <- copy 2:literal) ; unsafe; can't do this in general
(2:integer <- copy 34:literal)
(3:integer <- copy 1:integer-address/deref)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 34 3 34))
(prn "F - 'copy' performs indirect addressing"))
; Output args can use indirect addressing. In the test below the value is
; stored at the location stored in location 1 (i.e. location 2).
(reset)
(new-trace "indirect-addressing-oarg")
(add-code
'((function main [
(1:integer-address <- copy 2:literal)
(2:integer <- copy 34:literal)
(1:integer-address/deref <- add 2:integer 2:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 36))
(prn "F - instructions can perform indirect addressing on output arg"))
;; Compound data types
;
; Until now we've dealt with scalar types like integers and booleans and
; addresses, where mu looks like other assembly languages. In addition, mu
; provides first-class support for compound types: arrays and and-records.
;
; 'get' accesses fields in and-records
; 'index' accesses indices in arrays
;
; Both operations require knowledge about the types being worked on, so all
; types used in mu programs are defined in a single global system-wide table
; (see type* in mu.arc for the complete list of types; we'll add to it over
; time).
; first a sanity check that the table of types is consistent
(reset)
(each (typ typeinfo) type*
(when typeinfo!and-record
(assert (is typeinfo!size (len typeinfo!elems)))
(when typeinfo!fields
(assert (is typeinfo!size (len typeinfo!fields))))))
(reset)
(new-trace "get-record")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy nil:literal)
(3:boolean <- get 1:integer-boolean-pair 1:offset)
(4:integer <- get 1:integer-boolean-pair 0:offset)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 nil 3 nil 4 34))
(prn "F - 'get' accesses fields of and-records"))
;? (quit)
(reset)
(new-trace "get-indirect")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy nil:literal)
(3:integer-boolean-pair-address <- copy 1:literal)
(4:boolean <- get 3:integer-boolean-pair-address/deref 1:offset)
(5:integer <- get 3:integer-boolean-pair-address/deref 0:offset)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 nil 3 1 4 nil 5 34))
(prn "F - 'get' accesses fields of and-record address"))
(reset)
(new-trace "get-indirect-repeated")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:integer <- copy 35:literal)
(3:integer <- copy 36:literal)
(4:integer-point-pair-address <- copy 1:literal) ; unsafe
(5:integer-point-pair-address-address <- copy 4:literal) ; unsafe
(6:integer-integer-pair <- get 5:integer-point-pair-address-address/deref/deref 1:offset)
(8:integer <- get 5:integer-point-pair-address-address/deref/deref 0:offset)
])))
(run 'main)
(when (~memory-contains 6 '(35 36 34))
(prn "F - 'get' can deref multiple times"))
;? (quit)
(reset)
(new-trace "get-compound-field")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:integer <- copy 35:literal)
(3:integer <- copy 36:literal)
(4:integer-integer-pair <- get 1:integer-point-pair 1:offset)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 35 3 36 4 35 5 36))
(prn "F - 'get' accesses fields spanning multiple locations"))
(reset)
(new-trace "get-address")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy t:literal)
(3:boolean-address <- get-address 1:integer-boolean-pair 1:offset)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 t 3 2))
(prn "F - 'get-address' returns address of fields of and-records"))
(reset)
(new-trace "get-address-indirect")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy t:literal)
(3:integer-boolean-pair-address <- copy 1:literal)
(4:boolean-address <- get-address 3:integer-boolean-pair-address/deref 1:offset)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 t 3 1 4 2))
(prn "F - 'get-address' accesses fields of and-record address"))
(reset)
(new-trace "index-literal")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer-boolean-pair <- index 1:integer-boolean-pair-array 1:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 23 3 nil 4 24 5 t 6 24 7 t))
(prn "F - 'index' accesses indices of arrays"))
;? (quit)
(reset)
(new-trace "index-direct")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer <- copy 1:literal)
(7:integer-boolean-pair <- index 1:integer-boolean-pair-array 6:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 23 3 nil 4 24 5 t 6 1 7 24 8 t))
(prn "F - 'index' accesses indices of arrays"))
;? (quit)
(reset)
(new-trace "index-indirect")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer <- copy 1:literal)
(7:integer-boolean-pair-array-address <- copy 1:literal)
(8:integer-boolean-pair <- index 7:integer-boolean-pair-array-address/deref 6:integer)
])))
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1")))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 23 3 nil 4 24 5 t 6 1 7 1 8 24 9 t))
(prn "F - 'index' accesses indices of array address"))
;? (quit)
(reset)
(new-trace "index-indirect-multiple")
(add-code
'((function main [
(1:integer <- copy 4:literal)
(2:integer <- copy 23:literal)
(3:integer <- copy 24:literal)
(4:integer <- copy 25:literal)
(5:integer <- copy 26:literal)
(6:integer-array-address <- copy 1:literal) ; unsafe
(7:integer-array-address-address <- copy 6:literal) ; unsafe
(8:integer <- index 7:integer-array-address-address/deref/deref 1:literal)
])))
(run 'main)
(when (~is memory*.8 24)
(prn "F - 'index' can deref multiple times"))
(reset)
(new-trace "index-address")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer <- copy 1:literal)
(7:integer-boolean-pair-address <- index-address 1:integer-boolean-pair-array 6:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 23 3 nil 4 24 5 t 6 1 7 4))
(prn "F - 'index-address' returns addresses of indices of arrays"))
(reset)
(new-trace "index-address-indirect")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer <- copy 1:literal)
(7:integer-boolean-pair-array-address <- copy 1:literal)
(8:integer-boolean-pair-address <- index-address 7:integer-boolean-pair-array-address/deref 6:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 2 2 23 3 nil 4 24 5 t 6 1 7 1 8 4))
(prn "F - 'index-address' returns addresses of indices of array addresses"))
; Array values know their length. Record lengths are saved in the types table.
(reset)
(new-trace "len-array")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer <- length 1:integer-boolean-pair-array)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.6 2)
(prn "F - 'length' of array"))
(reset)
(new-trace "len-array-indirect")
(add-code
'((function main [
(1:integer <- copy 2:literal)
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer-address <- copy 1:literal)
(7:integer <- length 6:integer-boolean-pair-array-address/deref)
])))
;? (set dump-trace*)
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1")))
(run 'main)
;? (prn memory*)
(when (~is memory*.7 2)
(prn "F - 'length' of array address"))
; 'sizeof' is a helper to determine the amount of memory required by a type.
; Only for non-arrays.
(reset)
(new-trace "sizeof-record")
(add-code
'((function main [
(1:integer <- sizeof integer-boolean-pair:literal)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.1 2)
(prn "F - 'sizeof' returns space required by arg"))
(reset)
(new-trace "sizeof-record-not-len")
(add-code
'((function main [
(1:integer <- sizeof integer-point-pair:literal)
])))
(run 'main)
;? (prn memory*)
(when (is memory*.1 2)
(prn "F - 'sizeof' is different from number of elems"))
; Regardless of a type's length, you can move it around just like a primitive.
(reset)
(new-trace "copy-record")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy nil:literal)
(4:boolean <- copy t:literal)
(3:integer-boolean-pair <- copy 1:integer-boolean-pair)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 nil 3 34 4 nil))
(prn "F - ops can operate on records spanning multiple locations"))
(reset)
(new-trace "copy-record2")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:integer <- copy 35:literal)
(3:integer <- copy 36:literal)
(4:integer <- copy 0:literal)
(5:integer <- copy 0:literal)
(6:integer <- copy 0:literal)
(4:integer-point-pair <- copy 1:integer-point-pair)
])))
;? (= dump-trace* (obj whitelist '("run" "sizeof")))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 35 3 36
; result
4 34 5 35 6 36))
(prn "F - ops can operate on records with fields spanning multiple locations"))
) ; section 20
(section 100
; A special kind of record is the 'tagged type'. It lets us represent
; dynamically typed values, which save type information in memory rather than
; in the code to use them. This will let us do things like create heterogenous
; lists containing both integers and strings. Tagged values admit two
; operations:
;
; 'save-type' - turns a regular value into a tagged-value of the appropriate type
; 'maybe-coerce' - turns a tagged value into a regular value if the type matches
;
; The payload of a tagged value must occupy just one location. Save pointers
; to records.
(reset)
(new-trace "tagged-value")
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1")))
(add-code
'((function main [
(1:type <- copy integer:literal)
(2:integer <- copy 34:literal)
(3:integer 4:boolean <- maybe-coerce 1:tagged-value integer:literal)
])))
;? (set dump-trace*)
(run 'main)
;? (prn completed-routines*)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*)
(when (or (~is memory*.3 34)
(~is memory*.4 t))
(prn "F - 'maybe-coerce' copies value only if type tag matches"))
;? (quit)
(reset)
(new-trace "tagged-value-2")
;? (set dump-trace*)
(add-code
'((function main [
(1:type <- copy integer-address:literal)
(2:integer <- copy 34:literal)
(3:boolean 4:boolean <- maybe-coerce 1:tagged-value boolean:literal)
])))
(run 'main)
;? (prn memory*)
(when (or (~is memory*.3 0)
(~is memory*.4 nil))
(prn "F - 'maybe-coerce' doesn't copy value when type tag doesn't match"))
(reset)
(new-trace "save-type")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:tagged-value <- save-type 1:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 'integer 3 34))
(prn "F - 'save-type' saves the type of a value at runtime, turning it into a tagged-value"))
(reset)
(new-trace "init-tagged-value")
(add-code
'((function main [
(1:integer <- copy 34:literal)
(2:tagged-value-address <- init-tagged-value integer:literal 1:integer)
(3:integer 4:boolean <- maybe-coerce 2:tagged-value-address/deref integer:literal)
])))
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1" "sizeof")))
(run 'main)
;? (prn memory*)
(when (or (~is memory*.3 34)
(~is memory*.4 t))
(prn "F - 'init-tagged-value' is the converse of 'maybe-coerce'"))
;? (quit)
; Now that we can package values together with their types, we can construct a
; dynamically typed list.
(reset)
(new-trace "list")
;? (set dump-trace*)
(add-code
'((function main [
; 1 points at first node: tagged-value (int 34)
(1:list-address <- new list:literal)
(2:tagged-value-address <- list-value-address 1:list-address)
(3:type-address <- get-address 2:tagged-value-address/deref type:offset)
(3:type-address/deref <- copy integer:literal)
(4:location <- get-address 2:tagged-value-address/deref payload:offset)
(4:location/deref <- copy 34:literal)
(5:list-address-address <- get-address 1:list-address/deref cdr:offset)
(5:list-address-address/deref <- new list:literal)
; 6 points at second node: tagged-value (boolean t)
(6:list-address <- copy 5:list-address-address/deref)
(7:tagged-value-address <- list-value-address 6:list-address)
(8:type-address <- get-address 7:tagged-value-address/deref type:offset)
(8:type-address/deref <- copy boolean:literal)
(9:location <- get-address 7:tagged-value-address/deref payload:offset)
(9:location/deref <- copy t:literal)
(10:list-address <- get 6:list-address/deref 1:offset)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let first rep.routine!alloc
;? (= dump-trace* (obj whitelist '("run")))
;? (set dump-trace*)
(run)
;? (prn memory*)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (or (~all first (map memory* '(1 2 3)))
(~is memory*.first 'integer)
(~is memory*.4 (+ first 1))
(~is (memory* (+ first 1)) 34)
(~is memory*.5 (+ first 2))
(let second memory*.6
(or
(~is (memory* (+ first 2)) second)
(~all second (map memory* '(6 7 8)))
(~is memory*.second 'boolean)
(~is memory*.9 (+ second 1))
(~is (memory* (+ second 1)) t)
(~is memory*.10 nil))))
(prn "F - lists can contain elements of different types"))))
(run-code test2
(10:list-address <- list-next 1:list-address))
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~is memory*.10 memory*.6)
(prn "F - 'list-next can move a list pointer to the next node"))
;? (quit)
; 'init-list' takes a variable number of args and constructs a list containing
; them. Just integers for now.
(reset)
(new-trace "init-list")
(add-code
'((function main [
(1:integer <- init-list 3:literal 4:literal 5:literal)
])))
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1" "sizeof")))
(run 'main)
;? (prn memory*)
(let first memory*.1
;? (prn first)
(when (or (~is memory*.first 'integer)
(~is (memory* (+ first 1)) 3)
(let second (memory* (+ first 2))
;? (prn second)
(or (~is memory*.second 'integer)
(~is (memory* (+ second 1)) 4)
(let third (memory* (+ second 2))
;? (prn third)
(or (~is memory*.third 'integer)
(~is (memory* (+ third 1)) 5)
(~is (memory* (+ third 2) nil)))))))
(prn "F - 'init-list' can construct a list of integers")))
) ; section 100
(section 20
;; Functions
;
; Just like the table of types is centralized, functions are conceptualized as
; a centralized table of operations just like the "primitives" we've seen so
; far. If you create a function you can call it like any other op.
(reset)
(new-trace "new-fn")
(add-code
'((function test1 [
(3:integer <- add 1:integer 2:integer)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(test1)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4))
(prn "F - calling a user-defined function runs its instructions"))
;? (quit)
(reset)
(new-trace "new-fn-once")
(add-code
'((function test1 [
(1:integer <- copy 1:literal)
])
(function main [
(test1)
])))
;? (= dump-trace* (obj whitelist '("run")))
(run 'main)
(when (~is 2 curr-cycle*)
(prn "F - calling a user-defined function runs its instructions exactly once " curr-cycle*))
;? (quit)
; User-defined functions communicate with their callers through two
; primitives:
;
; 'arg' - to access inputs
; 'reply' - to return outputs
(reset)
(new-trace "new-fn-reply")
(add-code
'((function test1 [
(3:integer <- add 1:integer 2:integer)
(reply)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(test1)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4))
(prn "F - 'reply' stops executing the current function"))
;? (quit)
(reset)
(new-trace "new-fn-reply-nested")
(add-code
'((function test1 [
(3:integer <- test2)
])
(function test2 [
(reply 2:integer)
])
(function main [
(2:integer <- copy 34:literal)
(test1)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 2 34 3 34))
(prn "F - 'reply' stops executing any callers as necessary"))
;? (quit)
(reset)
(new-trace "new-fn-reply-once")
(add-code
'((function test1 [
(3:integer <- add 1:integer 2:integer)
(reply)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(test1)
])))
;? (= dump-trace* (obj whitelist '("run")))
(run 'main)
(when (~is 5 curr-cycle*)
(prn "F - 'reply' executes instructions exactly once " curr-cycle*))
;? (quit)
(reset)
(new-trace "reply-increments-caller-pc")
(add-code
'((function callee [
(reply)
])
(function caller [
(1:integer <- copy 0:literal)
(2:integer <- copy 0:literal)
])))
(freeze function*)
(= routine* (make-routine 'caller))
(assert (is 0 pc.routine*))
(push-stack routine* 'callee) ; pretend call was at first instruction of caller
(run-for-time-slice 1)
(when (~is 1 pc.routine*)
(prn "F - 'reply' increments pc in caller (to move past calling instruction)"))
(reset)
(new-trace "new-fn-arg-sequential")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- next-input)
(3:integer <- add 4:integer 5:integer)
(reply)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(test1 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4
; test1's temporaries
4 1 5 3))
(prn "F - 'arg' accesses in order the operands of the most recent function call (the caller)"))
;? (quit)
(reset)
(new-trace "new-fn-arg-random-access")
;? (set dump-trace*)
(add-code
'((function test1 [
(5:integer <- input 1:literal)
(4:integer <- input 0:literal)
(3:integer <- add 4:integer 5:integer)
(reply)
(4:integer <- copy 34:literal) ; should never run
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(test1 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4
; test's temporaries
4 1 5 3))
(prn "F - 'arg' with index can access function call arguments out of order"))
;? (quit)
(reset)
(new-trace "new-fn-arg-random-then-sequential")
;? (set dump-trace*)
(add-code
'((function test1 [
(_ <- input 1:literal)
(1:integer <- next-input) ; takes next arg after index 1
]) ; should never run
(function main [
(test1 1:literal 2:literal 3:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 3))
(prn "F - 'arg' with index resets index for later calls"))
;? (quit)
(reset)
(new-trace "new-fn-arg-status")
(add-code
'((function test1 [
(4:integer 5:boolean <- next-input)
])
(function main [
(test1 1:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 4 1 5 t))
(prn "F - 'arg' sets a second oarg when arg exists"))
;? (quit)
(reset)
(new-trace "new-fn-arg-missing")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- next-input)
])
(function main [
(test1 1:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 4 1))
(prn "F - missing 'arg' doesn't cause error"))
;? (quit)
(reset)
(new-trace "new-fn-arg-missing-2")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer 6:boolean <- next-input)
])
(function main [
(test1 1:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 4 1 6 nil))
(prn "F - missing 'arg' wipes second oarg when provided"))
;? (quit)
(reset)
(new-trace "new-fn-arg-missing-3")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- copy 34:literal)
(5:integer 6:boolean <- next-input)
])
(function main [
(test1 1:literal)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 4 1 6 nil))
(prn "F - missing 'arg' consistently wipes its oarg"))
;? (quit)
(reset)
(new-trace "new-fn-arg-missing-4")
(add-code
'((function test1 [
; if given two args, adds them; if given one arg, increments
(4:integer <- next-input)
(5:integer 6:boolean <- next-input)
{ begin
(break-if 6:boolean)
(5:integer <- copy 1:literal)
}
(7:integer <- add 4:integer 5:integer)
])
(function main [
(test1 34:literal)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 4 34 5 1 6 nil 7 35))
(prn "F - function with optional second arg"))
;? (quit)
(reset)
(new-trace "new-fn-arg-by-value")
(add-code
'((function test1 [
(1:integer <- copy 0:literal) ; overwrite caller memory
(2:integer <- next-input)
]) ; arg not clobbered
(function main [
(1:integer <- copy 34:literal)
(test1 1:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 0 2 34))
(prn "F - 'arg' passes by value"))
(reset)
(new-trace "arg-record")
(add-code
'((function test1 [
(4:integer-boolean-pair <- next-input)
])
(function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy nil:literal)
(test1 1:integer-boolean-pair)
])))
(run 'main)
(when (~iso memory* (obj 1 34 2 nil 4 34 5 nil))
(prn "F - 'arg' can copy records spanning multiple locations"))
(reset)
(new-trace "arg-record-indirect")
;? (set dump-trace*)
(add-code
'((function test1 [
(4:integer-boolean-pair <- next-input)
])
(function main [
(1:integer <- copy 34:literal)
(2:boolean <- copy nil:literal)
(3:integer-boolean-pair-address <- copy 1:literal)
(test1 3:integer-boolean-pair-address/deref)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 34 2 nil 3 1 4 34 5 nil))
(prn "F - 'arg' can copy records spanning multiple locations in indirect mode"))
(reset)
(new-trace "new-fn-reply-oarg")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- next-input)
(6:integer <- add 4:integer 5:integer)
(reply 6:integer)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(3:integer <- test1 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4
; test1's temporaries
4 1 5 3 6 4))
(prn "F - 'reply' can take aguments that are returned, or written back into output args of caller"))
(reset)
(new-trace "new-fn-reply-oarg-multiple")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- next-input)
(6:integer <- add 4:integer 5:integer)
(reply 6:integer 5:integer)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(3:integer 7:integer <- test1 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4 7 3
; test1's temporaries
4 1 5 3 6 4))
(prn "F - 'reply' permits a function to return multiple values at once"))
; 'prepare-reply' is useful for doing cleanup before exiting a function
(reset)
(new-trace "new-fn-prepare-reply")
(add-code
'((function test1 [
(4:integer <- next-input)
(5:integer <- next-input)
(6:integer <- add 4:integer 5:integer)
(prepare-reply 6:integer 5:integer)
(reply)
(4:integer <- copy 34:literal)
])
(function main [
(1:integer <- copy 1:literal)
(2:integer <- copy 3:literal)
(3:integer 7:integer <- test1 1:integer 2:integer)
])))
(run 'main)
;? (prn memory*)
(when (~iso memory* (obj 1 1 2 3 3 4 7 3
; test1's temporaries
4 1 5 3 6 4))
(prn "F - without args, 'reply' returns values from previous 'prepare-reply'."))
; When you have arguments that are both read from and written to, include them
; redundantly in both ingredients and results. That'll help tools track what
; changed.
; To enforce that the result and ingredient must always match, use the
; 'same-as-arg' property. Results with 'same-as-arg' properties should only be
; copied to a caller output arg identical to the specified caller arg.
(reset)
(new-trace "new-fn-same-as-arg")
(add-code
'((function test1 [
; increment the contents of an address
(default-space:space-address <- new space:literal 2:literal)
(x:integer-address <- next-input)
(x:integer-address/deref <- add x:integer-address/deref 1:literal)
(reply x:integer-address/same-as-arg:0)
])
(function main [
(2:integer-address <- new integer:literal)
(2:integer-address/deref <- copy 0:literal)
(3:integer-address <- test1 2:integer-address)
])))
(run 'main)
(let routine (car completed-routines*)
;? (prn rep.routine!error) ;? 1
(when (no rep.routine!error)
(prn "F - 'same-as-arg' results must be identical to a given input")))
;? (quit) ;? 2
) ; section 20
(section 11
;; Structured programming
;
; Our jump operators are quite inconvenient to use, so mu provides a
; lightweight tool called 'convert-braces' to work in a slightly more
; convenient format with nested braces:
;
; {
; some instructions
; {
; more instructions
; }
; }
;
; Braces are like labels in assembly language, they require no special
; parsing. The operations 'loop' and 'break' jump to just after the enclosing
; '{' and '}' respectively.
;
; Conditional and unconditional 'loop' and 'break' should give us 80% of the
; benefits of the control-flow primitives we're used to in other languages,
; like 'if', 'while', 'for', etc.
;
; Compare 'unquoted blocks' using {} with 'quoted blocks' using [] that we've
; gotten used to seeing. Quoted blocks are used by top-level instructions to
; provide code without running it.
(reset)
(new-trace "convert-braces")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("c{0" "c{1")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin ; 'begin' is just a hack because racket turns braces into parens
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
(((break-if)) ((4 boolean)))
(((5 integer)) <- ((copy)) ((0 literal)))
}
(((reply)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
(((jump-if)) ((4 boolean)) ((1 offset)))
(((5 integer)) <- ((copy)) ((0 literal)))
(((reply)))))
(prn "F - convert-braces replaces break-if with a jump-if to after the next close-brace"))
;? (quit)
(reset)
(new-trace "convert-braces-empty-block")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("c{0" "c{1")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin
(((break)))
}
(((reply)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((0 offset)))
(((reply)))))
(prn "F - convert-braces works for degenerate blocks"))
;? (quit)
(reset)
(new-trace "convert-braces-nested-break")
(= traces* (queue))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
(((break-if)) ((4 boolean)))
{ begin
(((5 integer)) <- ((copy)) ((0 literal)))
}
}
(((reply)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
(((jump-if)) ((4 boolean)) ((1 offset)))
(((5 integer)) <- ((copy)) ((0 literal)))
(((reply)))))
(prn "F - convert-braces balances braces when converting break"))
(reset)
(new-trace "convert-braces-repeated-jump")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("c{0" "c{1")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
{ begin
(((break)))
(((2 integer)) <- ((copy)) ((0 literal)))
}
{ begin
(((break)))
(((3 integer)) <- ((copy)) ((0 literal)))
}
(((4 integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((1 offset)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((1 offset)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-braces handles jumps on jumps"))
;? (quit)
(reset)
(new-trace "convert-braces-nested-loop")
(= traces* (queue))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
{ begin
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
}
(((loop-if)) ((4 boolean)))
(((5 integer)) <- ((copy)) ((0 literal)))
}
(((reply)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 boolean)) <- ((not-equal)) ((1 integer)) ((3 integer)))
(((jump-if)) ((4 boolean)) ((-3 offset)))
(((5 integer)) <- ((copy)) ((0 literal)))
(((reply)))))
(prn "F - convert-braces balances braces when converting 'loop'"))
(reset)
(new-trace "convert-braces-label")
(= traces* (queue))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
foo
(((2 integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
foo
(((2 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-braces skips past labels"))
;? (quit)
(reset)
(new-trace "convert-braces-label-increments-offset")
(= traces* (queue))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
{ begin
(((break)))
foo
}
(((2 integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((1 offset)))
foo
(((2 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-braces treats labels as instructions"))
;? (quit)
(reset)
(new-trace "convert-braces-label-increments-offset2")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("c{0" "c{1")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
{ begin
(((break)))
foo
}
(((2 integer)) <- ((copy)) ((0 literal)))
{ begin
(((break)))
(((3 integer)) <- ((copy)) ((0 literal)))
}
(((4 integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((1 offset)))
foo
(((2 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((1 offset)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-braces treats labels as instructions - 2"))
;? (quit)
(reset)
(new-trace "break-multiple")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("-")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
{ begin
{ begin
(((break)) ((2 blocks)))
}
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 integer)) <- ((copy)) ((0 literal)))
(((5 integer)) <- ((copy)) ((0 literal)))
}))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((4 offset)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 integer)) <- ((copy)) ((0 literal)))
(((5 integer)) <- ((copy)) ((0 literal)))))
(prn "F - 'break' can take an extra arg with number of nested blocks to exit"))
;? (quit)
(reset)
(new-trace "loop")
;? (set dump-trace*)
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
{ begin
(((3 integer)) <- ((copy)) ((0 literal)))
(((loop)))
}))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((-2 offset)))))
(prn "F - 'loop' jumps to start of containing block"))
;? (quit)
; todo: fuzz-test invariant: convert-braces offsets should be robust to any
; number of inner blocks inside but not around the loop block.
(reset)
(new-trace "loop-nested")
;? (set dump-trace*)
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
{ begin
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin
(((4 integer)) <- ((copy)) ((0 literal)))
}
(((loop)))
}))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((4 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((-3 offset)))))
(prn "F - 'loop' correctly jumps back past nested braces"))
(reset)
(new-trace "loop-multiple")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("-")))
(when (~iso (convert-braces
'((((1 integer)) <- ((copy)) ((0 literal)))
{ begin
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
{ begin
(((loop)) ((2 blocks)))
}
}))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))
(((jump)) ((-3 offset)))))
(prn "F - 'loop' can take an extra arg with number of nested blocks to exit"))
;? (quit)
(reset)
(new-trace "convert-labels")
(= traces* (queue))
(when (~iso (convert-labels
'(loop
(((jump)) ((loop offset)))))
'(loop
(((jump)) ((-2 offset)))))
(prn "F - 'convert-labels' rewrites jumps to labels"))
;; Variables
;
; A big convenience high-level languages provide is the ability to name memory
; locations. In mu, a lightweight tool called 'convert-names' provides this
; convenience.
(reset)
(new-trace "convert-names")
(= traces* (queue))
;? (set dump-trace*)
(when (~iso (convert-names
'((((x integer)) <- ((copy)) ((0 literal)))
(((y integer)) <- ((copy)) ((0 literal)))
(((z integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names renames symbolic names to integer locations"))
(reset)
(new-trace "convert-names-compound")
(= traces* (queue))
(when (~iso (convert-names
; copying 0 into pair is meaningless; just for testing
'((((x integer-boolean-pair)) <- ((copy)) ((0 literal)))
(((y integer)) <- ((copy)) ((0 literal)))))
'((((1 integer-boolean-pair)) <- ((copy)) ((0 literal)))
(((3 integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names increments integer locations by the size of the type of the previous var"))
(reset)
(new-trace "convert-names-nil")
(= traces* (queue))
;? (set dump-trace*)
(when (~iso (convert-names
'((((x integer)) <- ((copy)) ((0 literal)))
(((y integer)) <- ((copy)) ((0 literal)))
; nil location is meaningless; just for testing
(((nil integer)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((nil integer)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names never renames nil"))
(reset)
(new-trace "convert-names-string")
;? (set dump-trace*)
(when (~iso (convert-names
'((((1 integer-address)) <- ((new)) "foo")))
'((((1 integer-address)) <- ((new)) "foo")))
(prn "convert-names passes through raw strings (just a convenience arg for 'new')"))
(reset)
(new-trace "convert-names-raw")
(= traces* (queue))
(when (~iso (convert-names
'((((x integer)) <- ((copy)) ((0 literal)))
(((y integer) (raw)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((y integer) (raw)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names never renames raw operands"))
(reset)
(new-trace "convert-names-literal")
(= traces* (queue))
(when (~iso (convert-names
; meaningless; just for testing
'((((x literal)) <- ((copy)) ((0 literal)))))
'((((x literal)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names never renames literals"))
(reset)
(new-trace "convert-names-literal-2")
(= traces* (queue))
(when (~iso (convert-names
'((((x boolean)) <- ((copy)) ((x literal)))))
'((((1 boolean)) <- ((copy)) ((x literal)))))
(prn "F - convert-names never renames literals, even when the name matches a variable"))
; kludgy support for 'fork' below
(reset)
(new-trace "convert-names-functions")
(= traces* (queue))
(when (~iso (convert-names
'((((x integer)) <- ((copy)) ((0 literal)))
(((y integer)) <- ((copy)) ((0 literal)))
; meaningless; just for testing
(((z fn)) <- ((copy)) ((0 literal)))))
'((((1 integer)) <- ((copy)) ((0 literal)))
(((2 integer)) <- ((copy)) ((0 literal)))
(((z fn)) <- ((copy)) ((0 literal)))))
(prn "F - convert-names never renames fns"))
(reset)
(new-trace "convert-names-record-fields")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("cn0")))
(when (~iso (convert-names
'((((x integer)) <- ((get)) ((34 integer-boolean-pair)) ((bool offset)))))
'((((1 integer)) <- ((get)) ((34 integer-boolean-pair)) ((1 offset)))))
(prn "F - convert-names replaces record field offsets"))
(reset)
(new-trace "convert-names-record-fields-ambiguous")
(= traces* (queue))
(when (errsafe (convert-names
'((((bool boolean)) <- ((copy)) ((t literal)))
(((x integer)) <- ((get)) ((34 integer-boolean-pair)) ((bool offset))))))
(prn "F - convert-names doesn't allow offsets and variables with the same name in a function"))
(reset)
(new-trace "convert-names-record-fields-ambiguous-2")
(= traces* (queue))
(when (errsafe (convert-names
'((((x integer)) <- ((get)) ((34 integer-boolean-pair)) ((bool offset)))
(((bool boolean)) <- ((copy)) ((t literal))))))
(prn "F - convert-names doesn't allow offsets and variables with the same name in a function - 2"))
(reset)
(new-trace "convert-names-record-fields-indirect")
(= traces* (queue))
;? (= dump-trace* (obj whitelist '("cn0")))
(when (~iso (convert-names
'((((x integer)) <- ((get)) ((34 integer-boolean-pair-address) (deref)) ((bool offset)))))
'((((1 integer)) <- ((get)) ((34 integer-boolean-pair-address) (deref)) ((1 offset)))))
(prn "F - convert-names replaces field offsets for record addresses"))
;? (quit)
(reset)
(new-trace "convert-names-record-fields-multiple")
(= traces* (queue))
(when (~iso (convert-names
'((((2 boolean)) <- ((get)) ((1 integer-boolean-pair)) ((bool offset)))
(((3 boolean)) <- ((get)) ((1 integer-boolean-pair)) ((bool offset)))))
'((((2 boolean)) <- ((get)) ((1 integer-boolean-pair)) ((1 offset)))
(((3 boolean)) <- ((get)) ((1 integer-boolean-pair)) ((1 offset)))))
(prn "F - convert-names replaces field offsets with multiple mentions"))
;? (quit)
(reset)
(new-trace "convert-names-label")
(= traces* (queue))
(when (~iso (convert-names
'((((1 integer)) <- ((copy)) ((0 literal)))
foo))
'((((1 integer)) <- ((copy)) ((0 literal)))
foo))
(prn "F - convert-names skips past labels"))
;? (quit)
) ; section 11
(section 20
; A rudimentary memory allocator. Eventually we want to write this in mu.
;
; No deallocation yet; let's see how much code we can build in mu before we
; feel the need for it.
(reset)
(new-trace "new-primitive")
(add-code
'((function main [
(1:integer-address <- new integer:literal)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
;? (set dump-trace*)
(run)
;? (prn memory*)
(when (~iso memory*.1 before)
(prn "F - 'new' returns current high-water mark"))
(when (~iso rep.routine!alloc (+ before 1))
(prn "F - 'new' on primitive types increments high-water mark by their size"))))
;? (quit)
(reset)
(new-trace "new-array-literal")
(add-code
'((function main [
(1:type-array-address <- new type-array:literal 5:literal)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
(run)
;? (prn memory*)
(when (~iso memory*.1 before)
(prn "F - 'new' on array with literal size returns current high-water mark"))
(when (~iso rep.routine!alloc (+ before 6))
(prn "F - 'new' on primitive arrays increments high-water mark by their size"))))
(reset)
(new-trace "new-array-direct")
(add-code
'((function main [
(1:integer <- copy 5:literal)
(2:type-array-address <- new type-array:literal 1:integer)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
(run)
;? (prn memory*)
(when (~iso memory*.2 before)
(prn "F - 'new' on array with variable size returns current high-water mark"))
(when (~iso rep.routine!alloc (+ before 6))
(prn "F - 'new' on primitive arrays increments high-water mark by their (variable) size"))))
(reset)
(new-trace "new-allocation-chunk")
(add-code
'((function main [
(1:integer-address <- new integer:literal)
])))
; start allocating from address 30, in chunks of 10 locations each
(= Memory-allocated-until 30
Allocation-chunk 10)
(let routine make-routine!main
(assert:is rep.routine!alloc 30)
(assert:is rep.routine!alloc-max 40)
; pretend the current chunk is full
(= rep.routine!alloc 40)
(enq routine running-routines*)
(run)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~is rep.routine!alloc 41)
(prn "F - 'new' can allocate past initial routine memory"))
(when (~is rep.routine!alloc-max 50)
(prn "F - 'new' updates upper bound for routine memory @rep.routine!alloc-max")))
(reset)
(new-trace "new-skip")
(add-code
'((function main [
(1:integer-boolean-pair-address <- new integer-boolean-pair:literal)
])))
; start allocating from address 30, in chunks of 10 locations each
(= Memory-allocated-until 30
Allocation-chunk 10)
(let routine make-routine!main
(assert:is rep.routine!alloc 30)
(assert:is rep.routine!alloc-max 40)
; pretend the current chunk has just one location left
(= rep.routine!alloc 39)
(enq routine running-routines*)
; request 2 locations
(run)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (or (~is memory*.1 40)
(~is rep.routine!alloc 42)
(~is rep.routine!alloc-max 50)
(~is Memory-allocated-until 50))
(prn "F - 'new' skips past current chunk if insufficient space")))
(reset)
(new-trace "new-skip-noncontiguous")
(add-code
'((function main [
(1:integer-boolean-pair-address <- new integer-boolean-pair:literal)
])))
; start allocating from address 30, in chunks of 10 locations each
(= Memory-allocated-until 30
Allocation-chunk 10)
(let routine make-routine!main
(assert:is rep.routine!alloc 30)
(assert:is rep.routine!alloc-max 40)
; pretend the current chunk has just one location left
(= rep.routine!alloc 39)
; pretend we allocated more memory since we created the routine
(= Memory-allocated-until 90)
(enq routine running-routines*)
; request 2 locations
(run)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (or (~is memory*.1 90)
(~is rep.routine!alloc 92)
(~is rep.routine!alloc-max 100)
(~is Memory-allocated-until 100))
(prn "F - 'new' allocates a new chunk if insufficient space")))
(reset)
(new-trace "new-array-skip-noncontiguous")
(add-code
'((function main [
(1:integer-array-address <- new integer-array:literal 4:literal)
])))
; start allocating from address 30, in chunks of 10 locations each
(= Memory-allocated-until 30
Allocation-chunk 10)
(let routine make-routine!main
(assert:is rep.routine!alloc 30)
(assert:is rep.routine!alloc-max 40)
; pretend the current chunk has just one location left
(= rep.routine!alloc 39)
; pretend we allocated more memory since we created the routine
(= Memory-allocated-until 90)
(enq routine running-routines*)
; request 4 locations
(run)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*.1) ;? 1
;? (prn rep.routine) ;? 1
;? (prn Memory-allocated-until) ;? 1
(when (or (~is memory*.1 90)
(~is rep.routine!alloc 95)
(~is rep.routine!alloc-max 100)
(~is Memory-allocated-until 100))
(prn "F - 'new-array' allocates a new chunk if insufficient space")))
;? (quit) ;? 1
; Even though our memory locations can now have names, the names are all
; globals, accessible from any function. To isolate functions from their
; callers we need local variables, and mu provides them using a special
; variable called default-space. When you initialize such a variable (likely
; with a call to our just-defined memory allocator) mu interprets memory
; locations as offsets from its value. If default-space is set to 1000, for
; example, reads and writes to memory location 1 will really go to 1001.
;
; 'default-space' is itself hard-coded to be function-local; it's nil in a new
; function, and it's restored when functions return to their callers. But the
; actual space allocation is independent. So you can define closures, or do
; even more funky things like share locals between two coroutines.
(reset)
(new-trace "set-default-space")
(add-code
'((function main [
(default-space:space-address <- new space:literal 2:literal)
(1:integer <- copy 23:literal)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
;? (set dump-trace*)
(run)
;? (prn memory*)
(when (~and (~is 23 memory*.1)
(is 23 (memory* (+ before 2))))
(prn "F - default-space implicitly modifies variable locations"))))
;? (quit)
(reset)
(new-trace "set-default-space-skips-offset")
(add-code
'((function main [
(default-space:space-address <- new space:literal 2:literal)
(1:integer <- copy 23:offset)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
;? (set dump-trace*)
(run)
;? (prn memory*)
(when (~and (~is 23 memory*.1)
(is 23 (memory* (+ before 2))))
(prn "F - default-space skips 'offset' types just like literals"))))
(reset)
(new-trace "default-space-bounds-check")
(add-code
'((function main [
(default-space:space-address <- new space:literal 2:literal)
(2:integer <- copy 23:literal)
])))
;? (set dump-trace*)
(run 'main)
;? (prn memory*)
(let routine (car completed-routines*)
(when (no rep.routine!error)
(prn "F - default-space checks bounds")))
(reset)
(new-trace "default-space-and-get-indirect")
(add-code
'((function main [
(default-space:space-address <- new space:literal 5:literal)
(1:integer-boolean-pair-address <- new integer-boolean-pair:literal)
(2:integer-address <- get-address 1:integer-boolean-pair-address/deref 0:offset)
(2:integer-address/deref <- copy 34:literal)
(3:integer/raw <- get 1:integer-boolean-pair-address/deref 0:offset)
])))
;? (= dump-trace* (obj blacklist '("sz" "mem" "addr" "cvt0" "cvt1")))
(run 'main)
;? (prn memory*)
;? (prn completed-routines*)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~is 34 memory*.3)
(prn "F - indirect 'get' works in the presence of default-space"))
;? (quit)
(reset)
(new-trace "default-space-and-index-indirect")
(add-code
'((function main [
(default-space:space-address <- new space:literal 5:literal)
(1:integer-array-address <- new integer-array:literal 4:literal)
(2:integer-address <- index-address 1:integer-array-address/deref 2:offset)
(2:integer-address/deref <- copy 34:literal)
(3:integer/raw <- index 1:integer-array-address/deref 2:offset)
])))
;? (= dump-trace* (obj whitelist '("run" "array-info")))
(run 'main)
;? (prn memory*)
;? (prn completed-routines*)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~is 34 memory*.3)
(prn "F - indirect 'index' works in the presence of default-space"))
;? (quit)
(reset)
(new-trace "convert-names-default-space")
(= traces* (queue))
(when (~iso (convert-names
'((((x integer)) <- ((copy)) ((4 literal)))
(((y integer)) <- ((copy)) ((2 literal)))
; unsafe in general; don't write random values to 'default-space'
(((default-space integer)) <- ((add)) ((x integer)) ((y integer)))))
'((((1 integer)) <- ((copy)) ((4 literal)))
(((2 integer)) <- ((copy)) ((2 literal)))
(((default-space integer)) <- ((add)) ((1 integer)) ((2 integer)))))
(prn "F - convert-names never renames default-space"))
(reset)
(new-trace "suppress-default-space")
(add-code
'((function main [
(default-space:space-address <- new space:literal 2:literal)
(1:integer/raw <- copy 23:literal)
])))
(let routine make-routine!main
(enq routine running-routines*)
(let before rep.routine!alloc
;? (set dump-trace*)
(run)
;? (prn memory*)
(when (~and (is 23 memory*.1)
(~is 23 (memory* (+ before 1))))
(prn "F - default-space skipped for locations with metadata 'raw'"))))
;? (quit)
(reset)
(new-trace "array-copy-indirect-scoped")
(add-code
'((function main [
(10:integer <- copy 30:literal) ; pretend allocation
(default-space:space-address <- copy 10:literal) ; unsafe
(1:integer <- copy 2:literal) ; raw location 12
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer-boolean-pair-array-address <- copy 12:literal) ; unsafe
(7:integer-boolean-pair-array <- copy 6:integer-boolean-pair-array-address/deref)
])))
;? (set dump-trace*)
;? (= dump-trace* (obj whitelist '("run" "mem" "sizeof")))
(run 'main)
;? (prn memory*)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~iso memory*.18 2) ; variable 7
(prn "F - indirect array copy in the presence of 'default-space'"))
;? (quit)
(reset)
(new-trace "len-array-indirect-scoped")
(add-code
'((function main [
(10:integer <- copy 30:literal) ; pretend allocation
(default-space:space-address <- copy 10:literal) ; unsafe
(1:integer <- copy 2:literal) ; raw location 12
(2:integer <- copy 23:literal)
(3:boolean <- copy nil:literal)
(4:integer <- copy 24:literal)
(5:boolean <- copy t:literal)
(6:integer-address <- copy 12:literal) ; unsafe
(7:integer <- length 6:integer-boolean-pair-array-address/deref)
])))
;? (= dump-trace* (obj whitelist '("run" "addr" "sz" "array-len")))
(run 'main)
;? (prn memory*)
(when (~iso memory*.18 2)
(prn "F - 'len' accesses length of array address"))
;? (quit)
(reset)
(new-trace "default-space-shared")
(add-code
'((function init-counter [
(default-space:space-address <- new space:literal 30:literal)
(1:integer <- copy 3:literal) ; initialize to 3
(reply default-space:space-address)
])
(function increment-counter [
(default-space:space-address <- next-input)
(1:integer <- add 1:integer 1:literal) ; increment
(reply 1:integer)
])
(function main [
(1:space-address <- init-counter)
(2:integer <- increment-counter 1:space-address)
(3:integer <- increment-counter 1:space-address)
])))
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*)
(when (or (~is memory*.2 4)
(~is memory*.3 5))
(prn "F - multiple calls to a function can share locals"))
;? (quit)
(reset)
(new-trace "default-space-closure")
(add-code
'((function init-counter [
(default-space:space-address <- new space:literal 30:literal)
(1:integer <- copy 3:literal) ; initialize to 3
(reply default-space:space-address)
])
(function increment-counter [
(default-space:space-address <- new space:literal 30:literal)
(0:space-address <- next-input) ; share outer space
(1:integer/space:1 <- add 1:integer/space:1 1:literal) ; increment
(1:integer <- copy 34:literal) ; dummy
(reply 1:integer/space:1)
])
(function main [
(1:space-address <- init-counter)
(2:integer <- increment-counter 1:space-address)
(3:integer <- increment-counter 1:space-address)
])))
;? (set dump-trace*)
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*)
(when (or (~is memory*.2 4)
(~is memory*.3 5))
(prn "F - closures using /space metadata"))
;? (quit)
(reset)
(new-trace "default-space-closure-with-names")
(add-code
'((function init-counter [
(default-space:space-address <- new space:literal 30:literal)
(x:integer <- copy 23:literal)
(y:integer <- copy 3:literal) ; correct copy of y
(reply default-space:space-address)
])
(function increment-counter [
(default-space:space-address <- new space:literal 30:literal)
(0:space-address/names:init-counter <- next-input) ; outer space must be created by 'init-counter' above
(y:integer/space:1 <- add y:integer/space:1 1:literal) ; increment
(y:integer <- copy 34:literal) ; dummy
(reply y:integer/space:1)
])
(function main [
(1:space-address/names:init-counter <- init-counter)
(2:integer <- increment-counter 1:space-address/names:init-counter)
(3:integer <- increment-counter 1:space-address/names:init-counter)
])))
;? (set dump-trace*)
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*)
(when (or (~is memory*.2 4)
(~is memory*.3 5))
(prn "F - /names to name variables in outer spaces"))
;? (quit)
(reset)
(new-trace "default-space-shared-with-names")
(add-code
'((function f [
(default-space:space-address <- new space:literal 30:literal)
(x:integer <- copy 3:literal)
(y:integer <- copy 4:literal)
(reply default-space:space-address)
])
(function g [
(default-space:space-address/names:f <- next-input)
(y:integer <- add y:integer 1:literal)
(x:integer <- add x:integer 2:literal)
(reply x:integer y:integer)
])
(function main [
(1:space-address <- f)
(2:integer 3:integer <- g 1:space-address)
])))
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (or (~is memory*.2 5)
(~is memory*.3 5))
(prn "F - override names for the default space"))
(reset)
(new-trace "default-space-shared-with-extra-names")
(add-code
'((function f [
(default-space:space-address <- new space:literal 30:literal)
(x:integer <- copy 3:literal)
(y:integer <- copy 4:literal)
(reply default-space:space-address)
])
(function g [
(default-space:space-address/names:f <- next-input)
(y:integer <- add y:integer 1:literal)
(x:integer <- add x:integer 2:literal)
(z:integer <- add x:integer y:integer)
(reply z:integer)
])
(function main [
(1:space-address <- f)
(2:integer <- g 1:space-address)
])))
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
(when (~is memory*.2 10)
(prn "F - shared spaces can add new names"))
(reset)
(new-trace "default-space-shared-extra-names-dont-overlap-bindings")
(add-code
'((function f [
(default-space:space-address <- new space:literal 30:literal)
(x:integer <- copy 3:literal)
(y:integer <- copy 4:literal)
(reply default-space:space-address)
])
(function g [
(default-space:space-address/names:f <- next-input)
(y:integer <- add y:integer 1:literal)
(x:integer <- add x:integer 2:literal)
(z:integer <- copy 2:literal)
(reply x:integer y:integer)
])
(function main [
(1:space-address <- f)
(2:integer 3:integer <- g 1:space-address)
])))
(run 'main)
(each routine completed-routines*
(aif rep.routine!error (prn "error - " it)))
;? (prn memory*) ;? 1
(when (or (~is memory*.2 5)
(~is memory*.3 5))
(prn "F - new names in shared spaces don't override old ones"))
;? (quit) ;? 1
) ; section 20
(section 100
;; Dynamic dispatch
;
; Putting it all together, here's how you define generic functions that run
; different code based on the types of their args.
(reset)
(new-trace "dispatch-clause")
;? (set dump-trace*)
(add-code
'((function test1 [
; doesn't matter too much how many locals you allocate space for (here 20)
; if it's slightly too many -- memory is plentiful
; if it's too few -- mu will raise an error
(default-space:space-address <- new space:literal 20:literal)
(first-arg-box:tagged-value-address <- next-input)
; if given integers, add them
{ begin
(first-arg:integer match?:boolean <- maybe-coerce first-arg-box:tagged-value-address/deref integer:literal)
(break-unless match?:boolean)
(second-arg-box:tagged-value-address <- next-input)
(second-arg:integer <- maybe-coerce second-arg-box:tagged-value-address/deref integer:literal)
(result:integer <- add first-arg:integer second-arg:integer)
(reply result:integer)
}
(reply nil:literal)
])
(function main [
(1:tagged-value-address <- init-tagged-value integer:literal 34:literal)
(2:tagged-value-address <- init-tagged-value integer:literal 3:literal)
(3:integer <- test1 1:tagged-value-address 2:tagged-value-address)
])))
(run 'main)
;? (prn memory*)
(when (~is memory*.3 37)
(prn "F - an example function that checks that its oarg is an integer"))
;? (quit)
(reset)
(new-trace "dispatch-multiple-clauses")
;? (set dump-trace*)
(add-code
'((function test1 [
(default-space:space-address <- new space:literal 20:literal)
(first-arg-box:tagged-value-address <- next-input)
; if given integers, add them
{ begin
(first-arg:integer match?:boolean <- maybe-coerce first-arg-box:tagged-value-address/deref integer:literal)
(break-unless match?:boolean)
(second-arg-box:tagged-value-address <- next-input)
(second-arg:integer <- maybe-coerce second-arg-box:tagged-value-address/deref integer:literal)
(result:integer <- add first-arg:integer second-arg:integer)
(reply result:integer)
}
; if given booleans, or them (it's a silly kind of generic function)
{ begin
(first-arg:boolean match?:boolean <- maybe-coerce first-arg-box:tagged-value-address/deref boolean:literal)
(break-unless match?:boolean)
(second-arg-box:tagged-value-address <- next-input)
(second-arg:boolean <- maybe-coerce second-arg-box:tagged-value-address/deref boolean:literal)
(result:boolean <- or first-arg:boolean second-arg:boolean)
(reply result:integer)
}
(reply nil:literal)
])
(function main [
(1:tagged-value-address <- init-tagged-value boolean:literal t:literal)
(2:tagged-value-address <- init-tagged-value boolean:literal nil:literal)
(3:boolean <- test1 1:tagged-value-address 2:tagged-value-address)
])))
;? (each stmt function*!test-fn
;? (prn " " stmt))
(run 'main)
;? (wipe dump-trace*)
;? (prn memory*)
(when (~is memory*.3 t)
(prn "F - an example function that can do different things (dispatch) based on the type of its args or oargs"))
;? (quit)
(reset)
(new-trace "dispatch-multiple-calls")
(add-code
'((function test1 [
(default-space:space-address <- new space:literal 20:literal)
(first-arg-box:tagged-value-address <- next-input)
; if given integers, add them
{ begin
(first-arg:integer match?:boolean <- maybe-coerce first-arg-box:tagged-value-address/deref integer:literal)
(break-unless match?:boolean)
(second-arg-box:tagged-value-address <- next-input)
(second-arg:integers="s">"Mike", NULL, NULL, NULL, FALSE);
roster_add("Dave", NULL, NULL, NULL, FALSE);
roster_add("Jamm", NULL, NULL, NULL, FALSE);
roster_add("Jamn", NULL, NULL, NULL, FALSE);
roster_add("Matt", NULL, NULL, NULL, FALSE);
roster_add("Jamo", NULL, NULL, NULL, FALSE);
roster_add("Jamy", NULL, NULL, NULL, FALSE);
roster_add("Jamz", NULL, NULL, NULL, FALSE);
char* result1 = roster_contact_autocomplete("Jam", FALSE, NULL);
char* result2 = roster_contact_autocomplete(result1, FALSE, NULL);
char* result3 = roster_contact_autocomplete(result2, FALSE, NULL);
char* result4 = roster_contact_autocomplete(result3, FALSE, NULL);
char* result5 = roster_contact_autocomplete(result4, FALSE, NULL);
assert_string_equal("Jamo", result5);
free(result1);
free(result2);
free(result3);
free(result4);
free(result5);
roster_destroy();
}
void
find_twice_returns_first_when_two_match_and_reset(void** state)
{
roster_create();
roster_add("James", NULL, NULL, NULL, FALSE);
roster_add("Jamie", NULL, NULL, NULL, FALSE);
roster_add("Bob", NULL, NULL, NULL, FALSE);
char* result1 = roster_contact_autocomplete("Jam", FALSE, NULL);
roster_reset_search_attempts();
char* result2 = roster_contact_autocomplete(result1, FALSE, NULL);
assert_string_equal("James", result2);
free(result1);
free(result2);
roster_destroy();
}
void
add_contact_with_no_group(void** state)
{
roster_create();
roster_add("person@server.org", NULL, NULL, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 0);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_group(void** state)
{
roster_create();
GSList* groups = NULL;
groups = g_slist_append(groups, strdup("friends"));
roster_add("person@server.org", NULL, groups, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 1);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "friends");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_two_groups(void** state)
{
roster_create();
GSList* groups = NULL;
groups = g_slist_append(groups, strdup("friends"));
groups = g_slist_append(groups, strdup("work"));
roster_add("person@server.org", NULL, groups, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 2);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "friends");
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "work");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups(void** state)
{
roster_create();
GSList* groups = NULL;
groups = g_slist_append(groups, strdup("friends"));
groups = g_slist_append(groups, strdup("work"));
groups = g_slist_append(groups, strdup("stuff"));
roster_add("person@server.org", NULL, groups, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 3);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "friends");
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "work");
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "stuff");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups_update_adding_two(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("friends"));
groups2 = g_slist_append(groups2, strdup("work"));
groups2 = g_slist_append(groups2, strdup("stuff"));
groups2 = g_slist_append(groups2, strdup("things"));
groups2 = g_slist_append(groups2, strdup("people"));
roster_update("person@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 5);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "friends");
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "work");
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "stuff");
found = g_list_find_custom(groups_res, "things", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "things");
found = g_list_find_custom(groups_res, "people", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "people");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups_update_removing_one(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("friends"));
groups2 = g_slist_append(groups2, strdup("stuff"));
roster_update("person@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 2);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "friends");
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "stuff");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups_update_removing_two(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("stuff"));
roster_update("person@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 1);
GList* found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
assert_string_equal(found->data, "stuff");
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups_update_removing_three(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
roster_update("person@server.org", NULL, NULL, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 0);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contact_with_three_groups_update_two_new(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("newfriends"));
groups2 = g_slist_append(groups2, strdup("somepeople"));
roster_update("person@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 2);
GList* found = g_list_find_custom(groups_res, "newfriends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "somepeople", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_remove_contact_groups(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
roster_remove("person@server.org", "person@server.org");
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 0);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contacts_with_different_groups(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("newfriends"));
groups2 = g_slist_append(groups2, strdup("somepeople"));
roster_add("bob@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 5);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "newfriends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "somepeople", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contacts_with_same_groups(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("friends"));
groups2 = g_slist_append(groups2, strdup("work"));
groups2 = g_slist_append(groups2, strdup("stuff"));
roster_add("bob@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 3);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
add_contacts_with_overlapping_groups(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(groups2, strdup("friends"));
groups2 = g_slist_append(groups2, strdup("work"));
groups2 = g_slist_append(groups2, strdup("different"));
roster_add("bob@server.org", NULL, groups2, NULL, FALSE);
GList* groups_res = roster_get_groups();
assert_int_equal(g_list_length(groups_res), 4);
GList* found = g_list_find_custom(groups_res, "friends", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "work", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "stuff", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
found = g_list_find_custom(groups_res, "different", (GCompareFunc)g_strcmp0);
assert_true(found != NULL);
g_list_free_full(groups_res, free);
roster_destroy();
}
void
remove_contact_with_remaining_in_group(void** state)
{
roster_create();
GSList* groups1 = NULL;
groups1 = g_slist_append(groups1, strdup("friends"));
groups1 = g_slist_append(groups1, strdup("work"));
groups1 = g_slist_append(groups1, strdup("stuff"));
roster_add("person@server.org", NULL, groups1, NULL, FALSE);
GSList* groups2 = NULL;
groups2 = g_slist_append(