Continued from Target Languages Concordance part I
In addition to the above, instructions or intrinsics for each of the following is provided by two platforms in this study:
Arithmetic:
Memory access:
Stack ops:
Atomics and Sync:
Control flow:
Data structures:
Misc:
LuaJIT?2 and CIL have instructions to load various higher-level data structures such as strings.
JVM and LuaJIT?2 use constant tables/constant pools. (i think that LuaJIT?2's constant load instructions can be used for both immediate constants and constant tables, depending on if their argumens is negative or not). Note: CIL does not have a runtime-accessible constant table; it has a constant table for use at compile-time but "Compilers inspect this information, at compile time, when importing metadata, but the value of the constant itself, if used, becomes embedded into the CIL stream the compiler emits. There are no CIL instructions to access the Constant table at runtime" ([1] section II.22.9, page 216).
JVM provides instructions to push constants of null, or 0,1 sometimes 2, or -1...5, depending on type. CIL provides instructions to load -1..8 to i32 as well as null.
RISC-V has an instruction to load PC-relative constant, and ARM has PC-relative addition.
Andreas Olofsson of Adapteva said on a blog that he is "not convinced that ((AUIPC)) is essential" [2], however a commenter, Chris, from the RISC-V project explained that it was useful [3].
LLVM, CIL, ARM have 32-bit signed integer addition and subtraction with overflow. Only LLVM, CIL have multiplication with overflow, as well as unsigned and 64-bit variants of addition, subtraction, multiplication with overflow.
LLVM offers 'constrained' floating point operations, which means that the rounding and exception modes are respected when those instructions are used. RISC-V provides a similar facility through its rounding mode and exception mode special registers; except that RISC-V does not provide a floating-point mod/remainder instruction.
integer addition with overflow (64-bit and unsigned variants):
integer subtraction with overflow (64-bit and unsigned variants):
integer multiplication, lower bits, with overflow:
floating point constrained operations (similar to corresponding RISC-V operations, not shown):
None.
WASM and ARM provide right rotate.
Rotate right:
None.
WASM and LLVM provide inequality (integer and floating point), integer greater-than-or-equal-to, less-than-or-equal-to, floating point greater-than-or-equal-to (they also provide that for integer, but so do other platforms, so those are listed above rather than here).
Note that some other platforms provide these operations, but only as branches rather than vanilla compares.
Integer inequality:
Integer greater-than-or-equal-to, less-than-or-equal-to:
Floating point:
CLZ, CTZ, POPCNT are provided by WASM and LLVM (but only as LLVM intrinsics).
LLVM and ARM provide various byteswaps.
byte swap:
WASM and LLVM provide abs(-olute-value), although LLVM abs is an intrinsic.
RISC-V and LLVM provide rounding in the form of conversion operations and rounding modes (LLVM requires the 'constrained' intrinsics to use these). WASM and LLVM provide rounding in the form of ceil, floor, trunc, nearest instructions (but WASM does not provide rounding mode control for other instructions; see [4] and [5]).
RISC-V and LLVM support IEEE exception flags and rounding modes (but LLVM only supports these with 'constrained' intrinsics. Because two platforms support this functionality it is included here, but note that some of the LLVM 'constrained' intrinsics are listed above, in the section 'Add, subtract, multiply, divide'.
Andreas Olofsson of Adapteva noted in a blog post that the Epiphany ISA does not include operations like RISC-V's exception flags, coercion operations, and rounding modes, because they were not needed for Epiphany's use case [6].
RISC-V provides an FCLASS instruction to report the attributes of a floating-point number. Andreas Olofsson of Adapteva said in a blog post that he felt that this instruction was "not essential" [7]. CIL provides ckfinite to check if a floating-point number is finite.
Fused multiply-add is provided by RISC-V and LLVM (but only as an LLVM intrinsic).
pow is provided by LLVM as an intrinsic and by LuaJIT?2.
some notes on the default rounding mode of other platforms: " WebAssembly? uses “non-stop” mode, and floating point exceptions are not otherwise observable. In particular, neither alternate floating point exception handling attributes nor the non-computational operators on status flags are supported. There is no observable difference between quiet and signalling NaN?. However, positive infinity, negative infinity, and NaN? are still always produced as result values to indicate overflow, invalid, and divide-by-zero conditions, as specified by IEEE 754-2008.
WebAssembly? uses the round-to-nearest ties-to-even rounding attribute, except where otherwise specified. Non-default directed rounding attributes are not supported. " -- [8]
" By default, LLVM optimization passes assume that the rounding mode is round-to-nearest and that floating-point exceptions will not be monitored. Constrained FP intrinsics are used to support non-default rounding modes and accurately preserve exception behavior " -- [9]
absolute value:
floor, ceiling, trunc:
nearest int or rounding-mode determined rounding:
FRM (3-bit floating point rounding mode):
FFLAGS (5-bit Accrued Exception Flags):
FCSR (32-bit floating point status and control register):
LLVM and ARM provide instructions to convert from 8-bit or 16-bit quantities to larger ones.
sign extend:
zero extend:
WASM and LuaJIT?2 support loads and stores to/from global variables. JVM and CIL provide short instructions to load/store the first 4 variables.
locals:
globals:
JVM, CIL provide dup.
dup:
RISC-V and LLVM provide the AMOs (Atomic Memory Operations): SWAP, ADD, AND, OR, XOR, MIN, MAX, MINU, MAXU.
RISC-V provides load-release/store-conditional. LLVM provides compare-and-swap. These are different operations but in some sense they are similar in that each can be used as a primitive upon which to build synchronization/consensus/atomicity.
C/C++ are not included in this concordance, but it's worth noting that (as of 2019) C/C++ atomics [10] [11] offer the following atomics (this is a rough summary): (compare and) exchange, load, store, test-and-set flag, clear flag, add, sub, or, xor, and, fence.
Non-Cortex ARM instructions are not included in this concordance, but ARMv8.1-A provides the AMOs: SWP, CAS, LDADD, LDCLR, LDEOR, LDSET, LDSMAX, LDSMIN, LDUMAX, LDUMIN ([12] slide 14) (and in [13] there is also talk of MemAtomicOp?_BIC (result = data AND NOT(value)) and MemAtomicOp?_ORR (result = data OR value), so perhaps those were already in there in some other update?).
Note that the intersection of the C/C++ AMOs with ARMv8.1-A AMOs with the set of AMOs provided by RISC-V and LLVM is (if you assume that ARM's BIC corresponds to an AND): swap, add, and, or, xor.
RISC-V Geneology lists only AMOSWAP and AMOADD as appearing "in at least three" of the instruction set architectures under consideration in that paper.
swap:
add:
xor:
and:
or:
min:
unsigned min:
max:
unsigned max:
RISC-V and ARM (all of the hardware processor ISAs included in this study) both provide unconditional jump instructions which also place the source address in a 'link register'. In fact, all of RISC-V unconditional jumps do this. JVM used to provide a similar instruction with JSR, but this has been effectively deprecated.
RISC-V and ARM (all of the hardware processor ISAs included in this study) both provide unconditional indirect branch instructions to an arbitrary location in code. The other platforms do provide indirect jumps, but either require an enumerated set of all potential destinations, or provide indirect jumps for higher-level constructs only (such as LuaJIT?2, which provides the CALL instruction to call a function).
ARM and JVM provide <0, >=0.
Note: Instead of many conditional branches, WASM and LLVM instead provide separate comparison ops, and boolean conditional branch (listed above rather than here, because boolean conditional branch is instead grouped with branch-if-not-zero, which is offered by more than two platforms).
unary compare <0:
unary compare >=0:
WASM and LLVM provide SELECT. Note that these are the same two platforms that separate compares and boolean-conditional-branch.
LuaJIT?2 and CIL provides tail calls.
WASM and Lua provide structured control flow loops.
JVM and CIL provide invoke instructions for object-oriented calling.
tailcall:
Loops:
OOP calls:
None.
JVM, CIL provide OOP data structures.
LuaJIT?2 and CIL provide strings (but not many operations specifically for them, as far as i can tell; operations not shown).
creation, memory:
types:
accessors:
Cycle counters are provided by RISC-V and LLVM (as LLVM intrinsics).
RISC-V and ARM provide Control-and-Status-Registers. Note that these are all of the hardware processor ISAs in our dataset.
Memory operations such as memcpy are provided by LLVM intrinsics and CIL.
cycle counters:
special registers:
memory:
Unlike the above sections for classes of instructions supported by n platforms, where n>1, here we won't bother to introduce/provide a list at the beginning of the section of the instructions classes in this section.
RISC-V alone provides LUI and AUIPC.
Andreas Olofsson of Adapteva said on a blog that he is "not convinced that ((AUIPC)) is essential" [14], however a commenter, Chris, from the RISC-V project explained that it was useful [15].
Load upper bits:
Load PC-relative constant:
RISC-V alone also provides integer multiplication instructions returning the high-order bits. Andreas Olofsson of Adapteva said on a blog that these operations had a "high expense/benefit ratio" [16].
LLVM intrinsics alone has saturated addition and subtraction (in both signed and unsigned forms); fixed point multiplication (in both signed and unsigned and signed saturated forms).
ARM alone has PC-relative addition.
JVM alone has increment (which operates directly on variables, not on the stack).
Increment:
PC-relative add:
Multiply, undefined upon overflow:
Multiply, upper bits:
WASM alone provides left rotate.
Rotate left:
ARM alone also provides and bit-clear (bitwise-AND (x, bitwise-NOT (y)).
WASM alone provides equals-zero compares.
LLVM alone breaks out each floating point comparison into 'ordered' and 'unordered', which provide two options for what to do with NaNs?.
JVM alone provides trinary comparisons for floats, doubles, and longs.
Equals zero:
Trinary compare:
Trinary compare:
Other:
LLVM alone provides bitreverse, and funnel shifts.
Other bit manipulation:
RISC-V alone provides copy-negated-sign (FSGNJN) and XOR-sign, although these could be thought of as binary generalizations replacements for the unary sign operations negate and abs, which are provided by other systems.
Andreas Olofsson of Adapteva said in a blog post that he felt that RISC-V's floating point sign instructions (FSGNJ, FSGNJN, FSGNJX) were "not essential" [17].
RISC-V alone provides three fused multiply-add variants.
In a blog post, Andreas Olofsson of Adapteva questioned whether the FNMSUB fused multiply-add instruction variant provided by RISC-V is needed [18].
LLVM alone provides, as intrinsics only, certain other variants of min, max, and round, canonicalize, fma, and the standard C library math functions sin, cos, pow, exp, exp2, log, log10, log2.
other binary sign ops:
CIL alone provides 'native' types (native int ('i'), native float ('r')).
CIL alone provides conversion variants with overflow detection.
LLVM alone provides intrinsics to convert to and from 16-bit floating point.
LLVM alone provides conversions between pointers and ints; however such conversions are unneeded in some other systems, where pointers are untyped or the same type as ints.
LLVM alone provides 'addrspacecast' to convert pointers between different 'address spaces'.
JVM alone provides conversions to chars, but not vice versa, as far as i can tell.
ARM alone provides load multiple and store multiple.
CIL alone provides load/store 'native' integers (type 'i'), and load/store opaque references (type 'O').
Load/store multiple:
Load/store native:
Reference (pointer) loads and stores:
CIL alone provides loads of the addresses of local variables. WASM provides a function TEE_LOCAL which sets a local but then also returns its argument. LuaJIT?2 also provides loads and stores from/to upvalues.
ARM alone provides push, pop. JVM alone provides swap.
push, pop:
swap:
LLVM alone provides some rarely seen AMOs: sub nand fadd fsub.
ARM alone also provides branch-if-overflow, branch-if-not-overflow, unsigned >, unsigned <=.
JVM alone also provides >0, <=0.
LuaJIT?2 alone provides equality/inequality compares against strings.
LuaJIT?2 alone provides special instructions for iterators, and for closures.
iterators:
closures:
WASM alone provides blocks and if/else.
LLVM alone provides a shufflevector operation.
CIL alone provides address-of-array-element and address-of-object-field accessors.
CIL alone provides polymorphic boxing and unboxing instructions.
LuaJIT?2 alone provides a cat (concatenate) operation (i think that LuaJIT?2 len is on strings and tables. I don't know if cat is only for strings or if it applies to tables also).
LLVM alone provides vector reduction ops, and masked vector ops.
array misc:
array accessors:
OOP accessors:
misc:
vector reduction:
masked vectors (LLVM Intrinsics):
LLVM alone provides phi, and various other intrinsics.
ARM alone provides interrupt handling, and 'event' hints for power saving.
JVM alone provides a compression instruction instruction (wide), and implementation-dependent instructions.
LuaJIT?2 alone provides function header instructions.
CIL provides various misc. prefixes such as readonly, no.rangecheck.
misc misc:
WASM, JVM, CIL have instructions to directly load i32, i64, f32, f64 constants (but not unsigned?). RISC-V has various instructions that can be specialized to directly load i64 (or i32, if the chip is RV32I instead of RV64I); everything else must be synthesized/coerced.
Lua has f64 only, but LuaJIT?2 does have an instruction to load 16-bit immediate constants. LuaJIT?2 uses constant tables. LuaJIT?2 and CIL have instructions to load various higher-level data structures such as strings.
JVM uses a constant pool rather than immediate constants. JVM does provide immediate constants of 8- and 16-bits (extended to ints of 32 bits) via bipush and sipush.
JVM provides instructions to push constants of null, or 0,1 sometimes 2, or -1...5, depending on type. CIL provides instructions to load -1..8 to i32 as well as null.
RISC-V alone provides LUI.
LLVM doesn't need constant loads because constants can be assigned to a variable in the LLVM IR without an instruction.
Load upper bits:
Moves are provided by RISC-V, ARM, LuaJIT?2. RISC-V provides MOVs as pseudoinstructions. WASM, JVM, and CIL have a stack and LLVM has SSA assignment instead of MOVs.
All of RISC-V, WASM, LLVM, ARM, JVM, CIL have 32-bit signed ADD and SUB. LuaJIT?2 has 64-bit signed ADD and SUB.
RISC-V, WASM, LLVM, JVM, CIL have all 4 combinations of integer, float, 64-bit, 32-bit variants of ADD and SUB.
All of RISC-V, WASM, LLVM, ARM, JVM, CIL provide an integer multiplication which returns the lower half of the resulting bits (the "low order bits"; equivalently, the result mod 2^bitwidth).
RISC-V alone also provides integer multiplication instructions returning the high-order bits. Andreas Olofsson of Adapteva said on a blog that these operations had a "high expense/benefit ratio" [19].
RISC-V, WASM, LLVM, JVM, CIL have 64-bit and 32-bit signed integer variants of DIV and REM, and 64-bit and 32-bit float variants of MUL and DIV. LuaJIT?2 provides only 64-bit variants of MUL, DIV, MOD.
RISC-V, WASM, LLVM, CIL have all 4 combinations of 64-bit, 32-bit, signed, unsigned integer variants, of DIV and REM, and 64-bit and 32-bit float variants of MUL and DIV.
Andreas Olofsson of Adapteva noted in a blog post that RISC-V's FDIV (floating point division) instruction is "expensive" and that it was a "tough call" whether to include such an instruction in his Epiphany ISA [20]. In the same blog post, he noted that Epiphany did not have integer division or remainder because they didn't fit Epiphany's intended use cases.
RISC-V floating point operations are only included in the floating point extensions, not the base integer instruction set. RISC-V integer multiplication, division, and remainder are only included in the M extension, not the base integer instruction set.
LLVM, JVM, LuaJIT?2, CIL have floating point remainder/mod.
LLVM intrinsics alone has saturated addition and subtraction (in both signed and unsigned forms); fixed point multiplication (in both signed and unsigned and signed saturated forms).
Integer addition and subtraction with overflow or carry, signed 32-bit, is provided by LLVM intrinsics, ARM, CIL. LLVM, CIL also have multiplication with overflow, as well as unsigned and 64-bit variants of addition, subtraction, multiplication with overflow.
LLVM offers 'constrained' floating point operations, which means that the rounding and exception modes are respected when those instructions are used.
JVM and CIL have integer negation and ARM has reverse subtraction.
JVM alone has increment (which operates directly on variables, not on the stack).
Used for both integers and floats
Integer division truncates towards zero.
Integer division or negation throws DivideByZeroException? if division by zero is attempted. Integer division or negation throws System.ArithmeticException? if the result cannot be represented in the result type, for example, for (the smallest representable integer value) / -1.
Integer negation is twos-complement negation. Neg(the most negative number) = the most negative number (note that in twos-complement, the true negation of the most negative number cannot be represented).
For integer remainder, " result = value1 rem value2 satisfies the following conditions:
| result | <= | value2 |
Floating-point division is per IEC 60559:1989. Division of a finite number by 0 produces the correctly signed infinite value and 0 / 0 = NaN?, infinity / infinity = NaN?, anything / infinity = 0.
Negation of a floating point number cannot overflow. neg(NaN?) = NaN?.
Floating-point overflow returns +inf or -inf. For floating-point types, 0 * infinity = NaN?.
For floating-point remainder,
"rem is defined similarly as for integer operands, except that, if value2 is zero or value1 is infinity, result is NaN?. If value2 is infinity, result is value1. This definition is different from the one for floating-point remainder in the IEC 60559:1989 Standard. That Standard specifies that value1 div value2 is the nearest integer instead of truncating towards zero. System.Math.IEEERemainder(see Partition IV) provides the IEC 60559:1989 behavior.
...
Example:
For the various floating-point values of 10.0 and 6.0, rem gives the same values; System.Math.IEEERemainder, however, gives the following values.
Increment:
Subtract:
Negation
Reverse subtract:
Multiply, lower bits:
Multiply, undefined upon overflow:
Multiply, upper bits:
Div:
Rem:
Add:
Sub:
Mul:
Div:
Remainder/mod:
All of RISC-V, WASM, LLVM, ARM, CIL provide 32-bit left shift, logical/unsigned right shift, and arithmetic/signed right shift. RISC-V, WASM, LLVM, CIL also provide 64-bit variants.
WASM and ARM provide 32-bit right rotate. WASM alone provides left rotates (and 64-bit variant of right rotate).
Shift left:
Shift right logical/shift right unsigned:
Shift right arithmetic/shift right signed:
Rotate left:
Rotate right:
All of RISC-V, WASM, LLVM, ARM, JVM, CIL provide AND, OR, and XOR. RISC-V provides one instruction which is 32- or 64- bits depending on the register size, where WASM and JVM provide separate instructions for each size. ARM only provides 32-bit.
ARM, CIL provide bitwise NOT.
ARM alone also provides and bit-clear (bitwise-AND (x, bitwise-NOT (y)).
LuaJIT?2 provides boolean NOT.
Andreas Olofsson of Adapteva said on a blog that he felt that the immediate variants provided by RISC-V (ANDI, ORI, XORI) could have been left out (and were left out in his architecture, Epiphany) [23].
AND:
OR:
XOR:
NOT:
misc:
In this section we are only talking about compares which produce a result value, not branch, which is treated in the control flow section.
RISC-V, WASM, LLVM provide < (less-than) in integer (signed and unsigned) and floating point. All of RISC-V and WASM and LLVM provide floating point == (equality), < (less-than) and <= (less-than-or-equal-to) in both 32- and 64-bit. RISC-V and LLVM provide one instruction which is 32- or 64- bits depending on the register/value size, whereas WASM provides separate instructions for each size.
WASM and LLVM also provide integer <=, >, >= (less-than-or-equal-to, greater-than, and greater-than-or-equal-to) in all of 32- and 64-bit and unsigned and signed. WASM and LLVM also provide integer ==, != (equality, inequality), and floating point !=, >, >= (inequality, greater-than, greater-than-or-equal-to), all in both 32- and 64-bit, although i believe these would be trivial to synthesize on RISC-V. WASM also provides ==0 (equality with zero), which would also be trivial to synthesize on RISC-V or LLVM.
CIL provides ==, < > in both integer and floating point, both 32- and 64-bit, and < > in both signed and unsigned.
WASM alone provides equals-zero compares.
LLVM also breaks out each floating point comparison into 'ordered' and 'unordered', which provide two options for what to do with NaNs?.
JVM alone provides trinary comparisons for floats, doubles, and longs. JVM provides compare-and-branch instructions for integers and pointers rather than separate compare instructions.
CIL also has separate pointer types and provides various comparisons over pointers through the same polymorphic compare instructions used for other types.
Andreas Olofsson of Adapteva said on a blog that he felt that the SLT* instructions provided by RISC-V could have been left out (and were left out in his architecture, Epiphany) [24]