Commit 39604478 authored by Kunshan Wang's avatar Kunshan Wang

Added status flags (NZCV) to binOp.

In the text form, the NZCV flags are optional, so existing programs
are still valid. In the bundle building API, `new_binop` remain
unchanged, but an additional function `new_binop_with_status` is added,
so existing programs are still valid.

scripts/*.py are modified to adapt the MuBinOpStatus enum type.
parent 58d95aec
This diff is collapsed.
...@@ -5,15 +5,16 @@ Instruction Set ...@@ -5,15 +5,16 @@ Instruction Set
Overview Overview
======== ========
Mu uses a variant of static single assignment (SSA) form and a comprehensive but Mu uses a variant of static single assignment (SSA) form and has a comprehensive
low-level instruction set. but low-level instruction set.
Conventions Conventions
=========== ===========
If the results of an instruction is not mentioned, the instruction produces no In Mu, each instruction could produce 0 or more results, depending on the
results. Otherwise an instruction produces one result, unless explicitly stated instruction. In this chapter, if the results of an instruction is not mentioned,
otherwise. the instruction produces no results. Otherwise an instruction produces one
result, unless explicitly stated otherwise.
In all examples in this chapter, the following definitions are assumed to be In all examples in this chapter, the following definitions are assumed to be
present:: present::
...@@ -345,10 +346,14 @@ Basic Operations ...@@ -345,10 +346,14 @@ Basic Operations
Binary Operations Binary Operations
----------------- -----------------
*binOp* ``<`` *T* ``>`` *op1* *op2* *excClause* *binOp* *statusFlags* :sub:`opt` ``<`` *T* ``>`` *op1* *op2* *excClause*
*statusFlags* ::= ``[`` ``#N`` :sub:`opt` ``#Z`` :sub:`opt` ``#C`` :sub:`opt` ``#V`` :sub:`opt` ``]``
binOp binOp
The binary operation. The binary operation.
statusFlags :sub:`opt`
*flag list*: A list of produced status flags. See below.
T T
*type*: The type of both operands. *type*: The type of both operands.
op1, op2 op1, op2
...@@ -356,7 +361,9 @@ op1, op2 ...@@ -356,7 +361,9 @@ op1, op2
excClause excClause
*exception clause*: the destination for erroneous conditions. *exception clause*: the destination for erroneous conditions.
result: result:
Type *T*: return the result of the computation. - The first result: Type *T*: return the result of the computation.
- Subsequent results: As many ``int<1>`` results as the *statusFlags*.
*binOp* is one in the following table: *binOp* is one in the following table:
...@@ -383,7 +390,47 @@ result: ...@@ -383,7 +390,47 @@ result:
FREM 0xB4 FP FP remainder FREM 0xB4 FP FP remainder
========= ======== ===== ======================== ========= ======== ===== ========================
TODO: Move the opcode to the IR Builder API. *statusFlags*:
======= ========================= ====================
flag applicable to description
======= ========================= ====================
#N all integer operations negative
#Z all integer operations zero
#C ADD, SUB, MUL unsigned overflow
#V ADD, SUB, MUL signed overflow
======= ========================= ====================
The binOp instruction is the most basic arithmetic and logical operations for
integer and floating point operands of both scalar and vector form.
The binOp instruction may have 0 to 4 status flags. Each flag can be ``#N``,
``#Z``, ``#C`` or ``#V``, and must appear in this order (N before Z before C
before V).
NOTE: The reason for fixing the order of flags is to make the C-based
bundle building API simpler. In the C language, it can use the logical "or"
operator on bit flags, like ``MuBinOpStatus flags = MU_BOS_N | MU_BOS_Z |
MU_BOS_C | MU_BOS_V``.
The binOp instruction produces ``1+n`` results where ``n`` is the
number of flags. The first result is always the result of computation, and has
type *T*. Other results have type ``int<1>``, each corresponding to the next
status flags in the *statusFlags* lists.
The ``N`` and the ``Z`` flags are applicable for all **scalar integer**
operations, while ``C`` and ``V`` are only applicable for ADD, SUB and MUL on
**scalar** operands.
NOTE: Vector (SIMD) operations work very differently from scalar operations
on most architectures. They don't raise any flags. Floating point operations
usually handle exceptions by raising floating point flags (not usable for
branching or conditional operations) or returning NaN values.
For implementation reasons, it is *recommended* for the client to generate
instruction sequences that use the flags immediately after they are produced (by
SELECT, BRANCH2, or other instructions). Micro VM implementations are encouraged
to optimise for this idiom.
In all binary operations, the type of both *op1* and *op2* must have type *T*. In all binary operations, the type of both *op1* and *op2* must have type *T*.
...@@ -396,36 +443,53 @@ vector of floating point type. ...@@ -396,36 +443,53 @@ vector of floating point type.
When *T* is a vector type, the operation is applied for the corresponding When *T* is a vector type, the operation is applied for the corresponding
elements of the two vectors. elements of the two vectors.
SDIV, SREM, UDIV and UREM may have exception clause. For other operations, the Only SDIV, SREM, UDIV and UREM may have exception clauses. Other operations must
exception clause must be omitted. not have exception clauses.
For ADD, SUB and MUL, both operands are considered unsigned. When overflow, ADD, SUB and MUL are applicable for both signed and unsigned operands. The
returns the result modulo 2^n where n is the length of *T*. result is the last n bits of the mathematical result, where n is the bit length
of the integer type. The ``#C`` and the ``#V`` flags are produced for unsigned
and signed overflow, respectively. "Overflow" means the result is not equal to
the mathematical result of the unsigned/signed operation.
NOTE: Since negative numbers are encoded in the 2's complement notation, NOTE: Since negative numbers are encoded in the 2's complement notation,
ADD, SUB and MUL work for both signed and unsigned numbers. ADD, SUB and MUL work for both signed and unsigned numbers. However, when
multiplying two n-bit numbers, the result can have up to 2n bits. Most
machines actually support multiplications of the form "n * n -> 2n".
However, such MUL operations need to distinguish between signed and unsigned
operands. In Mu, we follow the LLVM's tradition and suggest the client use
the idiom: when doing signed multiplication, first sign-extend both operands
from n to 2n bits, and then use the MUL instruction; for unsigned operands,
zero-extend both operands. After the operation, the higher and lower n bits
can be obtained by truncating and shifting the result.
For SDIV, SREM, UDIV and UREM, when dividing by zero, it *continues For SDIV, SREM, UDIV and UREM, when dividing by zero, it *continues
exceptionally*. For SDIV and SREM, when overflow, the result is truncated to n exceptionally*. For SDIV and SREM, when overflow, the result is truncated to n
bits where n is the length of *T*. bits where n is the length of *T*.
NOTE: This means when doing 32-bit signed integer division, ``-0x80000000 /
-1 = -0x80000000``; but ``42 / 0`` will branch to the exceptional
destination, or have undefined behaviour if the exception clause is not
given.
For SHL, LSHR and ASHR, the second operand *op2* is considered unsigned. Only For SHL, LSHR and ASHR, the second operand *op2* is considered unsigned. Only
the lowest m bits of *op2* are used, where m is the smallest integer that 2^m >= the lowest m bits of *op2* are used, where m is the smallest integer that 2^m >=
n and n is the length *T*. n and n is the length *T*. The result is truncated to n bits.
NOTE: For 32-bit and 64-bit integers, the lowest 5 and 6 bits of *op2* are NOTE: For 32-bit and 64-bit integers, the lowest 5 and 6 bits of *op2* are
used, respectively. This is the "natural" behaviour of x86_64 and A64, but used, respectively. This is the "natural" behaviour of x86_64 and A64, but
not ARMv7. not ARMv7. It needs special consideration to implement shifting operations
on ARMv7.
All floating point operations follow the IEEE 754 standard. They use the All floating point operations follow the IEEE 754 standard. They use the
default roundTiesToEven rounding attribute. If either operand is NaN, the default roundTiesToEven rounding attribute. If either operand is NaN, the
result is NaN. Floating point exceptions do not cause exceptional control flow result is NaN. Floating point exceptions do not cause exceptional control flow
in Mu. in Mu.
Semantics: Semantics of operations:
ADD ADD
Return the sum of the two operands. Return the sum of the two operands.
SUB SUB
Return the difference of the two operands. Return the difference of the two operands.
MUL MUL
...@@ -466,6 +530,20 @@ FDIV ...@@ -466,6 +530,20 @@ FDIV
FREM FREM
Return the remainder of the two operands. Return the remainder of the two operands.
Semantics of status flags (the ``int<1>`` result is 1 if the condition is true,
0 otherwise):
N
The result is negative, i.e. the highest bit is 1.
Z
The result is zero.
C
Unsigned overflow, i.e. the result is not equal to the mathematical result
if both operands and the result are considered unsigned integers.
V
Signed overflow, i.e. the result is not equal to the mathematical result if
both operands and the result are considered signed integers.
.. ..
For LLVM users: this is directly borrowed from LLVM. Exceptional cases, For LLVM users: this is directly borrowed from LLVM. Exceptional cases,
...@@ -476,7 +554,6 @@ FREM ...@@ -476,7 +554,6 @@ FREM
Example:: Example::
.const @a <@i32> = 42 .const @a <@i32> = 42
.const @x <@double> = 42.0d .const @x <@double> = 42.0d
.const @z <@i64> = 0 .const @z <@i64> = 0
...@@ -498,6 +575,42 @@ FREM ...@@ -498,6 +575,42 @@ FREM
%handler(): %handler():
... // handle divide-by-zero error here ... // handle divide-by-zero error here
Example 2, Handling overflow by branching and trapping::
%bb1():
(%b %ovf) = ADD [ #V ] <@i32> @a @I32_1
BRANCH2 %ovf %bb2(%b) %bb3(%b)
%bb2(<@i32> %b):
[%the_overflow_trap] TRAP <> KEEPALIVE (%b) // if signed overflow occured, branch here
...
%bb3(<@i32> %b):
... // otherwise, branch here
Example 3, The following snippet tries to produce many flags and does not
consume the flags immediately. It is correct and will always work as
intended, but may not always perform equally well on all platforms. For
example, on x86, the machine-level SUB, MUL and CMP instructions overwrites
the machine flags::
(%b %neg %carry %ovf) = ADD [ #N #C #V ] <@i32> @a @I32_1
%blah = SUB <@i32> @a @a
%c = SELECT <@i32> %neg @I32_1 @I32_0
%blah2 = MUL <@i32> @a @a
%d = SELECT <@i32> %carry @I32_2 @I32_0
%blah3 = LT <@i32> %a @I32_0
%e = SELECT <@i32> %ovf @I32_3 @I32_0
It is recommended to consume the flags immediately after they are produced::
.const @I32_N1 <@i32> = -1
(%b %neg %zero) = ADD [ #N #Z ] <@i32> @a @I32_1
%c1 = SELECT <@i32> %zero @I32_0 @I32_1
%c = SELECT <@i32> %neg @I32_N1 %c1
// In this example, c = b < 0 ? -1 : b == 0 ? 0 : 1
Comparison Comparison
---------- ----------
......
...@@ -646,6 +646,21 @@ The canonical definition of each instruction is in the `Instruction Set ...@@ -646,6 +646,21 @@ The canonical definition of each instruction is in the `Instruction Set
MuVarNode opnd2, MuVarNode opnd2,
MuExcClause exc_clause); MuExcClause exc_clause);
void (*new_binop_with_status)(MuIRBuilder *b, MuID id, MuID result_id,
MuID *status_result_ids, MuArraySize n_status_result_ids,
MuBinOpStatus status_flags,
MuBinOptr optr,
MuTypeNode ty,
MuVarNode opnd1,
MuVarNode opnd2,
MuExcClause exc_clause); /// MUAPIPARSER exc_clause:optional
typedef MuFlag MuBinOpStatus;
#define MU_BOS_N ((MuBinOpStatus)0x01)
#define MU_BOS_Z ((MuBinOpStatus)0x02)
#define MU_BOS_C ((MuBinOpStatus)0x04)
#define MU_BOS_V ((MuBinOpStatus)0x08)
typedef MuFlag MuBinOptr; typedef MuFlag MuBinOptr;
#define MU_BINOP_ADD ((MuBinOptr)0x01) #define MU_BINOP_ADD ((MuBinOptr)0x01)
#define MU_BINOP_SUB ((MuBinOptr)0x02) #define MU_BINOP_SUB ((MuBinOptr)0x02)
...@@ -666,7 +681,14 @@ The canonical definition of each instruction is in the `Instruction Set ...@@ -666,7 +681,14 @@ The canonical definition of each instruction is in the `Instruction Set
#define MU_BINOP_FDIV ((MuBinOptr)0xB3) #define MU_BINOP_FDIV ((MuBinOptr)0xB3)
#define MU_BINOP_FREM ((MuBinOptr)0xB4) #define MU_BINOP_FREM ((MuBinOptr)0xB4)
``new_binop`` creates a binary operation. ``new_binop`` and ``new_binop_with_status`` create a binary operation.
``result_id`` is the result of the operation; ``status_result_ids`` is an array,
each element is a desired status flag result, in the order of N, Z, C, and then
V, and ``n_status_result_ids`` is its length.
``status_flags`` is the set of desired status flags. It is the logical OR
(``|``) of the ``MuBinOIpStatus`` constants.
``optr`` is the binary operator. ``ty`` is the operand type. ``opnd1`` and ``optr`` is the binary operator. ``ty`` is the operand type. ``opnd1`` and
``opnd2`` are the two operands. ``opnd2`` are the two operands.
...@@ -674,6 +696,9 @@ The canonical definition of each instruction is in the `Instruction Set ...@@ -674,6 +696,9 @@ The canonical definition of each instruction is in the `Instruction Set
``exc_clause`` is the **optional** exception clause to handle the ``exc_clause`` is the **optional** exception clause to handle the
division-by-zero and overflow case of division and remainder operations. division-by-zero and overflow case of division and remainder operations.
``new_binop`` is equivalent to ``new_binop_with_status`` with
``status_result_ids == NULL, n_status_result_ids == 0, status_flags == 0``.
:: ::
void (*new_cmp )(MuIRBuilder *b, MuID id, MuID result_id, void (*new_cmp )(MuIRBuilder *b, MuID id, MuID result_id,
......
This diff is collapsed.
...@@ -22,6 +22,7 @@ _type_map = { ...@@ -22,6 +22,7 @@ _type_map = {
"MuBool" : "int<32>", "MuBool" : "int<32>",
"MuWPID" : "int<32>", "MuWPID" : "int<32>",
"MuArraySize" : "int<64>", "MuArraySize" : "int<64>",
"MuBinOpStatus" : "int<32>",
"MuBinOptr" : "int<32>", "MuBinOptr" : "int<32>",
"MuCmpOptr" : "int<32>", "MuCmpOptr" : "int<32>",
"MuConvOptr" : "int<32>", "MuConvOptr" : "int<32>",
......
...@@ -72,6 +72,7 @@ _top_level_structs = ["MuVM", "MuCtx", "MuIRBuilder"] ...@@ -72,6 +72,7 @@ _top_level_structs = ["MuVM", "MuCtx", "MuIRBuilder"]
_enums = [(typename, re.compile(regex)) for typename, regex in [ _enums = [(typename, re.compile(regex)) for typename, regex in [
("MuTrapHandlerResult", r'^MU_(THREAD|REBIND)'), ("MuTrapHandlerResult", r'^MU_(THREAD|REBIND)'),
("MuDestKind", r'^MU_DEST_'), ("MuDestKind", r'^MU_DEST_'),
("MuBinOpStatus", r'^MU_BOS_'),
("MuBinOptr", r'^MU_BINOP_'), ("MuBinOptr", r'^MU_BINOP_'),
("MuCmpOptr", r'^MU_CMP_'), ("MuCmpOptr", r'^MU_CMP_'),
("MuConvOptr", r'^MU_CONV_'), ("MuConvOptr", r'^MU_CONV_'),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment