One major class of programming language functionality which cannot easily be expressed simply as loading a constant or calling a function is control structure: loops, ifs and such. From the compiler writer's point of view, these are all built out of labels marking places in the code, and branch instructions jumping to these labels.
Let's assemble a simple "nil if 1 else 2 fi" function, which demonstrates all the essentials:
stack: *asm* reset stack: *asm* assembleLabelGet --> *elseLabel* stack: *asm* assembleLabelGet --> *fiLabel* stack: nil *asm* assembleConstant stack: *elseLabel* *asm* assembleBeq stack: 1 *asm* assembleConstant stack: *fiLabel* *asm* assembleBra stack: *elseLabel* *asm* assembleLabel stack: 2 *asm* assembleConstant stack: *fiLabel* *asm* assembleLabel stack: nil -1 *fun* *asm* finishAssembly --> *cfn* stack: *cfn* call{ -> $ } stack: 2
This time, a peek at the disassembly produces:
constants: 0: nil code bytes: 00: 34 00 1f 04 33 01 2b 02 33 02 2e code disassembly: 000: 34 00 GETk 0 002: 1f 04 BEQ 008: 004: 33 01 GETi 1 006: 2b 02 BRA 00a: 008: 33 02 GETi 2 00a: 2e RETURN
How does this work?
assembleLabelGet
allocates code labels.
(These are currently small integers, but your
code should be written to depend on this.)
assembleLabel
deposits a the given label
at the current spot in the code. You must
have allocated the label via
assembleLabelGet
.
assembleBne
, assembleBeq
,
assembleBra
, each deposit a branch to the
specified label. You must have allocated the
label via assembleLabelGet
.
assembleBne
assembles a conditional
branch which pops the stack and branchs iff
it is non-NIL.
assembleBeq
assembles a conditional
branch which pops the stack and branchs iff
it is NIL.
assembleBra
assembles an unconditional
branch.
Labels may be deposited before or after the matching branch. Preceding labels result in loops; following labels result in conditionals. The Muq assembler cares nothing about how well-structured your jump architecture is (although future optimizing versions of it may have difficulty optimizing nasty unstructured code).
Just be sure that every label used was properly allocated, and that every branch assembled is to a properly deposited label.
As a minor note: The Muq server implements both one- and two-byte branch offsets. The assembler uses the usual jump minimization algorithm to always use one-byte branch offsets where possible. Supporting three-byte branch offsets would be trivial, but I don't think I want people clogging the virtual memory buffer with functions more than 64K long, so I haven't implemented this. Any function producing more than 64K of code probably needs to be rewritten :).
Go to the first, previous, next, last section, table of contents.