As both a thin layer over the hardware and an abstraction over concurrency, JIT compiling and GC, Mu must strike a balance between portability and the ability to exploit platform-specific features. Thus Mu is designed in such a way that
- There is a basic set of types and instructions that have common and defined behaviours and reasonably good performance on all platforms.
- Mu also includes platform-specific instructions. These instructions are either defined by this Mu specification or extended by Mu implementations.
In this chapter, required features must be implemented by Mu implementation and optional features may or may not be implemented. However, if an optional feature is implemented, it must behave as specified.
NOTE: Although "behaving as specified", the implementation can still reject some inputs in a compliant way. For example, if an array type is too large, Mu still needs to accept the Mu IR that contains such a type, but may always refuse to allocate such a type in the memory.
The platform-independent part of the native interface is a required component of the Mu spec, but the platform-dependent parts are not. There will be things which are "required but has implementation-defined behaviours". In this case, Mu must not reject IR programs that contain such constructs, but each implementation may do different things.
The int
type of lengths 1, 8, 16, 32, and 64 are required. int
of 6 and
52 bits are required if Mu also implements the tagref64
type. Other lengths
are optional.
Both float
and double
are required.
The vector types vector<int<32> 4>
, vector<float 4>
and vector<double
2>
are required. Other vector types are optional.
NOTE: Even though required to be accepted by Mu, they are not required to be implemented using hardware-provided vector instructions or vector registers of the exact same length. They can be implemented totally with scalar operations and general-purpose registers or memory, or implemented using different hardware vector sizes, larger or smaller.
Reference types ref<T>
, iref<T>
and weakref<T>
are required if T
is implemented. Otherwise optional.
A struct type struct<...>
are required if it has at most 256 fields and all
of its field types are implemented. Otherwise optional.
An array type array<T n>
is required if T is implemented and n is less than
2^64. Otherwise optional.
NOTE: This implies Mu must accept array types of up to 2^64-1 elements. However, arrays must be in the memory. Whether such an array can be successfully allocated is a different story.
A hybrid type hybrid<Fs V>
is required if all fields in Fs
and V
are
implemented.
The void type void
is required.
A function type funcref<Sig>
is required if Sig
is implemented.
The opaque types threadref
and stackref
are both required.
The tagged reference type tagref64
is optional.
A function signature R (P0 P1 ...)
is required if all of its parameter types
and its return type are implemented and there are at most 256 parameters.
Otherwise optional.
Pointer types uptr<T>
and ufuncptr<sig>
are required for required and
native-safe types T
and signatures sig
. Both are represented as
integers, but their lengths are implementation-defined.
Integer constants of type int<n>
is required for all implemented n.
Float and double constants are required.
A struct constant is required if constants for all of its fields are implemented.
All NULL constants are required.
Pointer constants are required, but the implementation defines the length of them.
All integer binary operations and comparisons are required for int
of length
8, 16, 32, 64 and required integer vector types, and optional for other integer
lengths or integer vector types. All floating-point binary operations and
comparisons are required for all floating point types and required floating
point vector types, and optional for other floating point vector types.
In the event of signed and unsigned integer overflow in binary operations, the result is truncated to the length of the operand type.
Divide-by-zero caused by UDIV
and SDIV
results in exceptional control
flows. The result of signed overflow by SDIV
is the left-hand-side.
NOTE: -0x80000000 / -1 == -0x80000000 for signed 32-bit int.
For shifting instructions SHL
, LSHR
and ASHR
for integer type
int<n>
, only the lowest m
bits of the right-hand-side are used, where
m
is the smallest integer that 2^m
>= n
.
Conversions instructions are required between any two implemented types that can
be converted. Specifically, given two types T1 and T2 and a conversion operation
CONVOP, if both T1 and T2 are implemented and they satisfied the requirement of
the T1
and T2
parameters of CONVOP (see instruction-set.rest), then the
CONVOP operation converting from T1 to T2 is required.
Binary floating point operations round to nearest and round ties to even. Conversions involving floating point numbers round towards zero except converting from floating point to integer, in which case, round towards zero and the range is clamped to that of the result type and NaN is converted to 0. Binary operations, comparisons and conversions involving floating point numbers never raise exceptions or hardware traps. [JVM behaviour]
Switch is required for the operand type of int
of length 8, 16, 32 and 64.
Otherwise optional.
Calling a function whose value is NULL
is undefined behaviour.
Throwing exceptions across native frames has implementation-defined behaviour.
Stack overflow when calling a function results in taking the exceptional control
flow and the exceptional parameter instruction receives NULL
.
EXTRACTELEMENT
and INSERTELEMENT
is required for all implemented vector
types and integer types. SHUFFLEELEMENT
is required if the source vector
type, the mask vector type and the result vector type are all implemented.
All memory allocation instructions NEW
, NEWHYBRID
, ALLOCA
and
ALLOCAHYBRID
are allowed to result in error, in which case the exceptional
control flow is taken.
NOTE: This is for out-of-memory error and other errors.
The GETELEMIREF
and SHIFTIREF
instructions accept any integer type as
the index or offset type. The index and the offset are treated as signed. When
these two instructions result in a reference beyond the size of the actual array
in the memory, they have undefined behaviours.
GETVARPARTIREF
has undefined behaviour if the hybrid has zero elements in
its variable part.
All memory addressing instructions GETIREF
, GETFIELDIREF
,
GETELEMIREF
, SHIFTIREF
and GETVARPARTIREF
give
undefined behaviour when applied to NULL
references. But when applied to
pointers, these instructions calculates the result by calculating the offset
according to the memory layout, which is implementation-defined.
All memory access instructions LOAD
, STORE
, CMPXCHG
and
ATOMICRMW
that access the NULL
references take the exceptional control
flow. If they access an invalid memory location (this include the case when the
stack frame that contains a stack cell created by ALLOCA
is popped and a
reference to it becomes a dangling reference), then they have undefined
behaviours.
Accessing a memory location which represents a type different from the type expected by the instruction gives undefined behaviour.
Accessing the memory via a pointer behaves as if accessing via an iref
if
the byte region represented by the pointer overlaps with the mapped (pinned)
memory region of the Mu memory location. It behaves as if updating the memory
byte-by-byte (not atomically) when all bytes in the byte region pointed by the
pointer are part of some Mu memory locations. Otherwise such memory access has
implementation-defined behaviours.
The following types are required for both non-atomic and atomic LOAD
and
STORE
for all implemented T
and Sig
: int<8>
, int<16>
,
int<32>
, int<64>
, float
, double
, ref<T>
, iref<T>
,
weakref<T>
, funcref<Sig>
, threadref
, stackref
, uptr<T>
and
ufuncptr<Sig>
.
The following types are required for non-atomic LOAD
and STORE
:
vector<int<32> 4>
, vector<float 4>
and vector<double 2>
.
int<32>
, int<64>
, ref
, iref
, weakref
, funcref
,
threadref
, stackref
, uptr
and ufuncptr
are required for
CMPXCHG
and the XCHG
operation of the ATOMICRMW
instruction.
int<32>
and int<64>
are required for all ATOMICRMW
operations.
If tagref64
is implemented, it is required for both atomic and non-atomic
LOAD
and STORE
, and the XCHG
operation of the ATOMICRMW
instruction.
Other types are optional for CMPXCHG
and any subset of ATOMICRMW
operations.
One atomic Mu instruction does not necessarily correspond to exactly one
machine instruction. So some atomic read-modify-write operations can be
implemented using CMPXCHG
or load-link store-conditional constructs.
CCALL
is required, but the behaviour is implementation-defined. The
available calling conventions are implementation-defined.
@uvm.new_stack
and NEWTHREAD
is allowed to result in error, in which
case the exceptional control flow is taken.
The availability of COMMINST
is specified in the next section.
In any cases when an error occurs and the control flow is expected to transfer to the exceptional control flow, but the exception clause is not supplied, then it gives undefined behaviours.
All instructions whose availability is not explicitly specified above are required for all types and signatures that are implemented and suitable.
Required only when tagref64
is implemented:
- @uvm.tr64.is_fp
- @uvm.tr64.is_int
- @uvm.tr64.is_ref
- @uvm.tr64.to_fp
- @uvm.tr64.to_int
- @uvm.tr64.to_ref
- @uvm.tr64.to_tag
- @uvm.tr64.from_fp
- @uvm.tr64.from_int
- @uvm.tr64.from_ref
All other common instructions are always required.
The Mu implementation may add common instructions.