This proposal adds SIMD types and operations to ECMAScript. The proposal adds new primitive types Float32x4, etc, together with wrappers and a definition of their behavior in the language. The current proposal should form a full first draft with all functions and types included.
One problem that this spec aims to solve is to define equality for SIMD values. Before the spec was written, prior implementations used object identity-based equality. However, maintaining object identity puts a big burden on compilers to maintain this identity through operations, where they would rather be able to duplicate and de-duplicate SIMD values arbitrarily based on algebraic identities. By making SIMD values into primitive types with structural equality, compilers are given more freedom.
Ideally, SIMD values will fit into a larger value types proposal. Such a proposal would be a bit more involved, but good work has already been done in that direction. This document describes SIMD without a larger value type system, but it aims to be consistent with how value types might work, and once value types are described in more detail, it will be great to refactor this text by just explaining SIMD in terms of value types. On the other hand, this proposal gives a vehicle to work out some of the issues in value types and can be used as a guide for future value type designs.
This document is organized in terms of where changes would be made to the ES2015 spec. Although ecmarkup generates numbering at the beginning of headers, these won't correspond to the numbering within the existing ECMAScript spec, so I've included a matching numbering in parentheses afterwards, referring to the ES2015 spec.
In this text, SIMD is used to refer to the various SIMD types: Float32x4, Int32x4, Int16x8Int8x16, Uint32x4, Uint16x8Uint8x16, Bool32x4, Bool16x8 and Bool8x16. Similarly to Number, SIMD is used to refer to both the type and the wrapper constructor object. This looks a bit confusing, but it provides the most regularity, as an aim of this specification is to make SIMD types primitives that operate analogously to the existing primitives, rather than a new, exotic sort of thing. To reduce ambiguity, the wrapper constructor is usually referred to as SIMDConstructor, and the type is referred to as SIMDType. SIMD types are associated with a descriptor spec object, called SIMDDescriptor.
Please file any issues here! The authoritative copy of this file is in in the simd.js repo; to propose changes to this spec, please send pull requests against that repository. Daniel Ehrenberg (littledan) is responsible for watching for changes to that file, generating the output with ecmarkup and pushing it to the official rendered location.
Because this document is in spec order, rather than written for direct readability, the logical starting point is actually halfway down.
v0.1: Initial proposal based on SIMD values held in Data Blocks
v0.2:
SIMD values are explained as Lists of Numbers, and serialized/deserialized only on loads and stores to TypedArrays, and casts.
SIMD values point to a type descriptor, not to the wrapper constructor, which makes cross-realm access more straightforward.
Add all SIMD types, more SIMD operations and all DataView operations.
v0.3: Add more operations, fix some errors and clarify wording.
v0.4: Fix various bugs, mostly reported by rwaldron.
v0.4.1: Refactor definitions for integer types to reduce duplication.
v0.5: Logical and bitwise operations; Int64x2.
v0.5.1: Logical casts between types, like SIMD.float32x4.fromInt32x4; Less exact definition for reciprocalApproximation, reciprocalSqrtApproximation
v0.5.2: Saturating arithmetic; fix mul mis-spec.
v0.5.3: Remove mention of signalling NaN (matches ES6).
v0.5.4: Better formatting for modifications of existing algorithms.
v0.5.5: toLocaleString, shifts, saturating functions only on int16 and smaller, sumOfAbsoluteDifferences. This is the first version to include all functions.
v0.5.6: Fix a few typos, added notes, removed DataView methods, store returns value.
v0.6: Remove Int64x2 and add boolean vectors.
v0.6.1: Relax behavior on subnormals, float to int conversion throws on out of bounds, shift does not mask.
v0.7: Remove Float64x2, Bool64x2, {load,store}[123], selectBits (could be in phase 2); add unsigned operations and sum of absolute differences operations; minor fix in 'shift' definition; clarify what the SIMD object is.
v0.7.1: Add back in {load,store}[123], make ToString() homoiconic.
v0.7.2: Fix shiftRightArithmetic, add @@toPrimitive, do implicit coercion for shift bits.
v0.7.3: Coerce lane arguments, no bitcast on bool, fix typo in .check() and .valueOf(), no constructing wrappers, fix denormal behavior to be deterministic and match hardware, fix shuffle definition, define arithmetic operations only on numeric types, fix up ES2015 links.
v0.8: Separate signed and unsigned integer types
v0.8.1: Minor language fixes and cleanups from review; no behavior changes
family of types which each consist of a set of SIMD vector values.
Note
For example, the Float32x4 type consists of the set of vectors of 4 32-bit floats.
1.5SIMD value
Member of a particular SIMD type. These values are represented as described in the SIMD section. SIMD values are primitives, and they are represented as records with two fields.
1.6SIMD object
For a particular SIMD type, a member of the Object type which has a [[SIMDData]] internal slot.
1.7SIMD type descriptor
A specification-internal record describing the behavior and properties of a SIMD type, described in the %SIMD% section. The meta-variable SIMDDescriptor ranges over type descriptors.
The initial value of the prototype data property of %SIMD.Bool8x16%
2.2SIMD types
A SIMD value is a homogeneous vector of Numbers or Booleans, possibly from a restricted range. Each SIMD value is represented as a record with the following immutable fields:
[[SIMDTypeDescriptor]], which refers to the type descriptor for the type.
[[SIMDElements]], which is a List of values representing the SIMD contents.
Note 1
The [[SIMDTypeDescriptor]] field effectively determines its type. The SIMD type descriptors are defined in a table.
Note 2
A list which is in the [[SIMDElements]] of a SIMD value field is never modified.
Note 3
[[SIMDElements]] is a list of Number values for integer and floating point SIMD types, and Boolean values for boolean SIMD types.
SIMD type descriptors are records with the following fields:
[[VectorLength]]: The number of elements present in a SIMD value of the type
[[ElementSize]]: Size in bytes of each element
[[Cast]]: An internal algorithm for down-casting a Number to the precision representable in the SIMD type
[[SerializeElement]]: An internal algorithm for writing a Number as [[ElementSize]] bytes
[[DeserializeElement]]: An internal algorithm for converting [[ElementSize]] bytes into a Number
[[ElementMax]]: The maximum value included in the range representable by the element type
[[ElementMin]]: The minimum value included in the range representable by the element type
Each SIMD type descriptor is listed in this table. All SIMD types are either integer SIMD types, boolean SIMD types or or floating point SIMD types. Certain operations are defined on either integer, boolean or floating point types, and integer types are be signed or unsigned, and this is noted in the table.
The [[ElementMax]], [[ElementMin]], [[ElementSize]], [[SerializeElement]] and [[DeserializeElement]] fields are optional, and defined only on certain SIMD types. Operations which use these fields have been defined only on the types which have them.
Note 4
Many SIMD type descriptors have a [[Cast]] algorithm which outputs a Number. However, this Number is often in a restricted range, and implementations may use a different representation internally.
2.2.1Float32x4
Float32x4 is a SIMD type representing four 32-bit floating point values. Float32x4 values can be created using the [[Call]] operation on the SIMD.Float32x4 object. Its behavior as a SIMD type is defined by the Float32x4 SIMD type descriptor.
2.2.2Int32x4
Int32x4 is a SIMD type representing four 32-bit signed integer values. Int32x4 values can be created using the [[Call]] operation on the SIMD.Int32x4 object. Its behavior as a SIMD type is defined by the Int32x4 SIMD type descriptor.
2.2.3Int16x8
Int16x8 is a SIMD type representing eight 16-bit signed integer values. Int16x8 values can be created using the [[Call]] operation on the SIMD.Int16x8 object. Its behavior as a SIMD type is defined by the Int16x8 SIMD type descriptor.
2.2.4Int8x16
Int8x16 is a SIMD type representing sixteen 8-bit signed integer values. Int8x16 values can be created using the [[Call]] operation on the SIMD.Int8x16 object. Its behavior as a SIMD type is defined by the Int8x16 SIMD type descriptor.
2.2.5Uint32x4
Uint32x4 is a SIMD type representing four 32-bit unsigned integer values. Uint32x4 values can be created using the [[Call]] operation on the SIMD.Uint32x4 object. Its behavior as a SIMD type is defined by the Uint32x4 SIMD type descriptor.
2.2.6Uint16x8
Uint16x8 is a SIMD type representing eight 16-bit unsigned integer values. Uint16x8 values can be created using the [[Call]] operation on the SIMD.Uint16x8 object. Its behavior as a SIMD type is defined by the Uint16x8 SIMD type descriptor.
2.2.7Uint8x16
Uint8x16 is a SIMD type representing sixteen 8-bit unsigned integer values. Uint8x16 values can be created using the [[Call]] operation on the SIMD.Uint8x16 object. Its behavior as a SIMD type is defined by the Uint8x16 SIMD type descriptor.
2.2.8Bool32x4
Bool32x4 is a SIMD type representing four boolean values, as an intermediate value in manipulating 128-bit vectors. Bool32x4 values can be created using the [[Call]] operation on the SIMD.Bool32x4 object. Its behavior as a SIMD type is defined by the Bool32x4 SIMD type descriptor.
2.2.9Bool16x8
Bool16x8 is a SIMD type representing eight boolean values, as an intermediate value in manipulating 128-bit vectors. Bool16x8 values can be created using the [[Call]] operation on the SIMD.Bool16x8 object. Its behavior as a SIMD type is defined by the Bool16x8 SIMD type descriptor.
2.2.10Bool8x16
Bool8x16 is a SIMD type representing sixteen boolean values, as an intermediate value in manipulating 128-bit vectors. Bool8x16 values can be created using the [[Call]] operation on the SIMD.Bool8x16 object. Its behavior as a SIMD type is defined by the Bool8x16 SIMD type descriptor.
If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices) return true; otherwise, return false.
If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices) return true; otherwise, return false.
If x and y are exactly the same sequence of code units (same length and same code units at corresponding indices) return true; otherwise, return false.
Two possible internal algorithms are provided. The choice is implementation dependent and should be the alternative that is most efficient for the implementation. An implementation must use the same choice each time this step is executed.
The first option:
Return n.
The second option:
Let subnormal be false.
If descriptor is Float32x4Descriptor, and if n rounded (ties to even) to a single-precision floating point number, in the IEEE 754-2008 single precision binary representation, is a subnormal value, let subnormal be true.
Otherwise, assert descriptor is not a floating point SIMD type descriptor.
If subnormal is true,
If n > 0, return +0.
Otherwise, return -0.
Otherwise, return n.
5.1.6SIMDBinaryOp( a, b, op, outputDescriptor )
Assert: a.[[SIMDTypeDescriptor]] and b.[[SIMDTypeDescriptor]] are the same SIMD type descriptor.
Let descriptor be a.[[SIMDTypeDescriptor]].
If outputDescriptor is not provided, let outputDescriptor be descriptor.
Let list be a new List of length descriptor.[[VectorLength]].
Assert: descriptor.[[VectorLength]] × descriptor.[[ElementSize]] = 128. Otherwise, in a future extension to the spec, different boolean descriptors will be returned.
Let length be descriptor.[[VectorLength]].
If length = 4, return Bool32x4Descriptor.
If length = 8, return Bool16x8Descriptor.
Assert length = 16.
Return Bool8x16Descriptor.
5.1.15SIMDRelationalOp( a, b, op )
Let outputDescriptor be SIMDBoolType(a.[[SIMDTypeDescriptor]]).
Given two arguments, calls ToNumber on each of the arguments and returns the smaller of the resulting values.
If n or m is is NaN, the result is NaN.
The comparison of values to determine the smallest value is done using the Abstract Relational Comparison algorithm (7.2.11) except that +0 is considered to be larger than −0.
Let R be a String value produced by concatenating S and next.
Increase k by 1.
Return R.
5.2Constructor properties SIMDConstructor of the SIMD object
Each SIMDConstructor, namely Float32x4, Int32x4, Int16x8, Int8x16, Uint32x4, Uint16x8, Uint8x16, Bool32x4, Bool16x8, and Bool8x16, is associated with a SIMDType and SIMDDescriptor. This section describes the constructors and properties on them. Most properties are identical, existing separately defined on each constructor, with most differences being in the SIMDDescriptor. Certain functions are defined only on a subset of SIMDConstructors, however, and this is noted above their algorithm definition.
Each SIMD constructor is defined as a property of the SIMD object. For example, the constructor for Float32x4 is defined as the property SIMD.Float32x4.
The definitions of the constructor and properties of the constructor to follow constitute different identities of functions and objects for each of the copies; in a real implementation, they may call out to completely different pieces of code, even if their implementation in the spec is the same.
5.2.1SIMDConstructor( ...values )
Note
SIMD wrapped objects cannot be created using new on SIMDConstructor; they can be created explicitly with Object() however.
If NewTarget is not undefined, throw a TypeError exception.
On Float32x4, in this specification, the math is done on two 64-bit values which have a precise representation as a 32-bit float, and then rounded to a Float32. This is equivalent to doing the math on two 32-bit values and storing the result in a 32-bit float. For more information, see When is Double Rounding Innocuous?.
On Float32x4, in this specification, the math is done on two 64-bit values which have a precise representation as a 32-bit float, and then rounded to a Float32. This is equivalent to doing the math on two 32-bit values and storing the result in a 32-bit float. For more information, see When is Double Rounding Innocuous?.
On Float32x4, in this specification, the math is done on two 64-bit values which have a precise representation as a 32-bit float, and then rounded to a Float32. This is equivalent to doing the math on two 32-bit values and storing the result in a 32-bit float. For more information, see When is Double Rounding Innocuous?.
On Float32x4, in this specification, the math is done on two 64-bit values which have a precise representation as a 32-bit float, and then rounded to a Float32. This is equivalent to doing the math on two 32-bit values and storing the result in a 32-bit float. For more information, see When is Double Rounding Innocuous?.
5.3.7SIMDConstructor.max(a, b)
This operation is only defined on floating point SIMD types.
If a.[[SIMDTypeDescriptor]] is not SIMDDescriptor or b.[[SIMDTypeDescriptor]] is not SIMDDescriptor, throw a TypeError exception.
Due to the definition of SIMDRelationalOp, denormals will perform in this function as if they are equal to each other on some platforms, but not others.
load takes a TypedArray of any element type as an argument. One way to use it is to pass in a Uint8Array regardless of SIMD type, which is useful because it allows the compiler to eliminate the shift in going from the index to the pointer offset. Other options considered were to use an ArrayBuffer (but this is not idiomatic, to take an ArrayBuffer directly as an argument to read off of) or a DataView (but DataViews don't tend to expose platform-dependent endianness, which is important here, and they tend to use methods on DataView.prototype, which are harder to optimize in an asm.js context).
In this definition, TIMD is not SIMD, and TIMD ranges over all SIMD types for which SIMDDescriptor.[[ElementSize]] × SIMDDescriptor.[[VectorLength]] = TIMDDescriptor.[[ElementSize]] × TIMDDescriptor.[[VectorLength]], unless one descriptor does not have a [[SerializeElement]] field.
Note
All of the SIMD types described in this spec are 16 bytes, but Booleans have no serialization, so all pairs of types which do not include Booleans are included.
If value.[[SIMDTypeDescriptor]] is not TIMDDescriptor, throw a TypeError exception.
In this definition, TIMD is not SIMD, TIMD ranges over all SIMD types for which SIMDDescriptor.[[VectorLength]] = TIMDDescriptor.[[VectorLength]], neither of SIMD and TIMD are of boolean type, and SIMD and TIMD are not both integer types.
If value.[[SIMDTypeDescriptor]] is not TIMDDescriptor, throw a TypeError exception.
Let list be a copy of value.[[SIMDElements]].
If SIMD is an integer type and TIMD is a floating point type,
An ECMAScript implementation that includes the ECMA-402 Internationalization API must implement the TypedArray.prototype.toLocaleString method as specified in the ECMA-402 specification. If an ECMAScript implementation does not include the ECMA-402 API the following specification of the toLocaleString method is used.
Note
The first and second editions of ECMA-402 did not include a replacement specification for the TypedArray.prototype.toLocaleString method.
The meanings of the optional parameters to this method are defined in the ECMA-402 specification; implementations that do not include ECMA-402 support must not use those parameter positions for anything else.
If this does not have a [[SIMDWrapperData]] internal slot, throw a TypeError exception.
If this.[[SIMDWrapperData]].[[SIMDTypeDescriptor]] is not SIMDDescriptor, throw a TypeError exception.
Let separator be the String value for the list-separator String appropriate for the host environment’s current locale (this is derived in an implementation-defined way).
This definition depends on the primitive SIMDType's behavior under ToString. Alternatively, SIMDType could have ToString defined by calling ToObject and then reaching this method (or whatever the user overrides it with), in which case the current definition in ToString would be brought down here.
5.4.5SIMDConstructor.prototype [ @@toStringTag ]
The initial value of the @@toStringTag property is the String value "SIMD.SIMD", e.g., "SIMD.Float32x4".
This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
5.4.6SIMDConstructor.prototype [ @@toPrimitive ] ( hint )
Note
This function is called by ECMAScript language operators to convert a Symbol object to a primitive value. The allowed values for hint are "default", "number", and "string".
When the @@toPrimitive method is called with argument hint, the following steps are taken:
If Type(s) is not Object, throw a TypeError exception.
If s does not have a [[SIMDWrapperData]] internal slot, throw a TypeError exception.
If s.[[SIMDWrapperData]].[[SIMDTypeDescriptor]] is not SIMDDescriptor, throw a TypeError exception.
Return the value of s’s [[SIMDWrapperData]] internal slot.
The value of the name property of this function is "[Symbol.toPrimitive]".
This property has the attributes { [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }.
5.5SIMD type descriptors
This section describes the SIMD type descriptors, which are organized as described in the %SIMD% section. The following internal algorithms support the definition of the SIMD types.
In the internal algorithms in this section, preceding the first step, if isLittleEndian is not present, set isLittleEndian to either true or false. The choice is implementation dependent and should be the alternative that is most efficient for the implementation. An implementation must use the same value each time one of the following algorithms is executed, and it must be consistent across all algorithms.
5.5.1SerializeFloat32( block, offset, n, isLittleEndian )
Note
Derived from part of SetValueInBuffer. Note that this specification does not require a particular bit pattern for NaN, and that it does not need to be the same across calls.
Assert: offset + 4 is less than or equal to the size of block.
Set rawBytes to a List containing the 4 bytes that are the result of converting value to IEEE 754-2008 binary32 format using “Round to nearest, ties to even” rounding mode. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If value is NaN, rawValue may be set to any implementation chosen NaN encoding. An implementation must always choose the same NaN encoding for a distinct Not-a-Number value.
Store the individual bytes of rawBytes into block, in order, starting at block[offset].
Derived from part of GetValueFromBuffer. Note that while this says to return "the NaN value", the binary representation is not observable and canonicalization is not required.
Assert: offset + descriptor.[[ElementSize]] is less than or equal to the size of block.
Let rawBytes be a List containing the descriptor.[[ElementSize]]-byte binary 2’s complement encoding of n. If isLittleEndian is false, the bytes are ordered in big endian order. Otherwise, the bytes are ordered in little endian order.
Store the individual bytes of rawBytes into block, in order, starting at block[offset].
Assert: offset + descriptor.[[ElementSize]] is less than or equal to the size of block.
Let rawValue be a List of descriptor.[[ElementSize]] containing, in order, the sequence of descriptor.[[ElementSize]] bytes starting with block[offset].
If isLittleEndian is false, reverse the order of the elements of rawValue.
Let intValue be the byte elements of rawValue concatenated and interpreted as a bit string encoding of an integer of bit length descriptor.[[ElementSize]] × 8. If descriptor is a signed type, interpret as signed 2's complement; if it is unsigned, interpret as an unsigned integer.
Return the Number value that corresponds to intValue.