The first task, as always, is simply to learn the
bare mechanics of producing a compiledFunction
by means of an assembler.
stack: makeAssembler --> *asm* stack: makeFunction --> *fun* stack: "my-function" --> *fun*$s.name stack: *asm* reset stack: 0 *fun* *asm* finishAssembly --> *cfn* stack: *cfn* stack: #<c-fn my-function>
That's it! Our first compiled function. If we disassembled it, we would see:
constants: code bytes: 00: 2e code disassembly: 000: 2e RETURN
which is the simplest function possible in Muq: No constants and code limited to a single one-byte RETURN bytecode.
Let's go over the example in detail.
makeAssembler --> *asm*
The above creates our own assembler. It records internally
everything we tell it about what we want in the final
compiledFunction
, and then when we're done
describing it, actually builds the compiledFunction
.
makeFunction --> *fun*
The above creates our function
. Do not confuse
function
objects with compiledFunction
objects!
function
contains book-keeping information of
interest to humans, such as the source code in text
form, the compiler used to compile the source, the
date created, and the names of any local variables
used by the function. A function
has a full
compliment of propdirs, and hence may be decorated
with any extra information you like. A function
will normally spend most of its life on disk, being
fetched into memory only if some human wishes to
inspect the source code or such.
compiledFunction
is very specialized, bare-bones
object containing all and only the information needed by the
bytecode interpreter to execute function, namely the
bytecodes for the instructions, and any constants needed by
those bytecodes. (Well... and an 'owner' field for
accounting purposes, and a pointer to the corresponding
function
object.) No creation date, no propdirs, no
name. 99% lean muscle. You will almost never have cause
to do anything with a compile-function
except to
call it or to fetch the pointer to the corresponding
function
, where all the human-readable stuff is.
(Note that there may be more than one
compiledFunction
pointing to a given
function
. This is most common when implementing
lispStyle lambda closures, scheme-style promises, or
functional programming stuff. For generic Algolic sorts of
code, however, there will normally be just one
compiledFunction
for each function
.)
"my-function" --> *fun*$s.name
We name the function "my-function". This is optional; The default name is "_".
*asm* reset
A single assembler may be used to assemble many functions; In a production compiler, it is much more efficient to re-use assemblers than to create and discard a new one for each function assembled.
We use reset
to prepare our assembler to compile a
new function.
In this particular example, the assembler was freshly created and hence already reset, so we could actually have skipped this step, but we include it for didactic completeness.
nil 0 *fun* *asm* finishAssembly --> *cfn*
The finishAssembly
command triggers construction
by the assembler of the desired compiledFunction
.
The *fun*
argument is used by the assembler to
initialize the cfn$s.source
slot in the resulting
compiledFunction
to the desired function
object: For security and reliability reasons, Muq doesn't
let anything much but an assembler tinker with the contents
of a compiledFunction
, so you can't do this
yourself.
What were the NIL and 0
parameters for? We cover this
in the next section.
Go to the first, previous, next, last section, table of contents.