Readme-driven development for Mu's floating-point operations.
This commit is contained in:
Kartik Agaram 2020-09-28 22:19:43 -07:00
parent 3a2888ae47
commit 9aea89ba73
2 changed files with 167 additions and 5 deletions

95
mu.md
View File

@ -127,16 +127,24 @@ var name/reg: type <- ...
Variables on the stack are never initialized. (They're always implicitly
zeroed them out.) Variables in registers are always initialized.
Register variables can go in 6 registers: `eax`, `ebx`, `ecx`, `edx`, `esi`
and `edi`. Defining a variable in a register either clobbers the previous
variable (if it was defined in the same block) or shadows it temporarily (if
it was defined in an outer block).
Register variables can go in 6 integer registers: `eax`, `ebx`, `ecx`, `edx`,
`esi` and `edi`. Floating-point values can also go in 8 other registers:
`xmm0`, `xmm1`, `xmm2`, `xmm3`, `xmm4`, `xmm5`, `xmm6` and `xmm7`.
Defining a variable in a register either clobbers the previous variable (if it
was defined in the same block) or shadows it temporarily (if it was defined in
an outer block).
Variables exist from their definition until the end of their containing block.
Register variables may also die earlier if their register is clobbered by a
new variable.
## Arithmetic primitives
Variables on the stack can be of many types (but not `byte`). Variables in
integer registers can only contain 32-bit values: `int`, `boolean`, `(addr
...)`. Variables in floating-point registers can only contain values of type
`float`.
## Integer primitives
Here is the list of arithmetic primitive operations supported by Mu. The name
`n` indicates a literal integer rather than a variable, and `var/reg` indicates
@ -208,6 +216,83 @@ Excluding dereferences, the above statements must operate on non-address
primitive types: `int` or `boolean`. (Booleans are really just `int`s, and Mu
assumes any value but `0` is true.)
## Floating-point primitives
These instructions may use the floating-point registers `xmm0` ... `xmm7`
(denoted by `/xreg2` or `/xrm32`). They also use integer values on occasion
(`/rm32` and `/r32`). They can't take literal floating-point values.
```
var/xreg <- add var2/xreg2
var/xreg <- add var2
var/xreg <- add *var2/reg2
var/xreg <- subtract var2/xreg2
var/xreg <- subtract var2
var/xreg <- subtract *var2/reg2
var/xreg <- multiply var2/xreg2
var/xreg <- multiply var2
var/xreg <- multiply *var2/reg2
var/xreg <- divide var2/xreg2
var/xreg <- divide var2
var/xreg <- divide *var2/reg2
var/xreg <- reciprocal var2/xreg2
var/xreg <- reciprocal var2
var/xreg <- reciprocal *var2/reg2
var/xreg <- square-root var2/xreg2
var/xreg <- square-root var2
var/xreg <- square-root *var2/reg2
var/xreg <- inverse-square-root var2/xreg2
var/xreg <- inverse-square-root var2
var/xreg <- inverse-square-root *var2/reg2
var/xreg <- min var2/xreg2
var/xreg <- min var2
var/xreg <- min *var2/reg2
var/xreg <- max var2/xreg2
var/xreg <- max var2
var/xreg <- max *var2/reg2
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
var/xreg <- convert var2
var/xreg <- convert *var2/reg2
var/reg <- convert var2/xreg2
var/reg <- convert var2
var/reg <- convert *var2/reg2
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
copy-to var1, var2/xreg
var/xreg <- copy var2
var/xreg <- copy *var2/reg2
Floating-point comparisons always put a register on the left-hand side:
compare var1/xreg1, var2/xreg2
compare var1/xreg1, var2
```
## Operating on individual bytes
A special-case is variables of type 'byte'. Mu is a 32-bit platform so for the

View File

@ -262,4 +262,81 @@ read-from-stream s: (addr stream T), out: (addr T)
write-to-stream s: (addr stream T), in: (addr T)
=> "(write-to-stream " s " " in " " size-of(T) ")"
# Floating-point operations
All the instructions so far use Intel's general-purpose integer registers.
However, some of them translate to different SubX if their arguments are in
floating-point registers.
var/xreg <- add var2/xreg2 => "f3 0f 58/add 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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 3/mod " xreg2 "/xm32 " 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"
var/reg <- convert var2/xreg2 => "f3 0f 2d/convert-to-int 3/mod " xreg2 "/xm32 " 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 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/<- 3/mod " xreg "/xm32 " 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 3/mod " xreg2 "/xm32 " xreg1 "/x32"
compare var1/xreg1, var2 => "0f 2f 2/mod *(ebp+" var2.stack-offset ") " xreg1 "/x32"
vim:ft=mu:nowrap:textwidth=0