Skip to content
This repository has been archived by the owner on Aug 2, 2019. It is now read-only.

Latest commit

 

History

History
240 lines (174 loc) · 10.2 KB

portability.rest

File metadata and controls

240 lines (174 loc) · 10.2 KB

Portability

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

  1. There is a basic set of types and instructions that have common and defined behaviours and reasonably good performance on all platforms.
  2. 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.

Type System

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.

Constants

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.

Instructions

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.

Common Instructions

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.