mu/mu_summary

193 lines
4.1 KiB
Plaintext

Mu programs are lists of functions. Each function has the following form:
fn _name_ _inouts_with_types_ -> _outputs_with_types_ {
_instructions_
}
Instructions may be primitives or function calls. Either way, all instructions
have one of the following forms:
# defining variables
var _name_: _type_
var _name_/_register_: _type_
# doing things with variables
_operation_ _inouts_
_outputs_ <- _operation_ _inouts_
Instructions and functions may have inouts and outputs. Both inouts and
outputs are variables.
As seen above, variables can be defined to live in a register, like this:
n/eax
Variables not assigned a register live in the stack.
Function inouts must always be on the stack, and outputs must always be in
registers. A function call must always write to the exact registers its
definition requires. For example:
fn foo -> x/eax: int {
...
}
fn main {
a/eax <- foo # ok
a/ebx <- foo # wrong
}
Primitive inouts may be on the stack or in registers, but outputs must always
be in registers.
Functions can contain nested blocks inside { and }. Variables defined in a
block don't exist outside it.
## Primitive instructions
Primitive instructions currently supported in Mu:
var/eax <- increment
var/ecx <- increment
var/edx <- increment
var/ebx <- increment
var/esi <- increment
var/edi <- increment
increment var
var/eax <- decrement
var/ecx <- decrement
var/edx <- decrement
var/ebx <- decrement
var/esi <- decrement
var/edi <- decrement
decrement var
var1/reg1 <- add var2/reg2
var/reg <- add var2
add-to var1, var2/reg
var/eax <- add n
var/reg <- add n
add-to var, n
var1/reg1 <- sub var2/reg2
var/reg <- sub var2
sub-from var1, var2/reg
var/eax <- sub n
var/reg <- sub n
sub-from var, n
var1/reg1 <- and var2/reg2
var/reg <- and var2
and-with var1, var2/reg
var/eax <- and n
var/reg <- and n
and-with var, n
var1/reg1 <- or var2/reg2
var/reg <- or var2
or-with var1, var2/reg
var/eax <- or n
var/reg <- or n
or-with var, n
var1/reg1 <- xor var2/reg2
var/reg <- xor var2
xor-with var1, var2/reg
var/eax <- xor n
var/reg <- xor n
xor-with var, n
var/eax <- copy n
var/ecx <- copy n
var/edx <- copy n
var/ebx <- copy n
var/esi <- copy n
var/edi <- copy n
var1/reg1 <- copy var2/reg2
copy-to var1, var2/reg
var/reg <- copy var2
var/reg <- copy n
copy-to var, n
compare var1, var2/reg
compare var1/reg, var2
compare var/eax, n
compare var, n
var/reg <- multiply var2
## Primitive jump instructions
There are two kinds of jumps, both with many variations: `break` and `loop`.
`break` instructions jump to the end of the containing block. `loop` instructions
jump to the beginning of the containing block.
Jumps can take an optional label starting with '$':
loop $foo
This instruction jumps to the beginning of the block called $foo. It must lie
somewhere inside such a box. Jumps are only legal to containing blocks.
There are two unconditional jumps:
loop
loop label
# unconditional break instructions don't seem useful
The remaining jump instructions are all conditional. Conditional jumps rely on
the result of the most recently executed `compare` instruction. (To keep
programs easy to read, keep compare instructions close to the jump that uses
them.)
break-if-=
break-if-= label
break-if-!=
break-if-!= label
Inequalities are similar, but have unsigned and signed variants. We assume
unsigned variants are only ever used to compare addresses.
break-if-<
break-if-< label
break-if->
break-if-> label
break-if-<=
break-if-<= label
break-if->=
break-if->= label
break-if-addr<
break-if-addr< label
break-if-addr>
break-if-addr> label
break-if-addr<=
break-if-addr<= label
break-if-addr>=
break-if-addr>= label
Similarly, conditional loops:
loop-if-=
loop-if-= label
loop-if-!=
loop-if-!= label
loop-if-<
loop-if-< label
loop-if->
loop-if-> label
loop-if-<=
loop-if-<= label
loop-if->=
loop-if->= label
loop-if-addr<
loop-if-addr< label
loop-if-addr>
loop-if-addr> label
loop-if-addr<=
loop-if-addr<= label
loop-if-addr>=
loop-if-addr>= label