Mu uses a variant of static single assignment (SSA) form and a comprehensive but low-level instruction set.
If the results of an instruction is not mentioned, 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 present:
.typedef @i1 = int<1> .typedef @i8 = int<8> .typedef @i16 = int<16> .typedef @i32 = int<32> .typedef @i64 = int<64> .typedef @float = float .typedef @double = double .typedef @void = void .typedef @refvoid = ref<@void> .typedef @irefvoid = iref<@void> .typedef @weakrefvoid = weakref<@void> .typedef @refi64 = ref<@i64> .typedef @irefi64 = iref<@i64> .typedef @sref = stackref .typedef @tref = threadref .typedef @tagref64 = tagref64 .typedef @4xi32 = vector<@i32 4> .typedef @4xfloat = vector<@float 4> .typedef @2xdouble = vector<@double 2> .const @I32_0 <@i64> = 0 .const @I32_1 <@i64> = 1 .const @I64_0 <@i64> = 0 .const @I64_1 <@i64> = 1 .const @F_0 <@float> = 0.0f .const @F_1 <@float> = 1.0f .const @F_NAN <@float> = nanf .const @D_0 <@double> = 0.0d .const @D_1 <@double> = 1.0d .const @D_NAN <@double> = nand .const @NULLREF <@refvoid> = NULL
Mu uses a variant of static single assignment (SSA) form. The single-definition rule still applies, but PHI-nodes are replaced with parameters at the beginning of basic blocks. Variables also do not live across basic blocks, and must be explicitly passed.
An SSA variable, or variable when unambiguous, holds a data value of a specific type. Every variable is defined (assigned) in exactly one place, but an SSA variable may hold different values in different contexts at different times.
NOTE: The original publications about SSA used the term "SSA form" and simply "variable". This specification uses the term "SSA variable" to emphasise that they are not variables in the usual sense that they can be updated arbitrarily as opposite to "constants". Most SSA variables, or simply "variables", in Mu never change. Some are changeable (instructions) not because they are assigned in another place, but because they themselves are re-evaluated.
For LLVM users: The concept of SSA variable or variable is the counterpart of the concept of SSA value or value in LLVM. Mu uses the traditional terminology that "a variable holds a value".
- SSA variable
- Global SSA variable
- Constant
- Global cell reference
- Function reference
- Exposed function
- Local SSA variable
- Parameter
- Instruction result
- Global SSA variable
A global SSA variable is valid in the whole Mu instance after it is defined. Their values never change.
- A constant is a global SSA variable. Its value is the constant value.
- A global cell is a global SSA variable. Its value is an internal reference to the global cell. (NOTE: The content in the global cell can change, but its iref never changes.)
- A function is a global SSA variable. Its value is a
funcref
to the function. (NOTE: A function version is NOT an SSA variable. Redefining the function does not change the value of the function.) - An exposed function (defined by the top-level function exposing
definition, not by the
@uvm.native.expose
instruction or theexpose
API) is a global SSA variable. Its value is the calling-convention-specific exposed value.
A local SSA variable is valid in the same basic block of the same function activation (frame) it is in.
- A parameter is a local SSA variable whose value is the value passed to the basic block as an argument.
- An instruction result is a local SSA variable whose value is one of the (0, 1 or many) results of the latest evaluation of an instruction in the current frame (defined later).
Whether an SSA variable uses the memory is implementation dependent. An SSA variable does not have a memory location.
NOTE: This allows a Mu implementation to store constants in the machine instruction flow as immediate values, or save the result of some instructions in registers and also spilling some other registers to the stack.
A local variable can be live or dead.
- A parameter is live when the current instruction of the frame is in the basic block.
- An instruction result is live in its basic block after the instruction is executed. For terminator instructions, the result(s) may only be live when the instruction will continue with some destinations but not others. These cases are defined by concrete instructions.
Otherwise it is dead.
TODO: Check if the definition of "local variable being live/dead" is (or will be) used in other parts of the specification. Currently only mentioned in "a live stack contains all live local variables". This definition may not be necessary.
The execution of an instruction is called an evaluation. An evaluation determines its results, and this process is called value computation. Accessing the memory (see Memory Model) or changing the state of the execution environment is called side effect. An evaluation may have side effects.
The following grammar structures are common to several instructions.
- typeList ::=
<
type rep>
- funcSigList ::=
<[
funcSig rep]>
- argList ::=
(
var rep)
where type, funcSig and var are names of types, function signatures and SSA variables, respectively. The "rep" means there may be zero or more such names.
A type list is a list of types.
A function signature list is a list of function signatures. This is only
used in COMMINST
to distinguish from type lists. Ordinary instructions
usually take no more than one function signature as argument, so the function
signature is written in angular brackets < >
directly in the same way as
types.
An argument list is a list of SSA variables.
Example:
SWAPSTACK %swappee RET_WITH <@T1 @T2> PASS_VALUES <@U1 @U2 @U3> (%a1 %a2 %a3) %val = COMMINST @uvm.native.expose [#DEFAULT] <[@sig]> (%func @COOKIE) CALL <@sig> @func (%arg0 %arg1 %arg2) TAILCALL <@sig> @func (%arg0 %arg1 %arg2) CCALL #DEFAULT <@func_ty @sig> @func (%arg0 %arg1 %arg2) NEWTHREAD %stack PASS_VALUES <@T1 @T2 @T3> (%arg0 %arg1 %arg2) COMMINST @some.common.instruction (%arg0 %arg1 %arg2)
destClause ::= dest argList
- dest
- basic block: The destination
- argList
- list of SSA variable: Arguments to the destination basic block
The destination clause designates a basic block as the destination of branching,
either normal or exceptional. It takes many arguments, which will be received
by the normal parameters of dest
. The arguments must match the number and
the types of the normal parameters. The exceptional parameter does not need to
be explicitly passed to.
Only terminator instructions may have destination clauses.
excClause ::= ( EXC
(
nor exc )
) opt
- nor
- destination clause: The normal destination
- exc
- destination clause: The exceptional destination
The exception clause provides two destinations for instructions that may have diverging control flows. nor and exc are the normal destination and the exceptional destination, respectively.
The exception clause can be omitted. Any instruction that may have exception clauses is not a terminator (see Mu IR) if the exception clause is omitted. Otherwise it is a terminator.
Any instruction that may have exception clause may continue normally or continue exceptionally. An instruction shall continue normally unless explicitly defined otherwise.
- When continuing normally,
- if the exception clause is absent, then the execution continues with the next instruction after the current instruction;
- if the exception clause is present, then branch to the normal destination.
- When continuing exceptionally,
- if the exception clause is absent, it has undefined behaviour unless explicitly defined to be otherwise in some instructions;
- if the exception clause is present, then branch to the exceptional destination.
For example, the
CALL
instruction has an exception clause. When the exception clause is omitted, it does not have undefined behaviour. Instead the exception will be thrown out of the current function:%entry(): ... %rv1 = CALL <@sig1> @func1 (%arg0 %arg1) // throw out %rv2 = CALL <@sig2> @func2 (%arg0 %arg1) EXC(%cont(%a) %exc_hdlr(%b)) // caught locally %cont(<@T> %a): ... %exc_hdlr(<@T> %b) [%exc]: ...On the contrary,
UDIV
andSDIV
rely on the exceptional destination to handle the case of division by zero. If the exception clause is omitted and the right-hand-side operand is zero, then they have undefined behaviours:%entry(): %rv1 = SDIV <@i64> %some_val @I64_0 // undefined behaviour %rv2 = SDIV <@i64> %some_val @I64_0 EXC(%cont() %exc_hdlr()) // handled locally %cont(): ... %exc_hdlr(): ... // handle divide-by-zero error here
keepAliveClause ::= ( KEEPALIVE
(
lv rep )
) opt
- lv rep
- list of local SSA variable: A sequence of local SSA variables. Those SSA variables are forced to be alive.
The keep-alive clause keeps some local SSA variables alive. During stack introspection, exactly the local variables in the keep-alive clause of the current instruction in any stack frames can be introspected. This clause is intended to assist on-stack replacement.
A variable in the keep-alive clause is considered a "use" of it. It must obey the SSA requirement that the definition of a local variable dominates all its uses.
The absence of a keep-alive clause is equivalent to a keep-alive clause with zero local variables.
Example: The
TRAP
instruction optionally has a keep-alive clause. The values of those variables in the clause are available for the client to introspect:%a = ADD ... %b = SUB ... %c = MUL ... %trap1 = TRAP <@ty> // Does not keep alive any variables %trap2 = TRAP <@ty> KEEPALIVE(%a %b) // Keep %a and %b alive
flag ::= #
[A-Z_
] rep
flagList ::= [
flag rep ]
In the text form, a flag starts with a hash character #
which is
followed by many capital letters [A-Z]
or underscores _
.
Examples:
#DEFAULT
#STDCALL
#ADD
#SIGNED_OVERFLOW
A flag list is a list of flags. Currently only used by the COMMINST
super instruction.
Examples:
[ #FLAG_FOO #FLAG_BAR #FLAG_BAZ ]
[]
NOTE: The purpose of flags is to allow the IR to be extended without changing the IR grammar. One particular user is the calling convention. Available calling conventions differ from platform to platform and they cannot be described by a fixed set of key words.
binOp <
T >
op1 op2 excClause
- binOp
- The binary operation.
- T
- type: The type of both operands.
- op1, op2
- variable of type T: The two operands.
- excClause
- exception clause: the destination for erroneous conditions.
- result:
- Type T: return the result of the computation.
binOp is one in the following table:
binOp | opcode | T | operation |
---|---|---|---|
ADD | 0x01 | int | add |
SUB | 0x02 | int | subtract |
MUL | 0x03 | int | multiply |
SDIV | 0x04 | int | signed divide |
SREM | 0x05 | int | signed remainder |
UDIV | 0x06 | int | unsigned divide |
UREM | 0x07 | int | unsigned remainder |
SHL | 0x08 | int | left shift |
LSHR | 0x09 | int | logical right shift |
ASHR | 0x0A | int | arithmetic right shift |
AND | 0x0B | int | bit-wise and |
OR | 0x0C | int | bit-wise or |
XOR | 0x0D | int | bit-wise exclusive or |
FADD | 0xB0 | FP | FP add |
FSUB | 0xB1 | FP | FP subtract |
FMUL | 0xB2 | FP | FP multiply |
FDIV | 0xB3 | FP | FP divide |
FREM | 0xB4 | FP | FP remainder |
TODO: Move the opcode to the IR Builder API.
In all binary operations, the type of both op1 and op2 must have type T.
For ADD, SUB, MUL, SDIV, SREM, UDIV, UREM, SHL, LSHR, ASHR, AND, OR and XOR,
T must be int
of a vector of int
.
For FADD, FSUB, FMUL, FDIV and FREM, T must be a floating point type or a vector of floating point type.
When T is a vector type, the operation is applied for the corresponding elements of the two vectors.
SDIV, SREM, UDIV and UREM may have exception clause. For other operations, the exception clause must be omitted.
For ADD, SUB and MUL, both operands are considered unsigned. When overflow, returns the result modulo 2^n where n is the length of T.
NOTE: Since negative numbers are encoded in the 2's complement notation, ADD, SUB and MUL work for both signed and unsigned numbers.
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 bits where n is the length of T.
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 >= n and n is the length T.
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 not ARMv7.
All floating point operations follow the IEEE 754 standard. They use the default roundTiesToEven rounding attribute. If either operand is NaN, the result is NaN. Floating point exceptions do not cause exceptional control flow in Mu.
Semantics:
- ADD
- Return the sum of the two operands.
- SUB
- Return the difference of the two operands.
- MUL
- Return the product of the two operands.
- SDIV
- Return the quotient of the two operands, rounded towards zero.
- SREM
- Return the remainder of the two operands.
- UDIV
- Return the quotient of the two operands, rounded towards zero.
- UREM
- Return the remainder of the two operands.
- SHL
- Return the value of op1 shifted to the left by the value of some lowest bits of op2.
- LSHR
- Return the value of op1 shifted to the right by the value of some lowest bits of op2. The most significant bits of the shifted value are filled with 0.
- ASHR
- Return the value of op1 shifted to the right by the value of some lowest bits of op2. The most significant bits of the shifted value are filled with the most significant bit of op1.
- AND
- Return the result of bit-wise AND.
- OR
- Return the result of bit-wise inclusive OR.
- XOR
- Return the result of bit-wise exclusive OR.
- FADD
- Return the sum of the two operands.
- FSUB
- Return the difference of the two operands.
- FMUL
- Return the product of the two operands.
- FDIV
- Return the quotient of the two operands.
- FREM
- Return the remainder of the two operands.
For LLVM users: this is directly borrowed from LLVM. Exceptional cases, including division by zero and signed division overflow, have defined behaviours in Mu.
Example:
.const @a <@i32> = 42 .const @x <@double> = 42.0d .const @z <@i64> = 0 // in some function %entry(): %b = ADD <@i32> @a @I32_1 %c = SUB <@i32> @a %b %y = FADD <@double> @x @D_1 %z = FSUB <@double> @x %y // The presence of the exception clause makes it a terminator %d = UDIV <@i32> %b @z EXC(%cont() %handler()) // terminator %cont(): ... // continue on normal condition %handler(): ... // handle divide-by-zero error here
cmpOp <
T >
op1 op2
- cmpOp
- The comparison operation.
- T
- type: The type of both operands.
- op1, op2
- variable of T: The two operands.
- result:
- If T is a scalar type, then the result type is
int<1>
: returns 1 for true or 0 for false, or - If T is a vector type, then the result type is
vector<int<1> n>
: returns a vector ofint<1>
where each element is the result of the comparison of the corresponding elements.
- If T is a scalar type, then the result type is
cmpOp is one in the following table:
cmpOp | opcode | T | Condition |
---|---|---|---|
EQ | 0x20 | EQ-comparable | equal |
NE | 0x21 | EQ-comparable | not equal |
SGE | 0x22 | int | signed greater than or equal |
SGT | 0x23 | int | signed greater than |
SLE | 0x24 | int | signed less than or equal |
SLT | 0x25 | int | signed less than |
UGE | 0x26 | ULT-comparable | unsigned greater than or equal |
UGT | 0x27 | ULT-comparable | unsigned greater than |
ULE | 0x28 | ULT-comparable | unsigned less than or equal |
ULT | 0x29 | ULT-comparable | unsigned less than |
FFALSE | 0xC0 | FP | always false |
FTRUE | 0xC1 | FP | always true |
FUNO | 0xC2 | FP | unordered |
FUEQ | 0xC3 | FP | unordered equal |
FUNE | 0xC4 | FP | unordered not equal |
FUGT | 0xC5 | FP | unordered greater than |
FUGE | 0xC6 | FP | unordered greater than or equal |
FULT | 0xC7 | FP | unordered less than |
FULE | 0xC8 | FP | unordered less than or equal |
FORD | 0xC9 | FP | ordered |
FOEQ | 0xCA | FP | ordered equal |
FONE | 0xCB | FP | ordered not equal |
FOGT | 0xCC | FP | ordered greater than |
FOGE | 0xCD | FP | ordered greater than or equal |
FOLT | 0xCE | FP | ordered less than |
FOLE | 0xCF | FP | ordered less than or equal |
TODO: Move the opcode to the IR Builder API.
In all comparison operations, both op1 and op2 must have type T.
For EQ and NE, T must be a EQ-comparable type (see type-system.rest) or a vector of EQ-comparable types.
For SGE, SGT, SLE, SLT, UGE, UGT, ULE and ULT, T must be int
or a vector
of int
.
For FFALSE, FTRUE, FUNO, FUEQ, FUNE, FUGT, FUGE, FULT, FULE, FORD, FOEQ, FONE, FOGT, FOGE, FOLT, FOLE, T must be a floating point type or a vector of a floating point type.
If T is a vector type, the comparison is done element-wise.
All floating point operations follow the IEEE 754 standard. Floating point exceptions do not cause exceptional control flow in Mu.
Comparison operations return 1 if the condition of the comparison is true, or 0 otherwise. The conditions are:
- EQ
For integers, op1 is equal to op2.
For pointers, operands are converted to integers and then compare for EQ.
For general reference types, op1 refers to the same object/memory location/function/stack/thread as op2.
For
iref
, if any of op1 and op2 refer to a memory location whose lifetime has expired, the result is unspecified.NOTE: The reason is that Mu does not prevent dangling internal references referring to alloca cells on the stack. Two different alloca cells (likely generated by two consecutive calls of the same function) may have the same address, but are considered different memory locations in Mu. This also means that it is dangerous to leak an internal references to alloca cells out of the scope of a function invocation.
- NE
- The opposite of EQ.
- SGE
- Interpret both operands as signed values and op1 is greater than or equal to op2.
- SGT
- Interpret both operands as signed values and op1 is greater than op2.
- SLE
- Interpret both operands as signed values and op1 is less than or equal to op2.
- SLT
- Interpret both operands as signed values and op1 is less than op2.
- UGE
- For integers and pointers, interpret both operands as unsigned integral values and op1 is greater than or equal to op2.
- For internal references, if both operands refer to elements in the same memory array, the result is true if and only if the referent of op1 is after the referent of op2 or both op1 and op2 refer to the same element; otherwise the result is unspecified.
- UGT
- For integers and pointers, interpret both operands as unsigned integral values and op1 is greater than op2.
- For internal references, if both operands refer to elements in the same memory array, the result is true if and only if the referent of op1 is after the referent of op2; otherwise the result is unspecified.
- ULE
- For integers and pointers, interpret both operands as unsigned integral values and op1 is less than or equal to op2.
- For internal references, if both operands refer to elements in the same memory array, the result is true if and only if the referent of op1 is before the referent of op2 or both op1 and op2 refer to the same element; otherwise the result is unspecified.
- ULT
For integers and pointers, interpret both operands as unsigned integral values and op1 is less than op2.
For internal references, if both operands refer to elements in the same memory array, the result is true if and only if the referent of op1 is before the referent of op2; otherwise the result is unspecified.
NOTE: Memory arrays includes arrays, vectors and the variable part of hybrids in the Mu memory.
- FFALSE
- Always false.
- FTRUE
- Always true.
- FUNO
- Either operand is NaN.
- FUEQ
- Either operand is NaN or op1 is equal to op2.
- FUNE
- Either operand is NaN or op1 is not equal to op2.
- FUGT
- Either operand is NaN or op1 is greater than op2.
- FUGE
- Either operand is NaN or op1 is greater than or equal to op2.
- FULT
- Either operand is NaN or op1 is less than op2.
- FULE
- Either operand is NaN or op1 is less than or equal to op2.
- FORD
- Both operands are not NaN.
- FOEQ
- Both operands are not NaN and op1 is equal to op2.
- FONE
- Both operands are not NaN and op1 is not equal to op2.
- FOGT
- Both operands are not NaN and op1 is greater than op2.
- FOGE
- Both operands are not NaN and op1 is greater than or equal to op2.
- FOLT
- Both operands are not NaN and op1 is less than op2.
- FOLE
- Both operands are not NaN and op1 is less than or equal to op2.
NOTE: All floating point numbers of the same type are comparable, including NaN. When comparing, there can only be four results: equal, less than, greater than and unordered. Unordered is returned whenever either of the operands is NaN. Those floating point comparisons can be summarised by the following table, where a predicate is true if and only if the comparison gets one of the listed results.
Comparison unordered less than greater than equal negation FFALSE FTRUE FOEQ EQ FUNE FOGT GT FULE FOGE GT EQ FULT FOLT LT FUGE FOLE LT EQ FUGT FONE LT GT FUEQ FORD LT GT EQ FUNO FUNO unordered FORD FUEQ unordered EQ FONE FUGT unordered GT FOLE FUGE unordered GT EQ FOLT FULT unordered LT FOGE FULE unordered LT EQ FOGT FUNE unordered LT GT FOEQ FTRUE unordered LT GT EQ FFALSE
For LLVM users: this is directly borrowed from LLVM.
Example:
.const @a <@i32> = 42 .const @b <@i32> = 43 .const @w <@double> = 42.0d .const @x <@double> = 43.0d %c = GT <@i32> @a @b // 0 (false) %d = LT <@i32> @a @b // 1 (true) %y = FOGT <@double> @w @x // 0 (false) %z = FULT <@double> @w @x // 1 (true) %u = FOGT <@double> @w @D_NAN // 0 (false) (result is "unordered") %v = FUGT <@double> @w @D_NAN // 1 (true) (result is "unordered") %e = NEW <@i64> %f = EQ <@refi64> %e %e // 1 (true) (same object) %g = ALLOCA <@i64> %h = EQ <@irefi64> %g %g // 1 (true) (same location) .typedef @i64array <@i64 10> .const @I64_3 <@i64> = 3 .const @I64_5 <@i64> = 5 %ar = ALLOCA <@i64array> %ar3 = GETELEMIREF <@i64array @i64> %ar @I64_3 %ar5 = GETELEMIREF <@i64array @i64> %ar @I64_5 %i = ULT <@irefi64> %ar3 %ar5 // 1 (true) (%a3 is before %a5 in the array) %j = ULT <@irefi64> %ar3 %ar3 // 0 (false) (%a3 is not before itself) %k = ULE <@irefi64> %ar3 %ar3 // 1 (true) (%a3 is equal to itself)
convOp <
T1 T2 >
opnd
- convOp
- The conversion operation.
- T1, T2
- type: The source type and the destination type, respectively.
- opnd
- value of type T1: The operand.
- result:
- Type T2: The result of the conversion.
opct | idt | idt | idt |
---|---|---|---|
opcode | T1 | T2 | op |
convOp is one in the following table.
convOp | opcode | T1 | T2 |
---|---|---|---|
TRUNC | 0x30 | int | int |
ZEXT | 0x31 | int | int |
SEXT | 0x32 | int | int |
FPTRUNC | 0x33 | FP | FP |
FPEXT | 0x34 | FP | FP |
FPTOUI | 0x35 | FP | int |
FPTOSI | 0x36 | FP | int |
UITOFP | 0x37 | int | FP |
SITOFP | 0x38 | int | FP |
BITCAST | 0x39 | (see below) | (see below) |
REFCAST | 0x3A | (see below) | (see below) |
PTRCAST | 0x3B | (see below) | (see below) |
TODO: Move the opcode to the IR Builder API.
In all conversions, the operand opnd must have type T1. All conversions convert opnd to type T2.
T1 and T2 can be either scalar types as shown below or vector types of the types shown below as its elements. In the case of vector types, T1 and T2 must have the same length and the conversion is done element-wise.
- For TRUNC, ZEXT and SEXT, Both T1 and T2 must be integer types.
- For TRUNC, the length of T2 must be less than the length of T1.
- For ZEXT and SEXT, the length of T2 must be greater than the length of T1.
- For FPTRUNC and FPEXT, Both T1 and T2 must be floating point types.
- For FPTRUNC, the length of T2 must be less than the length of T1.
- For FPEXT, the length of T2 must be greater than the length of T1.
- For FPTOUI and FPTOSI, T1 must be a floating point type and T2 must be an integer type.
- For UITOFP and SITOFP, T1 must be an integer type and T2 must be a floating point type.
- For BITCAST, T1 and T2 must be one of the following combinations:
- T1 is an integer type and T2 is a floating point type of the same number of bits.
- T1 is a floating point type and T2 is an integer type of the same number of bits.
- For REFCAST, T1 and T2 must be one of the following combinations:
- Both T1 and T2 are
ref
. - Both T1 and T2 are
iref
. - Both T1 and T2 are
funcref
.
- Both T1 and T2 are
- For PTRCAST, T1 and T2 must be two of the three cases:
uptr<T>
for some T.ufuncptr<sig>
for some sig.int<n>
for some n.
Semantics:
- TRUNC
- Truncate the high order bits of opnd, keeping only the lowest n bits of opnd where n is the length of T2.
- ZEXT
- Fill zero bits to opnd until it reaches the length of T2.
- SEXT
- Copy the highest order bit of opnd until it reaches the length of T2.
- FPTRUNC
- Convert opnd to a smaller floating point type, rounding to nearest and round ties to even according to IEEE754.
- FPEXT
- Convert opnd to a larger floating point type. Always exact.
- FPTOUI
- Convert opnd to an unsigned integer, rounding towards zero. NaN is converted to 0. If the value of opnd is greater than or less than the maximum or minimum representable value of the result type, the result shall be the maximum or minimum representable value of that type.
- FPTOSI
- Convert opnd to a signed integer, rounding towards zero. NaN is converted to 0. If the value of opnd is greater than or less than the maximum or minimum representable value of the result type, the result shall be the maximum or minimum representable value of that type.
- UITOFP
- Interpret opnd as unsigned and convert opnd to a floating point type, rounding to nearest and round ties to even according to IEEE754.
- SITOFP
- Interpret opnd as signed and convert opnd to a floating point type, rounding to nearest and round ties to even according to IEEE754.
- BITCAST
- The result has the same bit-wise representation as opnd.
- REFCAST
- When converting between
ref
, the result refers to the same object as opnd, but may have a different referent type. - When converting between
iref
, the result refers to a memory location with the same beginning as opnd. - When converting between
funcref
, the result refers to the same function as opnd, but may treat the function as having a different signature. - In all cases, if the operand is
NULL
, the result is theNULL
value of the result type.
- When converting between
- PTRCAST
- Cast between integers and pointer types, preserving the address. If the length of the result is less than opnd, only the lowest bits are kept. If the length of the result is greater than opnd, it is zero-extended.
For LLVM users: These instructions are borrowed from LLVM. Mu currently lacks the conversion between raw pointer types and numerical types and they will be added when raw pointers are introduce.REFCAST
is Mu-specific. Mu cannot usebitcast
to cast between reference types.
Example:
.const @a <@i32> = 42 .const @a2 <@i32> = -42 %b = TRUNC <@i32 @i16> @a // is a @i16 %c = ZEXT <@i32 @i64> @a // is a @i64 %c2 = SEXT <@i32 @i64> @a2 // is a @i64 %d = UITOFP <@i32 @double> @a // is a @double %d2 = SITOFP <@i32 @double> @a2 // is a @double .const @x <@double> = 42.0d %y = FPTRUNC <@double @float> @x // is a @float %z = FPEXT <@float @double> %y // is a @double %w = FPTOSI <@double @i64> @x // is a @i64 .typedef @Foo = ... .typedef @Bar = ... .typedef @refFoo = ref<@Foo> .typedef @refBar = ref<@Bar> .typedef @irefFoo = iref<@Foo> .typedef @irefBar = iref<@Bar> %f = NEW <@Foo> // is a ref<@Foo> %g = REFCAST <@refFoo @refBar> %f // is a ref<@Bar> %h = ALLOCA <@Foo> // is a iref<@Foo> %i = REFCAST <@irefFoo @irefBar> %h // is a iref<@Bar> .typedef @ii8 = iref<@i8> // iref<int<8>> .typedef @iii8 = iref<@ii8> // iref<iref<int<8>>> .funcsig @vv = () -> () .funcsig @main_sig = (@i32 @iii8) -> (@i32) .funcdecl @j <@main_sig> %k = REFCAST <@main_sig @vv> @j // is a funcref<@vv>
SELECT
<
S T >
cond ifTrue ifFalse
- S
- type: The type of cond.
- T
- type: The type of ifTrue, ifFalse and the result.
- cond
- variable of type S: The condition.
- ifTrue, ifFalse
- variable of T: The candidates of the result.
- result:
- Type T: A value according to cond.
S can be either int<1>
or a vector type of int<1>
. When S is a
vector type of int<1>
, T must also be a vector type of the same length as
S.
cond must have type S. Both ifTrue and ifFalse must have the same type as T. The result of this instruction has type T.
When S is int<1>
, then the result is ifTrue if cond is 1, or ifFalse
if cond is 0.
When S is a vector of int<1>
, the corresponding element of the result is
the corresponding element of ifTrue if the corresponding element of cond is
1, or the corresponding element of ifFalse otherwise.
For LLVM users: This instruction is directly borrowed from LLVM's select
instruction.
Example:
.const @a <@i64> = 42 .const @I64_2 <@i64> = 2 .const @I64_100 <@i64> = 100 .const @I64_200 <@i64> = 200 %a_mod_2 = SREM <@64> @a @I64_2 %a_is_even = EQ <@64> %a_mod_2 @I64_0 %b = SELECT <@i1 @i64> %a_is_even @I64_100 @I64_200 // %b is 100 if %a is even // 200 otherwise
NOTE: The following instructions are for jumping within a function.
BRANCH
dest
- dest
- destination clause: The destination of jumping.
This instruction branches to dest.
For LLVM users: This is the same as the one-branch
br
instruction. Mu uses the goto-with-values form.Example:
%entry(): BRANCH %head(%a) %head(<@T> %a): // Continue executing here.
BRANCH2
cond ifTrue ifFalse
- cond
- variable of type
int<1>
: The condition - ifTrue, ifFalse
- destination clause: The destinations to jump to when cond is 1 or 0, respectively
If cond is 1, jump to ifTrue, otherwise jump to ifFalse.
For LLVM users: This is the same as the two-branch br
instruction. Mu
uses the goto-with-values form.
Example:
.const @a <@i64> = ... %entry(): %b = EQ <@i64> @a @I64_1 BRANCH2 %b %equal() %notequal() %equal(): // if %b is 1, jump here %notequal(): // if %b is 0, jump here
SWITCH
<
T >
opnd default {
( value dest ) rep }
- T
- type: The type of opnd and all value
- opnd
- variable of T: The value to compare against.
- default:
- destination clause: The default destination, i.e. the destination if no case matches.
- value:
- constant value of T: The case value for a branch.
- dest:
- destination clause: The destination for the corresponding case.
There are zero or more value-dest pairs.
opnd and all value must have type T. default and all dest must be basic blocks.
T must be an EQ-comparable type.
All value must be constants and must have distinct values.
If the value of opnd equals one of the value, then jump to the corresponding dest. If no such value equals opnd, then jump to default.
For LLVM users: This is the same as the
switch
instruction.Example:
.const @a <@i64> = ... .const @ONE <@i64> = 1 .const @TWO <@i64> = 2 .const @THREE <@i64> = 3 %entry(): SWITCH <@i64> @a %defbranch() { @ONE %one() @TWO %two() @THREE %three() } %defbranch(): ... %one(): ... %two(): ... %three(): ...
CALL
<
sig >
callee argList excClause keepAliveClause
TAILCALL
<
sig >
callee argList
- sig
- function signature: The signature of the callee.
- callee
- variable of type
funcref<sig>
: The callee. - argList
- argument list: Argument list.
- excClause
- exception clause: Specifies the basic block to handle Mu exceptions and stack overflow.
- keepAliveClause
- keep-alive clause: For on-stack replacement.
- results:
CALL
produce as many results as the number of return values of the callee. The type of each return value matches the corresponding return type.TAILCALL
does not produce results.
The CALL
instruction creates a new stack frame for the callee, passes the
arguments in argList and starts executing from the entry block of the callee.
The CALL
instruction sets the state of the frame of the caller to
READY<Ts> where Ts
is the return types of the callee. The callee frame's
state is ACTIVE.
A CALL
instruction continues normally when the callee returns. In this
case the results of the CALL
instruction are the return values of the
callee.
A CALL
instruction continues exceptionally when an exception is thrown
from the callee. In this case the value of the CALL
instruction is not
defined. When the exception clause is present, the exception is received by the
exceptional parameter in the exceptional destination. When the exception clause
is absent, the exception is re-thrown out of the function activation which the
CALL
instruction is in.
A CALL
instruction continues exceptionally when the function call causes a
stack overflow. In this case the value of the CALL
instruction is not
defined. When the exception clause is present, the value of the exceptional
parameter in the exceptional destination is NULL
. When the exception clause
is absent, it has undefined behaviour.
TODO: There could be a standard error system where "serious" errors can be represented as pre-defined heap objects.
Whenever CALL
continues exceptionally, the CALL
instruction does not
produce results.
NOTE: This is to say, the following program is illegal:
%bb1(): %rv = CALL <...> @foo (...) EXC(%nor() %exc(%rv)) // ERROR: %rv cannot be used in the exceptional destination %exc(<@T> %rv) [%exc]: ...
The CALL
instruction is an OSR point.
When a stack is rebound and the top frame is pausing on a CALL
instruction,
it continues normally with the results being the values received, or continues
exceptionally, catching the exception received.
NOTE: This can be the result of OSR: Upper frames are popped and the current
CALL
instruction receives values provided by the client, or by other
means of stack binding. See Threads and Stacks.
The TAILCALL
instruction replaces the caller's stack frame with a new stack
frame for the callee. If the frame below the current frame has state
READY<Ts>, the callee must have return type Ts
. The caller of the
current function becomes the caller of the function TAILCALL
calls. The
TAILCALL
instruction cannot have any exception clause and is not an OSR
point. After TAILCALL
, the new frame is in the ACTIVE state.
NOTE:
TAILCALL
is semantically similar to calling a function and immediately return the returned value, but replaces the current frame. OSR will not see the old frame.NOTE:
TAILCALL
is not the same as branching to a basic block, even the goto-with-values form makes them look similar.BRANCH
cannot branch to the entry block.TAILCALL
is also subject to function redefinition: it may ends up tail-calling a newer version of the callee, even if the callee is the current function. (The instruction can't explicitly specify which version to call.)
Calling an undefined function is allowed. Such functions will trigger a trap when executed so that the client can handle it. See Mu IR for the behaviour of undefined functions.
For LLVM users:
- Like any instructions with the exception clause,
CALL
instruction is conditionally a terminator. When the exception clause is present, it is like theinvoke
instruction in LLVM and, when absent, it is like thecall
instruction.- Mu functions may return multiple values.
- Mu uses the goto-with-values form.
- The meaning of
TAILCALL
is similar to LLVM'smusttail
: in Mu, aTAILCALL
always replaces the current stack frame.- Calling conventions cannot be specified in Mu: Mu always uses its internal calling conventions. The
CCALL
instruction is for native calls.- Arguments will not be automatically zero or sign-extended or truncated for the programmer. Conversions must be explicitly done before calling.
- The
funcref
type in Mu is a dedicated function reference, not a pointer. SeeCCALL
which calls a native function.- All parameters are passed by value and parameters are SSA Values. To pass on-stack data or arrays, use
alloca
and passiref
.- The keep-alive clause is similar to the experimental intrinsic function
@llvm.experimental.stackmap
and@llvm.experimental.patchpoint
in LLVM.
Example:
.funcsig @sig = (@double @double) -> (@double) .funcdecl @sum <@sig> .funcdef @square_sum VERSION %v1 <@sig> { %entry(<@double> %x <@double> %y): %x2 = MUL <@double> %x %x %y2 = MUL <@double> %y %y // return the result of sum(x2,y2) TAILCALL <@sig> @sum (%x2 %y2) } .funcsig @vv = () -> () .const @D_3 <@double> = 3.0d .const @D_4 <@double> = 4.0d .const @D_5 <@double> = 5.0d .const @D_6 <@double> = 6.0d .funcdef @main VERSION %v1 <@vv> { %entry(): %a = CALL <@sig> @square_sum (@D_3 @D_4) %b = CALL <@sig> @square_sum (%a @D_5) EXC(%nor(%a %b) %exc()) %nor(<@double> %a <@double> %b): %c = [%important_call_site] CALL <@sig> @square_sum (@D_5 @D_6) KEEPALIVE(%a %b) %d = CALL <@sig> @square_sum (%c %c) EXC(%nor2(%c %d) %exc()) KEEPALIVE(%a %c) %nor2(<@double> %c <@double> %d): // continue here %exc() [%the_exc]: // handle the exception }
RET
(
rv rep )
RET
rv
- rv rep
- The return value of the current function. The number and the types must match the return types of the current function.
The RET
instruction returns from the current function with a list of rv as
the return values of the current function. The types of rv values must match
the return types of the current function.
From the stack's point of view, the RET
instruction removes the current
frame, and resumes the previous frame into the ACTIVE state.
RET rv
is a syntax sugar of RET (rv)
. In order to return from a function
with zero return values, use RET ()
.
The RET
instruction itself does not produce results. The return values are
returned to the caller.
For LLVM users: Equivalent to LLVM'sret
andret void
.
Example:
.funcsig @sig1 = (@double @double) -> (@double) .funcsig @sig2 = () -> () .funcsig @sig3 = (@i64 @i64) -> (@i64 @i64) .funcdef @sum VERSION %v1 <@sig1> { %entry(<@double> %x <@double> %y): %s = ADD <@double> %x %y RET %s } .funcdef @main VERSION %v1 <@sig2> { %entry(): RET () } .funcdef @swap VERSION %v1 <@sig3> { %entry(<@i64> %x <@i64> %y): RET (%y %x) }
THROW
exc
- exc
- variable of type
ref
to any type: The exception object.
The THROW
instruction throws the exception exc
from the current function
to its caller frame. Exceptions in Mu are object references to any type.
For LLVM users: There is no equivalent in LLVM. Theresume
instruction in LLVM continues the propagation of a in-flight exception. This can also be done by Mu'sTHROW
instruction. Mu programs can create a new exception object byNEW
and throw it, where LLVM must depend on platform-specific libraries to allocate new exceptions.
Example:
.funcsig @sig = (@i64 @i64) -> (@i64) .typedef @SomeExceptionType = ... // user-defined exception type .funcdef @safe_divide VERSION %v1 <@sig> { %entry(<@double> %x <@double> %y): %y0 = EQ <@i64> %y @I64_0 BRANCH %y0 %divbyzero() %okay(%x %y) %divbyzero(): %exc = NEW <@SomeExceptionType> // initialise %exc THROW %exc %okay(<@double> %x <@double> %y): %rv = SDIV <@i64> %x %y RET %rv }
These instructions operate on the struct
, the array
and the vector
types as SSA variables.
EXTRACTVALUE
<
T index >
opnd
- T
- type, must be a
struct
: The type of the operand. - index
- integer literal The index of the field to extract.
- opnd
- variable of type T: The struct to extract value from.
- result:
- Type is the index-th field of the struct type T: The value of the index-th field of opnd.
The EXTRACTVALUE
instruction extracts the index-th field from the value of
an SSA variable opnd which has type struct
.
For LLVM users: It is the counterpart of theextractvalue
instruction in LLVM. But Mu'sEXTRACTVALUE
does not work on arrays or nestedstruct
. UseEXTRACTVALUE
multiple times to extract the field in nested structs.
Example: see the example of INSERTVALUE
INSERTVALUE
<
T index >
opnd newVal
- T
- type, must be a
struct
: The type of the operand. - index
- integer literal The index of the field to insert.
- opnd
- variable of type T: The struct to insert value into.
- newVal
- variable of the type of the index-th field of T: The new value for the field specified by index.
- result:
- Type is T: A new struct with the index-th field being newVal.
The INSERTVALUE
instruction constructs a struct value which has the same
field values as opnd except the field specified by index which has the value
newVal.
For LLVM users: It is the counterpart of theinsertvalue
instruction in LLVM. But Mu'sINSERTVALUE
does not work on arrays or nestedstruct
. Use a combination ofEXTRACTVALUE
andINSERTVALUE
to replace a field in a nested struct.
Example:
.typedef @Foo = struct <@i32 @double @float> .const @A <@i32> = 42 .const @B <@double> = 84.0d .const @C <@float> = 3.14f .const @B2 <@double> = 126.0d .const @S <@Foo> = {@A @B @C} // {42 84.0d 3.14f} %a = EXTRACTVALUE <@Foo 1> @S // %a == 84.0d and is a double %s2 = INSERTVALUE <@Foo 1> @S @B2 // %s2 == {42 126.0d 3.14f} and is a @Foo .typedef @Baz = struct <@double @double> .typedef @Bar = struct <@double @Baz @double> .const @D <@double> = 1.0d .const @E <@double> = 2.1d .const @F <@double> = 2.2d .const @G <@double> = 3.0d .const @H <@double> = 999.0d .const @U <@Baz> = {@E @F} // {2.1d 2.2d} .const @T <@Bar> = {@D @U @G} // {1.0d {2.1d 2.2d} 3.0d} %b = EXTRACTVALUE <@Bar 1> @T // %b == {2.1d 2.2d} and is a @Baz %c = EXTRACTVALUE <@Baz 0> %b // %c == 2.1d and is a double %b2 = INSERTVALUE <@Baz 0> %b @H // %b2 == {999.0d 2.2d} and is a @Baz %t2 = INSERTVALUE <@Bar 1> @T %c // %t2 == {1.0d {999.9d 2.2d} 3.0d} and is a @Bar
EXTRACTELEMENT
<
T1 T2 >
opnd index
- T1
- type, must be an
array
orvector
type: The type of opnd - T2
- type, must be an
int
type: The type of index. - opnd
- variable of type T1: The array or vector to extract from.
- index
- variable of type T2: The index of the element to extract.
- result:
- Type is the element type of T1: The extracted element.
The EXTRACTELEMENT
instruction extracts the element at index from an array
or vector value opnd of vector type T1.
The index may have any integer type and is treated as unsigned. If index exceeds the length of the array or vector, it has undefined behaviour.
For LLVM users: This is the counterpart of the extractelement
instruction of LLVM.
Example: See the example in SHUFFLEVECTOR
.
INSERTELEMENT
<
T1 T2 >
opnd index newVal
- T1
- type, must be an
array
orvector
type: The type of opnd. - T2
- type, must be an
int
type: The type of index. - opnd
- variable of type T1: The array or vector to insert into.
- index
- variable of type T2: The index of the element to insert.
- newVal
- variable of the element type of T1: The new value for that element.
- result:
- Type is T1: A new array or vector with the index-th element being newVal.
The INSERTELEMENT
instruction creates a new array or vector value which has
the same element values as opnd except the element at index which has the
value of newVal.
The index may have any integer type and is treated as unsigned. If index exceeds the length of the array or vector, it has undefined behaviour.
For LLVM users: This is the counterpart of the insertelement
instruction
of LLVM.
Example: See the example in SHUFFLEVECTOR
.
SHUFFLEVECTOR
<
T1 T2 >
vec1 vec2 mask
- T1
- type, must be a
vector
: The type of vec1 and vec2. - T2
- type, must be a
vector
of anyint
type: The type of mask. - vec1, vec2
- variables of type T1: The two vectors to select values from.
- mask
- variable of type T2: The mask that determines the elements to select from vec1 and vec2.
- result:
- Type is a vector type of the element type of T1 and the size of T2: The new vector with elements selected from T1 and T2 according to mask.
The SHUFFLEVECTOR
instruction constructs a permutation of elements from two
vectors vec1 and vec2. The result is a vector with the same element type as
T1 and the same length as T2.
The elements of vec1 and vec2 are numbered from left to right. Specifically, the i-th element in vec1 is numbered i and the j-th element in vec2 is numbered n+j where n is the length of T1. Then the element in the result vector is the element of the correspondent number in the mask vector. Specifically, the k-th element of the result vector is the element numbered l where l equals the value of the k-th element of mask.
The element type of mask can be any integer type and is treated as unsigned. If any element in mask is not within the range between 0 and 2*n-1, inclusively, where n is the length of T1, then it has undefined behaviour.
For LLVM users: This is the counterpart of the shufflevector
instruction
in LLVM.
Example:
.const @S0 <@float> = 3.1f .const @S1 <@float> = 4.1f .const @S2 <@float> = 5.9f .const @S3 <@float> = 2.6f .const @S4 <@float> = 5.3f .const @S5 <@float> = 5.8f .const @S6 <@float> = 9.7f .const @S7 <@float> = 9.3f .const @V0 <@4xfloat> = { @S0 @S1 @S2 @S3 } // { 3.1f 4.1f 5.9f 2.6f } .const @V1 <@4xfloat> = { @S4 @S5 @S6 @S7 } // { 5.3f 5.8f 9.7f 9.3f } .const @I32_2 <@i32> = 2 .const @I32_3 <@i32> = 3 .const @I32_4 <@i32> = 4 .const @I32_5 <@i32> = 5 .const @I32_6 <@i32> = 6 .const @I32_7 <@i32> = 7 .const @M0 <@4xi32> = { @I32_1 @I32_0 @I32_2 @I32_1 } .typedef @8xi32 = vector <@i32 8> .typedef @8xfloat = vector <@float 8> .const @M1 <@8xi32> = { @I32_7 @I32_5 @I32_6 @I32_4 @I32_2 @I32_1 @I32_3 @I32_0 } %a = EXTRACTELEMENT <@4xfloat @i64> @V0 @I64_2 // 5.9f %b = INSERTELEMENT <@4xfloat @i64> @V0 @I64_3 @S7 // { 3.1f 4.1f 5.9f 9.3f } %c1 = SHUFFLEVECTOR <@4xfloat @4xi32> @V0 @V1 @M0 // { 4.1f 3.1f 5.9f 4.1f } %c2 = SHUFFLEVECTOR <@4xfloat @8xi32> @V0 @V1 @M1 // { 9.3f 5.8f 9.7f 5.3f 5.9f 4.1f 2.6f 3.1f }
This family of instructions allocate memory on the heap or the stack.
NEW
<
T1>
excClauseNEWHYBRID
<
T1 T2>
length excClauseALLOCA
<
T1>
excClauseALLOCAHYBRID
<
T1 T2>
length excClause
- T1
type: The type to allocate.
- For
NEW
andALLOCA
: T must not behybrid
. - For
NEWHYBRID
andALLOCAHYBRID
: T must behybrid
.
- For
- T2
- type, must be
int
: The type of length. - excClause
- exception clause: Specifies the basic block to handle allocation failure.
- result:
- For
NEW
andNEWHYBRID
: Type isref<T>
: An object reference to the newly allocated object. - For
ALLOCA
andALLOCAHYBRID
: Type isiref<T>
: An internal reference to the newly allocated stack cell.
- For
The NEW
instruction allocates an object of a fixed-length type T on the
heap. Return an object reference to it.
The NEWHYBRID
instruction allocates an object of the hybrid
type T1
type on the heap. Return an object reference to it.
The ALLOCA
instruction allocates a stack cell of a fixed-length type T on
the stack. Return an internal reference to it.
The ALLOCAHYBRID
instruction allocates a stack cell of a hybrid
type
T1 on the stack. Return an internal reference to it.
For NEWHYBRID
and ALLOCAHYBRID
, length is the length of the variable
part of the hybrid. T2 can be any integer type and is treated as unsigned.
If the allocation failed, this instruction continues exceptionally.
For LLVM users: LLVM does not have instructions for heap allocation.
malloc
is the unmanaged counterpart.
ALLOCA
is similar to LLVM'salloca
with a fixed-length type.ALLOCAHYBRID
is similar toalloca
with a number of elements. Ahybrid
in Mu may have a fixed prefix. This is similar to C99 flexible array member likestruct Something { ...; char suffix[];};
.
Example:
.typedef @Foo = struct <@i64 @float @double> .typedef @Bar = hybrid <@Foo @i8> .const @I64_42 <@i64> = 42 %n1 = NEW <@i64> // ref<@i64> %n2 = NEW <@Foo> // ref<@Foo> %nh = NEWHYBRID <@Bar @i64> @I64_42 // ref<@Bar> %a1 = ALLOCA <@i64> // iref<@i64> %a2 = ALLOCA <@Foo> // iref<@Foo> %ah = ALLOCAHYBRID <@Bar @i64> @I64_42 // iref<@Bar>
This family of instructions manipulates references/pointers, but does not actually read or write memory.
GETIREF
<
T >
opnd
- T
- type: The referent type of opnd.
- opnd
- variable of type
ref<T>
: The object reference to derive internal reference. - result:
- Type is
iref<T>
: An internal reference of the memory location of type T at the beginning of the memory location of object opnd.
NOTE: The cryptic description of the return value means the referent type
T
may be a prefix (see Mu and the Memory) of the
actual type represented in the object. In this case, this instruction only
provides access to the prefix part.
The GETIREF
instruction converts the object reference opnd to an internal
reference. The resulting internal reference refers to the memory location of the
heap object opnd.
For LLVM users: There is no equivalent instructions in LLVM.
Example:
%a = NEW <@i32> %b = GETIREF <@i32> %a
GETFIELDIREF
PTR
opt<
T1 index>
opndGETELEMIREF
PTR
opt<
T1 T2>
opnd indexSHIFTIREF
PTR
opt<
T1 T2>
opnd offsetGETVARPARTIREF
PTR
opt<
T1>
opnd
- PTR opt
- If present, the types of opnd and the result are pointers. Otherwise they are internal references.
- T1
type: The referent type of opnd.
- For
GETFIELDIREF
, T1 must bestruct
orhybrid
. - For
GETELEMIREF
, T1 must bearray
. - For
GETVARPARTIREF
, T1 must behybrid
.
- For
- T2
- type, must be
int
: The type of index (forGETELEMIREF
) and offset (forSHIFTIREF
). - index
- For
GETFIELDIREF
: integer literal: The index of the field. - For
GETELEMIREF
: variable of type T2: The index of the element.
- For
- offset
- variable of type T2: The offset to move the opnd.
- opnd
- variable of type
iref<T>
oruptr<T>
: The reference/pointer to derive reference from. - result
Type is
iref<U>
oruptr<U>
: The derived reference/pointer.- For
GETFIELDIREF
, U is the type of the index-th field of struct T1 or the fixed part of hybrid T1. - For
GETELEMIREF
, U is the element type of T1. - For
SHIFTIREF
, U is the same as T1. - For
GETVARPARTIREF
, U is the type of the elements of the variable part of T1.
- For
The GETFIELDIREF
instruction gets an internal reference/pointer to the
index-th field of the struct or the fixed part of the hybrid referenced by
opnd.
The GETELEMIREF
instruction gets an internal reference/pointer to the
index-th element of the array referenced by opnd.
The SHIFTIREF
instruction assumes the opnd already refers to an element of
a memory array (including arrays, vectors and the variable part of hybrids,
see Mu and the Memory). It moves opnd internal
reference/pointer forward by offset elements. If offset is negative, it
moves the reference/pointer backwards by the absolute value of offset.
For GETELEMIREF
and SHIFTIREF
, T2 can be any integer type and is
treated as signed. When working with internal references, if the resulting
memory location is outside the range of the array, it has undefined behaviour.
The GETVARPARTIREF
instruction gets an internal reference/pointer to the
first (0-th) element in the variable part of the hybrid referenced by opnd.
When working with internal references, if the variable part of opnd has zero
elements, the GETVARPARTIREF
has undefined behaviour.
For LLVM users: This family of instructions are essentially a decomposed
version of the getelementptr
instruction.
Example:
.typedef @Foo = struct <@i64 @float @double> %foo_r = NEW <@Foo> // ref<@Foo> %foo_i = GETIREF <@Foo> %foo_r // iref<@Foo> %foo_1 = GETFIELDIREF <@Foo 1> // iref<@float> .typedef @Bar = array <@i32 10> .const @I64_5 <@i64> = 5 .const @I64_NEG_2 <@i64> = -2 %bar_r = NEW <@Bar> // ref<@Bar> %bar_i = GETIREF <@Bar> %bar_r // iref<@Bar> %bar_5 = GETELEMIREF <@Bar @i64> %bar_i @I64_5 // iref<@i32> the 5th element %bar_6 = SHIFTIREF <@Bar @i64> %bar_5 @I64_1 // iref<@i32> the 6th element %bar_4 = SHIFTIREF <@Bar @i64> %bar_6 @I64_NEG_2 // iref<@i32> the 4th element .typedef @Baz = hybrid <@Foo @i8> .const @I64_42 <@i64> = 42 %baz_i = ALLOCAHYBRID <@Baz @i64> @I64_42 // iref<@Baz> %baz_f = GETFIELDIREF <@Baz 0> %baz_i // iref<@Foo> %baz_v = GETVARPARTIREF <@Baz> %baz_i // iref<@i8> the 0th element %baz_v5 = SHIFTIREF <@i8 @i64> %baz_i @I64_5 // iref<@i8> the 5th element .typedef @BigHybrid = hybrid <@i32 @i64 @double @i8> // 3 fields in the fixed part. %bh_i = ALLOCAHYBRID <@BigHybrid @i64> @I64_42 // iref<@BigHybrid> %bh_0 = GETFIELDIREF <@BigHybrid 0> %bh_i // iref<@i32> %bh_1 = GETFIELDIREF <@BigHybrid 1> %bh_i // iref<@i64> %bh_2 = GETFIELDIREF <@BigHybrid 2> %bh_i // iref<@double> %bh_v = GETVARPARTIREF <@BigHybrid> %bh_i // iref<@i8> the 0th element %bh_v2 = SHIFTIREF <@i8 @i64> %bh_v @I64_2 // iref<@i8> the 1st element
Example 2:
.typedef @Foo = struct <@i64 @float @double> %foo_r = NEW <@Foo> // ref<@Foo> %foo_p = COMMINST @uvm.native.pin <@Foo> (%foo_r) // uptr<@Foo> %foo_1 = GETFIELDIREF PTR <@Foo 1> // uptr<@float> COMMINST @uvm.native.unpin <@Foo> (%foo_r) // @foo_p and @foo_1 should not be used from now on.
This family of instructions actually load from or store into the memory.
The LOAD
, STORE
, CMPXCHG
and ATOMICRMW
instructions have
optional exceptional clauses. When the memory location argument loc is a NULL
reference, the instruction will continue exceptionally.
A memory order is one in the following table. Their semantics are described in memory-model.rest. Different instructions have different requirements for their memory orders. See memory-model.rest.
Keyword | Binary | Semantic |
---|---|---|
NOT_ATOMIC | 0x00 | not atomic |
RELAXED | 0x01 | relaxed |
CONSUME | 0x02 | consume |
ACQUIRE | 0x03 | acquire |
RELEASE | 0x04 | release |
ACQ_REL | 0x05 | acquire+release |
SEQ_CST | 0x06 | sequentially consistent |
TODO: Move the binary codes to the IR Builder API.
LOAD
PTR
opt ord opt <
T >
loc excClause
PTR
opt- If present, loc is a pointer. Otherwise it is an internal reference.
- ord opt
- memory order: The memory order. Default:
NOT_ATOMIC
- T
- type: The referent type of loc.
- loc
- variable of type
iref<T>
oruptr<T>
: The memory location/address to load from. - excClause
- exception clause: Used to handle NULL reference errors.
- result
- Type is the strong variant of T: The result of the load operation.
The LOAD
instruction performs a load operation with argument (ord, T,
loc) as defined in Mu and the Memory.
In the text form, ord can be omitted and defaults to NOT_ATOMIC
.
For LLVM users: This is the counterpart of theload
instruction.volatile
is absent in Mu because memory accesses are part of the behaviours.
Example:
.typedef @Foo = struct<@i64 @double @float> %r = NEW <@Foo> // ref<@Foo> %i = GETIREF <@Foo> %r // iref<@Foo> %i1 = GETFIELDIREF <@Foo 1> %i // iref<double> %d = LOAD <@double> %i1 // double // Note: all allocations initialise the memory to zero.
STORE
PTR
opt ord opt <
T >
loc newVal excClause
PTR
opt- If present, loc is a pointer. Otherwise it is an internal reference.
- ord opt
- memory order: The memory order. Default:
NOT_ATOMIC
- T
- type: The referent type of loc.
- loc
- variable of type
iref<T>
oruptr<T>
: The memory location/address to store into. - excClause
- exception clause: Used to handle NULL reference errors.
- newVal
- Type is the strong variant of T: The new value to store.
The STORE
instruction performs a store operation with argument (ord,
T, loc, newVal) as defined in Mu and the Memory.
In the text form, ord can be omitted and defaults to NOT_ATOMIC
.
For LLVM users: This is the counterpart of thestore
instruction.volatile
is absent.
Example:
.const @D_PI <@double> = 3.14159d .typedef @Foo = struct<@i64 @double @float> %r = NEW <@Foo> // ref<@Foo> %i = GETIREF <@Foo> %r // iref<@Foo> %i1 = GETFIELDIREF <@Foo 1> %i // iref<double> STORE <@double> %i1 @D_PI
CMPXCHG
PTR
opt WEAK
opt ordSucc ordFail <
T
>
loc expected desired excClause
PTR
opt- If present, loc is a pointer. Otherwise it is an internal reference.
WEAK
opt- If present, the
CMPXCHG
operation is weak. - ordSucc, ordFail
- memory order: The memory order for success and failure, respectively.
- T
- type, must be EQ-comparable: The referent type of loc.
- loc
- variable of
iref<T>
oruptr<T>
: The memory location/address to access. - expected
- variable of the strong variant of T: The expected value in the memory.
- desired
- variable of the strong variant of T: The new value to store into the memory.
- excClause
- exception clause: Used to handle NULL reference errors.
- results:
The underlying compare exchange operation produces a pair
(v, s)
. TheCMPXCHG
instruction produces two results:- Type is
U
where U is the strong variant of T: The valuev
. - Type is
int<1>
: The results
. 1 for success and 0 for failure.
- Type is
The CMPXCHG
instruction performs a compare exchange operation with
argument (isWeak, ordSucc, ordFail, T, loc, expected, desired),
where isWeak is true if WEAK
is present, otherwise false. The operation
is defined in Mu and the Memory.
This instruction produces two results which represent the v and s value as defined in Mu and the Memory. The second result is 1 for true and 0 for false.
For LLVM users: This is the counterpart of the cmpxchg
instruction.
Example:
.global @foo <@i64> .typedef @ResultType = struct<@i64 @i1> %orig = LOAD <@i64> @foo // @i64 %squared = MUL <@i64> %orig %orig // @i64 (%old %succ) = CMPXCHG ACQ_REL RELAXED <@i64> @foo %orig %squared // struct<@i64 @i1> BRANCH2 %succ %cont() %failed()
Example 2: This function tries to atomically update the content of
@foo
to its square by repeatedly doingCMPXCHG
, and returns the old value just before the actual atomic update:.global @foo <@i64> .typedef @ResultType = struct<@i64 @i1> .funcsig @atomic_square.sig = () -> (@i64) .funcdef @atomic_square VERSION %v1 <@atomic_square.sig> { %entry(): %old = LOAD <@i64> @foo // @i64 BRANCH %loop(%old) %loop(<@i64> %old): %squared = MUL <@i64> %old %old // @i64 (%old2 %succ) = CMPXCHG WEAK ACQ_REL RELAXED <@i64> @foo %old %squared // <@i64 @i1> BRANCH2 %succ %done(%old2) %loop(%old2) %done(<@i64> %old): RET %old }
ATOMICRMW
PTR
opt ord op <
T >
loc opnd excClause
PTR
opt- If present, loc is a pointer. Otherwise it is an internal reference.
- ord
- memory order: The memory order.
- op
- AtomicRMW operator: The operator.
- T
- type: The referent type of loc.
- loc
- variable of
iref<T>
oruptr<T>
: The memory location/address to access. - opnd
- variable of the strong variant of T: The right-hand-side of the operation.
- excClause
- exception clause: Used to handle NULL reference errors.
- result:
- Type is the strong variant of T: The result of the atomic-x operation where x is op.
An AtomicRMW operator is one of the following:
Keyword | Binary | T | Semantic |
---|---|---|---|
XCHG | 0x00 | any | exchange |
ADD | 0x01 | int | add |
SUB | 0x02 | int | subtract |
AND | 0x03 | int | bitwise and |
NAND | 0x04 | int | bitwise nand |
OR | 0x05 | int | bitwise or |
XOR | 0x06 | int | bitwise xor |
MAX | 0x07 | int | signed max |
MIN | 0x08 | int | signed min |
UMAX | 0x09 | int | unsigned max |
UMIN | 0x0A | int | unsigned min |
TODO: Move the binary code to the IR Builder API.
The ATOMICRMW
instruction performs an atomic-x operation with argument
(ord, T, loc, opnd), where the x in atomic-x is op. The operation
is defined in Mu and the Memory.
For LLVM users: This is the counterpart of theatomicrmw
instruction.volatile
is absent.
Example:
.global @foo <@i64> %old = ATOMICRMW SEQ_CST ADD <@i64> @foo 42
FENCE
ord
- ord
- memory order: The memory order.
The FENCE
is a fence of memory order ord. Its semantics is specified in
memory-model.rest.
For LLVM users: This is the counterpart of the fence
instruction.
Example:
FENCE ACQUIRE FENCE RELEASE FENCE ACQ_REL FENCE SEQ_CST
TRAP
<
Ts >
excClause keepAliveClause
WATCHPOINT
wpid <
Ts >
dis ena ( WPEXC
(
exc )
) opt keepAliveClause
- wpid
- integer literal: Watchpoint identifier.
- Ts
- list of types: The types of the return values.
- excClause
- exception clause: For
TRAP
, specify the basic block to handle exception. - keepAliveClause
- keep-alive clause: For on-stack replacement.
- dis
- destination clause: For
WATCHPOINT
, the normal destination before the watchpoint is enabled. - ena
- destination clause: For
WATCHPOINT
, the normal destination after the watchpoint is enabled. - exc
- Optional destination clause: For
WATCHPOINT
, the exceptional destination after the watchpoint is enabled. This can be omitted. - results
- When the stack is rebound passing values, the results are those values passed. There are as many results as the types Ts, matching each type.
TRAP
and WATCHPOINTS
are designed to let the trap handler registered by
the client handle occasions which the Mu micro VM cannot handle by itself.
A TRAP
instruction is always enabled. A WATCHPOINT
can be enabled or
disabled by its watch point ID wpid. A disabled WATCHPOINT
does not
produce any results and unconditionally branches to the destination dis.
The API provides functions to enable or disable watchpoints.
A TRAP
or an enabled WATCHPOINT
temporarily unbinds the current thread
from the current stack, leaving the current frame in the READY< Ts >
state, and transfers the control to a trap handler in the Mu client. When the
current stack is bound again, this instruction may continue normally with
results of types Ts, and may continue exceptionally with a Mu exception.
When rebinding to a stack stopping at an enabled TRAP
or WATCHPOINT
instruction, if rebinding (normally) with values, the results of the TRAP
or
WATCHPOINT
instruction are the values passed to the current stack, and the
instruction continues normally. Specifically, WATCHPOINT
branches to the
ena destination.
When rebinding with an exception, the TRAP
or WATCHPOINT
continues
exceptionally. Specifically, WATCHPOINT
branches to the exc destination
where the exception is received by the exception parameter. When the exception
clause of TRAP
or the exc destination of WATCHPOINT
is absent, the
exception is thrown out of the current frame.
If a WATCHPOINT
is enabled when it is executed, but is disabled before the
current stack is rebound, the current WATCHPOINT
instruction continues as if
it is enabled.
NOTE: This means a particular evaluation of a
WATCHPOINT
determines its "enabled-ness" as soon as the instruction starts. The client may disable the very same watchpoint in the trap handler, but it does not make it branch to the dis destination.Consider the following example:
[%wp] WATCHPOINT 42 <> %dis() %ena() %exc()If the client disabled watch point 42 when handling this watchpoint and rebinds the current stack (see Threads and Stacks for stack/thread rebinding), then it will branch to
%ena
. The%wp
instruction will branch to%dis
if and only if watch point 42 is disabled when it is executed.
It is recommended to name the instruction so that the trap handler can identify
each individual traps (using the syntax %rv = [%trap_name] TRAP <@T> ...
).
TRAP
and WATCHPOINT
are OSR points.
NOTE: They are deliberately designed as OSR points. Its designed to interact with the client, and OSR is one of the main purposes.
For LLVM users: LLVM has the llvm.trap
intrinsic function, but its
semantics is not defined. LLVM itself does not have on-stack replacement
support, but Lameed and Hendren proposed a framework for
supporting on-stack replacement in LLVM.
Example 1:
%bb1(): %rv1 = [%trap1] TRAP <@i64> EXC(%bb2() %bb3()) KEEPALIVE(%a %b %c ...) %bb2(): // %trap1 normally continues here %bb3() [%exc] // %trap1 handles exception here %bb4(): %rv2 = [%wp1] WATCHPINT 1 <@i64> %bb5() %bb6() WPEXC(%bb7()) KEEPALIVE(%a %b %c ...) %bb5(): // %wp1 normally continues here when disabled %bb6(): // %wp1 normally continues here when enabled %bb7() [%exc]: // %wp1 handles exception here when enabled
Example 2: Use
TRAP
to let the client do anything, for instance, obtain the version number of Mu:.funcdef @some_func VERSION %v1 <...> { %entry(...): %version_num = [%the_trap] TRAP <@i64> CALL <...> @print (%version_num) ... }The hypothetical client identifies this concrete
TRAP
instruction by its ID or its global name@some_func_v1.the_trap
. Then it can help this particular instruction by returning normally, supplying its version number.
Example 3: use loop back-edge counting and an unconditional
TRAP
to handle frequently-executed loops:.const @LOOP_THRESHOLD <@i64> = 1000 .global @loop_count_for_some_loop <@i64> %loop_head(...): ... BRANCH2 %loop_cond %loop_body(...) %loop_exit(...) %loop_body(...): ... %old_count = LOAD <@i64> @loop_count_for_some_loop %new_count = ADD <@i64> %old_count @I64_1 STORE <@i64> @loop_count_for_some_loop %new_count %is_frequent = SGE <@i64> %new_count @LOOP_THRESHOLD BRANCH2 %is_frequent %to_trap(...) %loop_head(...) %to_trap(...): [%trap12345] TRAP <> KEEPALIVE(%some %local %variables) ... %loop_exit(...):
Example 4: use
WATCHPOINT
for de-optimising speculatively generated code. This code demonstrates a virtual table lookup:.const @SomeVFuncOffset <@i64> = 9 %bb1(...): %obj = ... %vtable = CALL <...> @get_virtual_table (%obj) %vt_ent = CALL <...> @get_virtual_table_entry (%vtable @SomeVFuncOffset) %func = LOAD <@SomeVFuncType> %vt_ent %rv = CALL <...> %func (%obj ...) ...But if the client knows that the class has no subclass, it can speculatively use static call rather than virtual call:
%bb1(...): %obj = ... [%wp1234] WATCHPOINT 5678 <> %bb1_cont(%obj) %bb2() KEEPALIVE(%obj) %bb1_cont(<@ref_T> %obj): %rv = CALL <...> @TheActualFunction (%obj ...) ... %bb2(): THROW @NULLREF // unreachable because the client should have // replaced the frame and not let the %wp1234 // continue at all.Then when the client loaded a new class which extends the class of
%obj
and overrides the virtual function, then the speculatively generated code is no longer valid. Then the client can enable the watchpoint 5678 and the%wp1234
instruction will go to the client.
WPBRANCH
wpid dis ena
- wpid
- integer literal: Watchpoint identifier.
- dis, ena
- destination clause: The destinations to jump to when wpid is disabled or enabled.
WPBRANCH
:
The WPBRANCH
instruction is a binary branching instruction. The destination
is determined by whether the watchpoint ID wpid is enabled or disabled. It can
be enabled or disabled in the same way as WATCHPOINT
.
Unlike WATCHPOINT
, this instruction does not trap to the client, and is not
an OSR point.
Example:
%entry(): WPBRANCH 42 %cont() %oops() %cont(): // continue normally here %oops(): // Oops! Something happened!
CCALL
callConv <
T sig >
callee argList excClause keepAliveClause
- callConv
- flag: The calling convention.
- T
- type: The type of callee.
- sig
- function signature: The signature of the callee.
- callee
- variable of type T: The callee.
- argList
- argument list: Arguments to the callee.
- excClause
- exception clause: Specify the basic block to handle exception.
- keepAliveClause
- keep-alive clause: For on-stack replacement.
- results
- The return values of the callee. There are as many results as the return types in sig, matching each return type.
calling convention is one in the following table or others defined by the platform-specific part of the Mu native interface:
Flag | Binary |
---|---|
#DEFAULT | 0x00 |
TODO: Move the binary flag into the IR Builder API.
The CCALL
instruction calls a native function.
NOTE: The name CCALL
indicates that it is intended to call C functions.
callConv specifies the calling convention used by this instruction.
The callee must have type T. The allowed type of T
, and the number of
return values, are implementation-dependent and calling convention-dependent.
See native-interface.rest for a detailed description of the native interface.
The return values of CCALL
are the return values of the native callee.
CCALL
may have an exception clause. How CCALL
receives exceptions from
the native callee is implementation-defined.
When executing the CCALL
instruction, the current frame enters the
READY<Rs> state, where Rs are the return types of the callee. It creates a
native frame which is in the ACTIVE state. Upon running from the native
function, the current frame enters the ACTIVE state again.
CCALL
is an OSR point.
If a stack is rebound and the top frame stops on a CCALL
instruction
(because of OSR), the result values of CCALL
are the values passed normally,
or it receives the exception provided by the rebinding process.
For LLVM users: Mu is not designed to be compatible with C and functions defined in Mu IR does not use the native C ABI. This instruction is necessary to communicate with other parts of the system, especially operating systems since most operating systems are currently written in C and provide C interfaces.
For Java users: The
CCALL
instruction is not like JNI. JNI is a thick layer between the JVM and the native world, but theCCALL
intends to introduce minimal overhead. However, JEP191 proposed a similar approach as C#'s unsafe native function calls and it may be similar to Mu'sCCALL
instruction.For JikesRVM users: This instruction is similar to the "syscall" mechanism where the overhead is minimum and great freedom is provided to the user, but the Mu VM provides no protection of abusing. This is the preferred way to call native functions.
Example: This example uses the C function
write
to print"Hello world!\n"
:// The client loads libc and gets the address for write. // Then the client defines a constant which hard-codes the address. .typedef @AddrType = int<64> .const @write_address <@AddrType> = 0x0011223344556600 // hard-coded address // A compatible signature must be provided to call this native function. // Note: write's signature in C is: // ssize_t write(int fildes, const void *buf, size_t nbytes); // Assume x86_64 architecture. .typedef @c_ssize_t = int<64> .typedef @c_int = int<32> .typedef @c_void = void .typedef @c_ptrvoid = uptr<@c_void> .typedef @c_size_t = int<64> .funcsig @write_sig = (@c_int @c_ptrvoid @c_size_t) -> (@c_ssize_t) .typedef @write_ptr = ufuncptr<@write_sig> // Define necessary constants and types .const @FD = 1 // 1 is for stdout .typedef @DynByteArray = hybrid<@i8> // No fixed part. Just a @i8 array of unknown length. .typedef @irefi8 = iref<@i8> .typedef @ptri8 = uptr<@i8> .const @ARY_SZ <@i64> = 13 .const @CHAR_H <@i8> = 0x48 // 'H' .const @CHAR_e <@i8> = 0x65 // 'e' .const @CHAR_l <@i8> = 0x6c // 'l' .const @CHAR_o <@i8> = 0x6f // 'o' .const @CHAR_SP <@i8> = 0x20 // ' ' .const @CHAR_w <@i8> = 0x77 // 'w' .const @CHAR_r <@i8> = 0x72 // 'r' .const @CHAR_d <@i8> = 0x64 // 'd' .const @CHAR_EX <@i8> = 0x21 // '!' .const @CHAR_NL <@i8> = 0x0a // '\n' // A Mu program allocates an array (represented as a hybrid) %h = NEWHYBRID <@DynByteArray @i64> @ARY_SZ %hi = GETIREF <@DynByteArray> %h %v = GETVARPARTIREF <@DynByteArray> %hi // Then fill %v with string "Hello world\n\0" STORE <@i8> %v @CHAR_H // store 'H' %v1 = SHIFTIREF <@i8 @i64> %v %I64_1 STORE <@i8> %v1 @CHAR_e // store 'e' ... %v12 = SHIFTIREF <@i8 @i64> %v11 %I64_1 STORE <@i8> %v12 @CHAR_NL // store '\n' // Before calling the C function, we need to pin the object %bufptr = COMMINST @uvm.native.pin <@irefi8> %v // pins the underlying object of %v. %bufptr is a uptr<@i8> %bufptr_pv = PTRCAST <@ptri8 @c_ptrvoid> %bufptr // cast to the correct type // Cast the function to a ufuncptr type %callee = PTRCAST <@AddrType @write_ptr> @write_address // Then use the CCALL instruction to call the write function. %bytes_written = CCALL #DEFAULT <@write_ptr @write_sig> @callee (@FD %bufptr_pv @ARY_SZ) // Unpin the object after calling to C so that the garbage collector // can move the object again. COMMINST @uvm.native.unpin <@irefi8> %v
See Thread and Stack for more information of stack creation and the swap-stack operation.
NOTE: New stacks can be created by the @uvm.new_stack
common instruction or the new_stack
API
function.
- newStackClause ::=
PASS_VALUES
<
Ts>
(
vals)
- newStackClause ::=
THROW_EXC
exc
- Ts
- list of types: The type of vals.
- vals
- list of variables matching the types of Ts: The values to pass to the target stack.
- exc
- variable of type
ref
: The exception object to raise to the target stack.
The new stack clause determines how to bind a thread to the target stack. It
is used by both NEWTHREAD
and SWAPSTACK
, since both binds a thread (new
or existing) to a given stack.
PASS_VALUES
will pass values vals which has types Ts to the target stack.THROW_EXC
will raise the exception exc to the target stack.
NEWTHREAD
stack threadLocalClause opt newStackClause excClause
- threadLocalClause ::=
THREADLOCAL
(
threadLocal)
- newStackClause ::=
PASS_VALUES
<
Ts2>
(
vals)
- newStackClause ::=
THROW_EXC
exc
- stack
- variable of
stackref
: The stack to bind the new thread to. - threadLocal
- variable of
ref<T>
for any T: The initial thread-local object reference of the created thread. If omitted, it defaults toNULL
. - newStackClause
- new stack clause: Determines how to bind to the stack.
- excClause
- exception clause: To handle thread creation errors.
- result:
- Type is
threadref
: The newly created thread.
The NEWTHREAD
instruction creates a new thread which immediately binds to
stack, passing values or raising exceptions to it according to the
newStackClause.
The threadLocalClause determines the initial thread-local object reference of
the newly created thread. If it is omitted, the thread-local object reference
will be NULL
.
This instruction continues exceptionally if Mu cannot create the new thread. The exception parameter receives NULL.
Example:
%stack = COMMINST @uvm.new_stack <[@sig]> @func %thread = NEWTHREAD %stack PASS_VALUES <@T1 @T2> (%a1 %a2)Rare case: Create a thread to unwind a stack:
%stack = ... %thread = NEWTHREAD %stack THROW_EXC %some_exceptionCreate a new thread with a thread-local objref:
.typedef @my_threadlocal_t = struct<...> // an arbitrary type %stack = COMMINST @uvm.new_stack <[@sig]> @func %tlobj = NEW <@my_threadlocal_t> %thread = NEWTHREAD %stack THREADLOCAL(%tlobj) PASS_VALUES <...> (...)
SWAPSTACK
swappee curStackClause newStackClause excClause keepAliveClause
- curStackClause ::=
RET_WITH
<
Ts>
- curStackClause ::=
KILL_OLD
- newStackClause: See above.
- swappee
- variable of type
stackref
: The stack to swap to. - curStackClause
- current stack clause: Action to the current stack.
- Ts
- list of types: The result types of this instruction if this stack is bound to a thread again.
- newStackClause
- new stack clause: Action to the swappee stack.
- excClause
- exception clause: To handle exceptions thrown to this stack.
- keepAliveClause
- keep-alive clause: For on-stack replacement.
The SWAPSTACK
instruction unbinds the current thread with the current stack
and rebinds it to another stack swappee.
In the curStackClause:
- If the curStackClause is
RET_WITH
, then the current frame will enter the READY<Ts> state. Ts1 can be an empty list, in which case it does not expect values. - If the curStackClause is
KILL_OLD
, then the current stack is killed.
According to the newStackClause, values or an exception can be passed to the swappee stack.
See Thread and Stack for more details about the SWAP-STACK operation.
When a stack is rebound and the top frame pauses on a SWAPSTACK
instruction
it continues normally with the return value being the value received, or
continues exceptionally, catching the exception received.
The SWAPSTACK
instruction is an OSR point.
For LLVM users: There is no native facilities provided by the official LLVM, but Dolan, Muralidharan and Gregg proposed an extension to LLVM to support the SWAPSTACK operation. The SWAPSTACK interface of Mu is inspired by their works.
Example: The following example demonstrates a coroutine that yields 1, 2 and 3 and then raise an exception in the main coroutine to terminate the iteration:
.funcsig @sig1 = (@sref) -> () .const @I64_2 <@i64> = 2 .const @I64_3 <@i64> = 3 .typedef @StopIterationException = ... // This coroutine (a generator) yields 1, 2 and 3 sequentially and stop. // param %from: the stack of the main coroutine, i.e. the "caller" stack .funcdef @one_two_three VERSION %v1 <@sig1> { %entry(<@sref> %from): // Return to the main stack after receiving the initial // parameter %from. So the main stack can use a loop. SWAPSTACK %from RET_WITH <> PASS_VALUES <> () SWAPSTACK %from RET_WITH <> PASS_VALUES <@i64> (@I64_1) SWAPSTACK %from RET_WITH <> PASS_VALUES <@i64> (@I64_2) SWAPSTACK %from RET_WITH <> PASS_VALUES <@i64> (@I64_3) %exc = NEW <@StopIterationException> SWAPSTACK %from KILL_OLD THROW_EXC %exc } .funcsig @main_sig = () -> () .funcdef @main VERSION %v1 <@main_sig> { %entry(): // Get the reference to the current stack %cur_stack = COMMINST @uvm.current_stack // Create a new stack %coro = COMMINST @uvm.new_stack <[@sig1]> (@one_two_three) // Initialise its parameters SWAPSTACK %coro RET_WITH <> PASS_VALUES <@sref> (%cur_stack) // Enter the loop BRANCH %head %head(<@sref> %coro): // Go to the coroutine %cur_num = SWAPSTACK %coro RET_WITH <@i64> PASS_VALUES <> () EXC( %body(%coro %cur_num) %exit() ) %body(<@sref> %coro <@i64> %cur_num): // Do something with %cur_num CALL <...> @print_num (%cur_num) // Get the next number BRANCH %head(%coro) %exit() [%exc]: // %exc receives the StopIterationException COMMINST @thread_exit }
COMMINST
inst flagList opt typeList opt funcSigList
opt argList opt excClause keepAliveClause
- inst
- global name: The name of the common instruction.
- flagList opt
- Optional flag list: A list of flags. Can be omitted.
- typeList opt
- Optional type list: A list of type arguments. Can be omitted.
- funcSigList opt
- Optional function signature list: A list of function signature arguments. Can be omitted.
- argList opt
- Optional argument list: A list of value arguments. Can be omitted.
- excClause
- exception clause: To provide an alternative control flow destination.
- keepAliveClause
- keep-alive clause: For on-stack replacement.
- results:
- Determined by the respective common instructions.
The COMMINST
instruction calls a common instruction identified by inst in
the text form. A list of flags flagList, a list of type arguments typeList,
a list of function signature arguments funcSigList, a list of value arguments
argList, an optional exception clause excClause and a possibly empty
keep-alive clause keepAliveClause are provided for the common instruction. The
return value of this instruction is decided by the specific common instruction.
The semantics is defined by the specific common instruction.
The flagList, typeList, funcSigList and argList are considered empty when omitted in the text form.
A COMMINST
instruction must supply as many type arguments and value
arguments as the specific common instruction requires. A COMMINST
instruction must not have an exception clause unless the common instruction can
continue exceptionally. A COMMINST
instruction must have an empty keep-alive
clause unless the common instruction is an OSR point.
See common-insts.rest for a complete list of common instructions.
For LLVM users: Common instructions in Mu is the counterpart of intrinsic
functions of the LLVM. Common instructions are just like other Mu
instructions except they have the same instruction format and can be
extended without changing the Mu IR grammar. They are not functions and are
not called by the CALL
instructions which calls Mu functions.
Example:
.const @F_2 <@float> = 2.0f .const @D_2 <@double> = 2.0d .const @MAX_SINT <@i32> = 0x7fffffff // extra math functions (planned, to be extended) %v1 = COMMINST @uvm.math.sin <@float> (@F_1) %v2 = COMMINST @uvm.math.cos <@float> (@F_2) %v3 = COMMINST @uvm.math.tan <@double> (@D_1) %v4 = COMMINST @uvm.math.sqrt <@double> (@D_2) %v5 = COMMINST @uvm.math.add_ovf <@i32> (@MAX_SINT @I32_1) EXC(%bb1() %bb2()) %bb1(): ... %bb2(): // Handle signed integer overflow here. // extra stack/thread operations %s = COMMINST @uvm.new_stack <[@sig]> (@func) %t = NEWTHREAD %s THROW_EXC %some_exc // the proper thread exit COMMINST @uvm.thread_exit // no type args or value args