6896
Readme-driven development for Mu's floating-point operations.
This commit is contained in:
parent
3a2888ae47
commit
9aea89ba73
95
mu.md
95
mu.md
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue