mu/mu_instructions

446 lines
24 KiB
Plaintext
Raw Permalink Normal View History

## Mu's instructions and their table-driven translation
2020-08-01 23:28:32 +00:00
See http://akkartik.name/akkartik-convivial-20200607.pdf for the complete
2022-04-01 13:13:20 +00:00
story. In brief: Mu is a memory-safe statement-oriented language where most
statements translate to a single instruction of machine code. Blocks consist of
flat lists of instructions. Instructions can have inputs after the operation,
and outputs to the left of a '<-'. Inputs and outputs must be variables. They
can't include nested expressions. Variables can be literals ('n'), or live in a
2020-03-27 09:08:02 +00:00
register ('var/reg') or in memory ('var') at some 'stack-offset' from the 'ebp'
register. Outputs must be registers. To modify a variable in memory, pass it in
by reference as an input. (Inputs are more precisely called 'inouts'.)
2021-01-04 06:38:41 +00:00
Conversely, registers that are just read from must not be passed as outputs.
2020-03-27 09:08:02 +00:00
The following chart shows all the instruction forms supported by Mu, along with
the SubX instruction they're translated to.
2020-03-22 00:25:52 +00:00
2020-11-01 02:55:33 +00:00
## Integer instructions
These instructions use the general-purpose registers.
2020-03-22 00:25:52 +00:00
var/eax <- increment => "40/increment-eax"
var/ecx <- increment => "41/increment-ecx"
var/edx <- increment => "42/increment-edx"
var/ebx <- increment => "43/increment-ebx"
var/esi <- increment => "46/increment-esi"
var/edi <- increment => "47/increment-edi"
increment var => "ff 0/subop/increment *(ebp+" var.stack-offset ")"
increment *var/reg => "ff 0/subop/increment *" reg
var/eax <- decrement => "48/decrement-eax"
var/ecx <- decrement => "49/decrement-ecx"
var/edx <- decrement => "4a/decrement-edx"
var/ebx <- decrement => "4b/decrement-ebx"
var/esi <- decrement => "4e/decrement-esi"
var/edi <- decrement => "4f/decrement-edi"
decrement var => "ff 1/subop/decrement *(ebp+" var.stack-offset ")"
decrement *var/reg => "ff 1/subop/decrement *" reg
var/reg <- add var2/reg2 => "01/add-to %" reg " " reg2 "/r32"
var/reg <- add var2 => "03/add *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- add *var2/reg2 => "03/add *" reg2 " " reg "/r32"
add-to var1, var2/reg => "01/add-to *(ebp+" var1.stack-offset ") " reg "/r32"
2020-09-29 03:11:40 +00:00
add-to *var1/reg1, var2/reg2 => "01/add-to *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/eax <- add n => "05/add-to-eax " n "/imm32"
var/reg <- add n => "81 0/subop/add %" reg " " n "/imm32"
add-to var, n => "81 0/subop/add *(ebp+" var.stack-offset ") " n "/imm32"
add-to *var/reg, n => "81 0/subop/add *" reg " " n "/imm32"
var/reg <- subtract var2/reg2 => "29/subtract-from %" reg " " reg2 "/r32"
var/reg <- subtract var2 => "2b/subtract *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- subtract *var2/reg2 => "2b/subtract *" reg2 " " reg1 "/r32"
subtract-from var1, var2/reg2 => "29/subtract-from *(ebp+" var1.stack-offset ") " reg2 "/r32"
2020-09-29 03:11:40 +00:00
subtract-from *var1/reg1, var2/reg2 => "29/subtract-from *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/eax <- subtract n => "2d/subtract-from-eax " n "/imm32"
var/reg <- subtract n => "81 5/subop/subtract %" reg " " n "/imm32"
subtract-from var, n => "81 5/subop/subtract *(ebp+" var.stack-offset ") " n "/imm32"
subtract-from *var/reg, n => "81 5/subop/subtract *" reg " " n "/imm32"
var/reg <- and var2/reg2 => "21/and-with %" reg " " reg2 "/r32"
var/reg <- and var2 => "23/and *(ebp+" var2.stack-offset " " reg "/r32"
var/reg <- and *var2/reg2 => "23/and *" reg2 " " reg "/r32"
and-with var1, var2/reg => "21/and-with *(ebp+" var1.stack-offset ") " reg "/r32"
2020-09-29 03:11:40 +00:00
and-with *var1/reg1, var2/reg2 => "21/and-with *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/eax <- and n => "25/and-with-eax " n "/imm32"
var/reg <- and n => "81 4/subop/and %" reg " " n "/imm32"
and-with var, n => "81 4/subop/and *(ebp+" var.stack-offset ") " n "/imm32"
and-with *var/reg, n => "81 4/subop/and *" reg " " n "/imm32"
var/reg <- or var2/reg2 => "09/or-with %" reg " " reg2 "/r32"
var/reg <- or var2 => "0b/or *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- or *var2/reg2 => "0b/or *" reg2 " " reg "/r32"
or-with var1, var2/reg2 => "09/or-with *(ebp+" var1.stack-offset " " reg2 "/r32"
2020-09-29 03:11:40 +00:00
or-with *var1/reg1, var2/reg2 => "09/or-with *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/eax <- or n => "0d/or-with-eax " n "/imm32"
var/reg <- or n => "81 1/subop/or %" reg " " n "/imm32"
or-with var, n => "81 1/subop/or *(ebp+" var.stack-offset ") " n "/imm32"
or-with *var/reg, n => "81 1/subop/or *" reg " " n "/imm32"
2021-05-17 04:46:37 +00:00
var/reg <- not => "f7 2/subop/not %" reg
not var => "f7 2/subop/not *(ebp+" var.stack-offset ")"
not *var/reg => "f7 2/subop/not *" reg
2020-03-22 00:25:52 +00:00
var/reg <- xor var2/reg2 => "31/xor-with %" reg " " reg2 "/r32"
var/reg <- xor var2 => "33/xor *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- xor *var2/reg2 => "33/xor *" reg2 " " reg "/r32"
xor-with var1, var2/reg => "31/xor-with *(ebp+" var1.stack-offset ") " reg "/r32"
2020-09-29 03:11:40 +00:00
xor-with *var1/reg1, var2/reg2 => "31/xor-with *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/eax <- xor n => "35/xor-with-eax " n "/imm32"
var/reg <- xor n => "81 6/subop/xor %" reg " " n "/imm32"
xor-with var, n => "81 6/subop/xor *(ebp+" var.stack-offset ") " n "/imm32"
xor-with *var/reg, n => "81 6/subop/xor *" reg " " n "/imm32"
2020-10-04 08:25:02 +00:00
var/reg <- negate => "f7 3/subop/negate %" reg
negate var => "f7 3/subop/negate *(ebp+" var.stack-offset ")"
negate *var/reg => "f7 3/subop/negate *" reg
2020-09-29 03:11:21 +00:00
var/reg <- shift-left n => "c1/shift 4/subop/left %" reg " " n "/imm32"
var/reg <- shift-right n => "c1/shift 5/subop/right %" reg " " n "/imm32"
var/reg <- shift-right-signed n => "c1/shift 7/subop/right-signed %" reg " " n "/imm32"
shift-left var, n => "c1/shift 4/subop/left *(ebp+" var.stack-offset ") " n "/imm32"
2020-09-29 03:11:40 +00:00
shift-left *var/reg, n => "c1/shift 4/subop/left *" reg " " n "/imm32"
2020-09-29 03:11:21 +00:00
shift-right var, n => "c1/shift 5/subop/right *(ebp+" var.stack-offset ") " n "/imm32"
2020-09-29 03:11:40 +00:00
shift-right *var/reg, n => "c1/shift 5/subop/right *" reg " " n "/imm32"
2020-09-29 03:11:21 +00:00
shift-right-signed var, n => "c1/shift 7/subop/right-signed *(ebp+" var.stack-offset ") " n "/imm32"
2020-09-29 03:11:40 +00:00
shift-right-signed *var/reg, n => "c1/shift 7/subop/right-signed *" reg " " n "/imm32"
2020-03-22 00:25:52 +00:00
var/eax <- copy n => "b8/copy-to-eax " n "/imm32"
var/ecx <- copy n => "b9/copy-to-ecx " n "/imm32"
var/edx <- copy n => "ba/copy-to-edx " n "/imm32"
var/ebx <- copy n => "bb/copy-to-ebx " n "/imm32"
var/esi <- copy n => "be/copy-to-esi " n "/imm32"
var/edi <- copy n => "bf/copy-to-edi " n "/imm32"
var/reg <- copy var2/reg2 => "89/<- %" reg " " reg2 "/r32"
copy-to var1, var2/reg => "89/<- *(ebp+" var1.stack-offset ") " reg "/r32"
2020-09-29 03:11:40 +00:00
copy-to *var1/reg1, var2/reg2 => "89/<- *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
var/reg <- copy var2 => "8b/-> *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- copy *var2/reg2 => "8b/-> *" reg2 " " reg "/r32"
var/reg <- copy n => "c7 0/subop/copy %" reg " " n "/imm32"
copy-to var, n => "c7 0/subop/copy *(ebp+" var.stack-offset ") " n "/imm32"
copy-to *var/reg, n => "c7 0/subop/copy *" reg " " n "/imm32"
var/reg <- copy-byte var2/reg2 => "8a/byte-> %" reg2 " " reg "/r32"
"81 4/subop/and %" reg " 0xff/imm32"
var/reg <- copy-byte *var2/reg2 => "8a/byte-> *" reg2 " " reg "/r32"
"81 4/subop/and %" reg " 0xff/imm32"
copy-byte-to *var1/reg1, var2/reg2 => "88/byte<- *" reg1 " " reg2 "/r32"
2020-03-22 00:25:52 +00:00
compare var1, var2/reg2 => "39/compare *(ebp+" var1.stack-offset ") " reg2 "/r32"
compare *var1/reg1, var2/reg2 => "39/compare *" reg1 " " reg2 "/r32"
compare var1/reg1, var2 => "3b/compare<- *(ebp+" var2.stack-offset ") " reg1 "/r32"
compare var/reg, *var2/reg2 => "3b/compare<- *" reg " " n "/imm32"
compare var/eax, n => "3d/compare-eax-with " n "/imm32"
2020-06-05 04:17:11 +00:00
compare var/reg, n => "81 7/subop/compare %" reg " " n "/imm32"
2020-03-22 00:25:52 +00:00
compare var, n => "81 7/subop/compare *(ebp+" var.stack-offset ") " n "/imm32"
compare *var/reg, n => "81 7/subop/compare *" reg " " n "/imm32"
var/reg <- multiply var2 => "0f af/multiply *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- multiply var2/reg2 => "0f af/multiply %" reg2 " " reg "/r32"
2020-03-22 00:25:52 +00:00
var/reg <- multiply *var2/reg2 => "0f af/multiply *" reg2 " " reg "/r32"
2020-11-01 02:55:33 +00:00
## Floating-point operations
2020-11-01 02:50:41 +00:00
2020-11-01 02:55:33 +00:00
These instructions operate on either floating-point registers (xreg) or
general-purpose registers (reg) in indirect mode.
2020-11-01 02:50:41 +00:00
var/xreg <- add var2/xreg2 => "f3 0f 58/add %" xreg2 " " xreg1 "/x32"
var/xreg <- add var2 => "f3 0f 58/add *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- add *var2/reg2 => "f3 0f 58/add *" reg2 " " xreg "/x32"
var/xreg <- subtract var2/xreg2 => "f3 0f 5c/subtract %" xreg2 " " xreg1 "/x32"
var/xreg <- subtract var2 => "f3 0f 5c/subtract *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- subtract *var2/reg2 => "f3 0f 5c/subtract *" reg2 " " xreg "/x32"
var/xreg <- multiply var2/xreg2 => "f3 0f 59/multiply %" xreg2 " " xreg1 "/x32"
var/xreg <- multiply var2 => "f3 0f 59/multiply *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- multiply *var2/reg2 => "f3 0f 59/multiply *" reg2 " " xreg "/x32"
var/xreg <- divide var2/xreg2 => "f3 0f 5e/divide %" xreg2 " " xreg1 "/x32"
var/xreg <- divide var2 => "f3 0f 5e/divide *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- divide *var2/reg2 => "f3 0f 5e/divide *" reg2 " " xreg "/x32"
There are also some exclusively floating-point instructions:
var/xreg <- reciprocal var2/xreg2 => "f3 0f 53/reciprocal %" xreg2 " " xreg1 "/x32"
var/xreg <- reciprocal var2 => "f3 0f 53/reciprocal *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- reciprocal *var2/reg2 => "f3 0f 53/reciprocal *" reg2 " " xreg "/x32"
var/xreg <- square-root var2/xreg2 => "f3 0f 51/square-root %" xreg2 " " xreg1 "/x32"
var/xreg <- square-root var2 => "f3 0f 51/square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- square-root *var2/reg2 => "f3 0f 51/square-root *" reg2 " " xreg "/x32"
var/xreg <- inverse-square-root var2/xreg2 => "f3 0f 52/inverse-square-root %" xreg2 " " xreg1 "/x32"
var/xreg <- inverse-square-root var2 => "f3 0f 52/inverse-square-root *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- inverse-square-root *var2/reg2 => "f3 0f 52/inverse-square-root *" reg2 " " xreg "/x32"
var/xreg <- min var2/xreg2 => "f3 0f 5d/min %" xreg2 " " xreg1 "/x32"
var/xreg <- min var2 => "f3 0f 5d/min *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- min *var2/reg2 => "f3 0f 5d/min *" reg2 " " xreg "/x32"
var/xreg <- max var2/xreg2 => "f3 0f 5f/max %" xreg2 " " xreg1 "/x32"
var/xreg <- max var2 => "f3 0f 5f/max *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- max *var2/reg2 => "f3 0f 5f/max *" reg2 " " xreg "/x32"
Remember, when these instructions use indirect mode, they still use an integer
register. Floating-point registers can't hold addresses.
Most instructions operate exclusively on integer or floating-point operands.
The only exceptions are the instructions for converting between integers and
floating-point numbers.
var/xreg <- convert var2/reg2 => "f3 0f 2a/convert-to-float %" reg2 " " xreg "/x32"
var/xreg <- convert var2 => "f3 0f 2a/convert-to-float *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- convert *var2/reg2 => "f3 0f 2a/convert-to-float *" reg2 " " xreg "/x32"
Converting floats to ints performs rounding by default. (We don't mess with the
MXCSR control register.)
var/reg <- convert var2/xreg2 => "f3 0f 2d/convert-to-int %" xreg2 " " reg "/r32"
var/reg <- convert var2 => "f3 0f 2d/convert-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- convert *var2/reg2 => "f3 0f 2d/convert-to-int *" reg2 " " reg "/r32"
There's a separate instruction for truncating the fractional part.
var/reg <- truncate var2/xreg2 => "f3 0f 2c/truncate-to-int %" xreg2 " " reg "/r32"
var/reg <- truncate var2 => "f3 0f 2c/truncate-to-int *(ebp+" var2.stack-offset ") " reg "/r32"
var/reg <- truncate *var2/reg2 => "f3 0f 2c/truncate-to-int *" reg2 " " reg "/r32"
There are no instructions accepting floating-point literals. To obtain integer
literals in floating-point registers, copy them to general-purpose registers
and then convert them to floating-point.
One pattern you may have noticed above is that the floating-point instructions
above always write to registers. The only exceptions are `copy` instructions,
which can write to memory locations.
var/xreg <- copy var2/xreg2 => "f3 0f 11/<- %" xreg " " xreg2 "/x32"
copy-to var1, var2/xreg => "f3 0f 11/<- *(ebp+" var1.stack-offset ") " xreg "/x32"
var/xreg <- copy var2 => "f3 0f 10/-> *(ebp+" var2.stack-offset ") " xreg "/x32"
var/xreg <- copy *var2/reg2 => "f3 0f 10/-> *" reg2 " " xreg "/x32"
Comparisons must always start with a register:
compare var1/xreg1, var2/xreg2 => "0f 2f/compare %" xreg2 " " xreg1 "/x32"
compare var1/xreg1, var2 => "0f 2f/compare *(ebp+" var2.stack-offset ") " xreg1 "/x32"
2020-11-01 03:38:11 +00:00
## Blocks
In themselves, blocks generate no instructions. However, if a block contains
variable declarations, they must be cleaned up when the block ends.
Clean up var on the stack => "81 0/subop/add %esp " size-of(var) "/imm32"
Clean up var/reg => "8f 0/subop/pop %" reg
Clean up var/xreg => "f3 0f 10/-> *esp " xreg "/x32"
"81 0/subop/add %esp 4/imm32"
## Jumps
Besides having to clean up any variable declarations (see above) between
themselves and their target, jumps translate like this:
2020-03-22 00:25:52 +00:00
break => "e9/jump break/disp32"
break label => "e9/jump " label ":break/disp32"
loop => "e9/jump loop/disp32"
loop label => "e9/jump " label ":loop/disp32"
break-if-= => "0f 84/jump-if-= break/disp32"
break-if-= label => "0f 84/jump-if-= " label ":break/disp32"
loop-if-= => "0f 84/jump-if-= loop/disp32"
loop-if-= label => "0f 84/jump-if-= " label ":loop/disp32"
break-if-!= => "0f 85/jump-if-!= break/disp32"
break-if-!= label => "0f 85/jump-if-!= " label ":break/disp32"
loop-if-!= => "0f 85/jump-if-!= loop/disp32"
loop-if-!= label => "0f 85/jump-if-!= " label ":loop/disp32"
break-if-< => "0f 8c/jump-if-< break/disp32"
break-if-< label => "0f 8c/jump-if-< " label ":break/disp32"
loop-if-< => "0f 8c/jump-if-< loop/disp32"
loop-if-< label => "0f 8c/jump-if-< " label ":loop/disp32"
break-if-> => "0f 8f/jump-if-> break/disp32"
break-if-> label => "0f 8f/jump-if-> " label ":break/disp32"
loop-if-> => "0f 8f/jump-if-> loop/disp32"
loop-if-> label => "0f 8f/jump-if-> " label ":loop/disp32"
break-if-<= => "0f 8e/jump-if-<= break/disp32"
break-if-<= label => "0f 8e/jump-if-<= " label ":break/disp32"
loop-if-<= => "0f 8e/jump-if-<= loop/disp32"
loop-if-<= label => "0f 8e/jump-if-<= " label ":loop/disp32"
break-if->= => "0f 8d/jump-if->= break/disp32"
break-if->= label => "0f 8d/jump-if->= " label ":break/disp32"
loop-if->= => "0f 8d/jump-if->= loop/disp32"
loop-if->= label => "0f 8d/jump-if->= " label ":loop/disp32"
break-if-addr< => "0f 82/jump-if-addr< break/disp32"
break-if-addr< label => "0f 82/jump-if-addr< " label ":break/disp32"
loop-if-addr< => "0f 82/jump-if-addr< loop/disp32"
loop-if-addr< label => "0f 82/jump-if-addr< " label ":loop/disp32"
break-if-addr> => "0f 87/jump-if-addr> break/disp32"
break-if-addr> label => "0f 87/jump-if-addr> " label ":break/disp32"
loop-if-addr> => "0f 87/jump-if-addr> loop/disp32"
loop-if-addr> label => "0f 87/jump-if-addr> " label ":loop/disp32"
break-if-addr<= => "0f 86/jump-if-addr<= break/disp32"
break-if-addr<= label => "0f 86/jump-if-addr<= " label ":break/disp32"
loop-if-addr<= => "0f 86/jump-if-addr<= loop/disp32"
loop-if-addr<= label => "0f 86/jump-if-addr<= " label ":loop/disp32"
break-if-addr>= => "0f 83/jump-if-addr>= break/disp32"
break-if-addr>= label => "0f 83/jump-if-addr>= " label ":break/disp32"
loop-if-addr>= => "0f 83/jump-if-addr>= loop/disp32"
loop-if-addr>= label => "0f 83/jump-if-addr>= " label ":loop/disp32"
2020-10-01 07:49:09 +00:00
Similar float variants like `break-if-float<` are aliases for the corresponding
`addr` equivalents. The x86 instruction set stupidly has floating-point
operations only update a subset of flags.
Four sets of conditional jumps are useful for detecting overflow.
break-if-carry => "0f 82/jump-if-carry break/disp32"
break-if-carry label => "0f 82/jump-if-carry " label "/disp32"
loop-if-carry => "0f 82/jump-if-carry break/disp32"
loop-if-carry label => "0f 82/jump-if-carry " label "/disp32"
break-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
break-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
loop-if-not-carry => "0f 83/jump-if-not-carry break/disp32"
loop-if-not-carry label => "0f 83/jump-if-not-carry " label "/disp32"
break-if-overflow => "0f 80/jump-if-overflow break/disp32"
break-if-overflow label => "0f 80/jump-if-overflow " label ":break/disp32"
loop-if-overflow => "0f 80/jump-if-overflow loop/disp32"
loop-if-overflow label => "0f 80/jump-if-overflow " label ":loop/disp32"
break-if-not-overflow => "0f 81/jump-if-not-overflow break/disp32"
break-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":break/disp32"
loop-if-not-overflow => "0f 81/jump-if-not-overflow loop/disp32"
loop-if-not-overflow label => "0f 81/jump-if-not-overflow " label ":loop/disp32"
All this relies on a convention that every `{}` block is delimited by labels
ending in `:loop` and `:break`.
## Returns
The `return` instruction cleans up variable declarations just like an unconditional
`jump` to end of function, but also emits a series of copies before the final
`jump`, copying each argument of `return` to the register appropriate to the
respective function output. This doesn't work if a function output register
contains a later `return` argument (e.g. if the registers for two outputs are
swapped in `return`), so you can't do that.
return => "c3/return"
---
2020-03-22 00:25:52 +00:00
In the following instructions types are provided for clarity even if they must
be provided in an earlier 'var' declaration.
2020-02-01 20:14:12 +00:00
2020-03-25 16:54:53 +00:00
# Address operations
2020-03-24 10:49:37 +00:00
var/reg: (addr T) <- address var2: T
=> "8d/copy-address *(ebp+" var2.stack-offset ") " reg "/r32"
2020-05-27 07:01:12 +00:00
# Array operations
2020-11-18 06:05:45 +00:00
var/reg: (addr T) <- index arr/rega: (addr array T), idx/regi: int
| if size-of(T) is 1, 2, 4 or 8
=> "81 7/subop/compare %" rega " 0/imm32"
"0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
"(__check-mu-array-bounds *" rega " %" regi " " size-of(T) ")"
"8d/copy-address *(" rega "+" regi "<<" log2(size-of(T)) "+4) " reg "/r32"
2020-11-18 06:05:45 +00:00
var/reg: (addr T) <- index arr: (array T len), idx/regi: int
=> "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") %" regi " " size-of(T) ")"
"8d/copy-address *(ebp+" regi "<<" log2(size-of(T)) "+" (arr.stack-offset + 4) ") " reg "/r32"
2020-11-18 06:05:45 +00:00
var/reg: (addr T) <- index arr/rega: (addr array T), n
=> "81 7/subop/compare %" rega " 0/imm32"
"0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
"(__check-mu-array-bounds *" rega " " n " " size-of(T) ")"
"8d/copy-address *(" rega "+" (n*size-of(T)+4) ") " reg "/r32"
2020-11-18 06:05:45 +00:00
var/reg: (addr T) <- index arr: (array T len), n
=> "(__check-mu-array-bounds *(ebp+" arr.stack-offset ") " n " " size-of(T) ")"
"8d/copy-address *(ebp+" (arr.stack-offset+4+n*size-of(T)) ") " reg "/r32"
var/reg: (offset T) <- compute-offset arr: (addr array T), idx/regi: int # arr can be in reg or mem
2020-05-24 15:02:11 +00:00
=> "69/multiply %" regi " " size-of(T) "/imm32 " reg "/r32"
var/reg: (offset T) <- compute-offset arr: (addr array T), idx: int # arr can be in reg or mem
2020-05-24 15:02:11 +00:00
=> "69/multiply *(ebp+" idx.stack-offset ") " size-of(T) "/imm32 " reg "/r32"
2021-08-26 05:17:19 +00:00
var/reg: (offset T) <- compute-offset arr: (addr array T), n # arr can be in reg or mem
=> "c7 0/subop/copy %" reg " " n*size-of(T) "/imm32"
2020-11-18 06:05:45 +00:00
var/reg: (addr T) <- index arr/rega: (addr array T), o/rego: (offset T)
=> "81 7/subop/compare %" rega " 0/imm32"
"0f 84/jump-if-= __mu-abort-null-index-base-address/disp32"
"(__check-mu-array-bounds %" rega " %" rego " 1 \"" function-name "\")"
"8d/copy-address *(" rega "+" rego "+4) " reg "/r32"
2020-05-24 20:27:08 +00:00
Computing the length of an array is complex.
2020-05-24 15:02:11 +00:00
2020-11-18 06:05:45 +00:00
var/reg: int <- length arr/reg2: (addr array T)
2020-05-24 15:02:11 +00:00
| if T is byte (TODO)
=> "8b/-> *" reg2 " " reg "/r32"
2020-05-24 20:27:08 +00:00
| if size-of(T) is 4 or 8 or 16 or 32 or 64 or 128
=> "8b/-> *" reg2 " " reg "/r32"
"c1/shift 5/subop/logic-right %" reg " " log2(size-of(T)) "/imm8"
| otherwise
2020-05-24 20:27:08 +00:00
x86 has no instruction to divide by a literal, so
we need up to 3 extra registers! eax/edx for division and say ecx
=> if reg is not eax
"50/push-eax"
if reg is not ecx
"51/push-ecx"
if reg is not edx
"52/push-edx"
"8b/-> *" reg2 " eax/r32"
"31/xor %edx 2/r32/edx" # sign-extend, but array size can't be negative
"b9/copy-to-ecx " size-of(T) "/imm32"
"f7 7/subop/idiv-eax-edx-by %ecx"
if reg is not eax
"89/<- %" reg " 0/r32/eax"
if reg is not edx
"5a/pop-to-edx"
if reg is not ecx
"59/pop-to-ecx"
if reg is not eax
"58/pop-to-eax"
2020-05-24 15:02:11 +00:00
2020-03-25 16:54:53 +00:00
# User-defined types
2020-02-28 00:58:16 +00:00
If a record (product) type T was defined to have elements a, b, c, ... of
types T_a, T_b, T_c, ..., then accessing one of those elements f of type T_f:
2020-06-05 20:56:19 +00:00
var/reg: (addr T_f) <- get var2/reg2: (addr T), f
=> "81 7/subop/compare %" reg2 " 0/imm32"
"0f 84/jump-if-= __mu-abort-null-get-base-address/disp32"
"8d/copy-address *(" reg2 "+" offset(f) ") " reg "/r32"
2020-06-05 20:56:19 +00:00
var/reg: (addr T_f) <- get var2: T, f
2020-03-22 00:25:52 +00:00
=> "8d/copy-address *(ebp+" var2.stack-offset "+" offset(f) ") " reg "/r32"
2020-02-28 00:58:16 +00:00
When the base is an address we perform a null check.
# Allocating memory
allocate in: (addr handle T)
=> "(allocate Heap " size-of(T) " " in ")"
populate in: (addr handle array T), num # can be literal or variable on stack or register
=> "(allocate-array2 Heap " size-of(T) " " num " " in ")"
2020-08-01 23:25:34 +00:00
populate-stream in: (addr handle stream T), num # can be literal or variable on stack or register
=> "(new-stream Heap " size-of(T) " " num " " in ")"
2020-11-16 06:54:56 +00:00
# Some miscellaneous helpers to avoid error-prone size computations
clear x: (addr T)
=> "(zero-out " s " " size-of(T) ")"
2020-08-01 23:25:34 +00:00
read-from-stream s: (addr stream T), out: (addr T)
=> "(read-from-stream " s " " out " " size-of(T) ")"
write-to-stream s: (addr stream T), in: (addr T)
=> "(write-to-stream " s " " in " " size-of(T) ")"
vim:ft=mu:nowrap:textwidth=0