Basic SIMD features

I think we should look at adding SIMD features to the ECMAScript specification.

I think this is justified for the following reasons:

  • The current threading model isn’t very practical for when you have to do a lot of identical simple operations. I do not think we should eliminate multi-threading as currently implemented, but I do think SIMD features would allow this particular set of cases to be handled in a better way.
  • WebAssembly has SIMD features now, albeit to a relatively limited degree. If the runtime systems work with this feature, they should at least theoretically be able to handle SIMD within more traditional web programming languages.
  • Canvas elements are fundamentally graphical, as are SVG instances. SIMD is helpful for speeding up graphics processing and associated logic.
  • Plenty of systems like Node and Bun require encryption for their client/server traffic for security reasons and this could make the performance hit from that less awful.

Here’s how I think SIMD could look in ECMAScript. Note that this is a first draft, and I admit that others will likely come up with better interface designs.

'use strict';
/*SIMD would either be provided as a module or just a value that's pre-created for you in your environment. I'm not sure yet.*/
const SIMD_multi_multi = SIMD.multiMulti;
const arrayA = [1, 2, 3, 5, 7, 11, 13];
const arrayB = [1, 2, 4, 8, 16, 32, 64];
const added = SIMD_multi_multi.add(arrayA, arrayB);
console.log(JSON.stringify(added)); //prints "[3,6,7,13,23,43,77]"
const SIMD_multi_single = SIMD.multiSingle;
const multiplied = SIMD_multi_single.multiply(arrayA, 2);
console.log(JSON.stringify(multiplied)); //prints "[2,4,6,10,14,22,26]"
const SIMD_single_multi = SIMD.singleMulti;
const subtracted = SIMD_single_multi.subtract(13, arrayA);
console.log(JSON.stringify(subtracted)); //prints "[12,11,10,8,6,2,0]"

My general idea for an interface is a single SIMD object. It would have the following members:

  • SIMD.accelerationExists : This is a boolean. If true, hardware acceleration exists for SIMD and may or may not be used. If this is false, traditional Array methods are used as workarounds by the runtime engine. This value is constant and identical across all threads.
  • SIMD.accelerationEnabled : This is a boolean. If true, hardware acceleration for SIMD operations will be used if executed. If false, it won’t be. This is because our code may or may not be the only code wanting to use hardware acceleration for SIMD. This value is not constant and may differ between threads.
  • SIMD.multiMulti : This Object has a series of static methods which all take 2 Array-like objects and return an Array. All of these are analogous to the language's operators like the dot operation, add, subtract, boolean logic, bitwise boolean logic, etc. If the two arrays are of different lengths, then missing values are treated as 0, null, or empty strings. I’m not sure at time of writing which option is the least awful.
  • SIMD.singleMulti: This Object has a series of static methods which all take a single value and an Array-like object and return an Array. All of these are analogous to the language's operators like the dot operation, add, subtract, boolean logic, bitwise boolean logic, etc.
  • SIMD.multiSingle: This is effectively a reversed version of SIMD.singleMulti . SIMD.singleMulti.divide(25,[1,5,25]) would yield \[25,5,1\], and SIMD.multiSingle.mod([1,5,25],25) would yield \[1,5,0\]. Both of these are included because there are JS operations where order of operands matter. 7/3 is not 3/7, and the same is true if you’re working with arrays.
  • SIMD.multi: This is for SIMD operations which are analogous to unary operators, such as negation, incrementing, and decrementing.

I get that this is a big feature to add, but I think it’ll be helpful. Please feel free to propose less awful interfaces.

See the opening paragraph on https://github.com/tc39/ecmascript_simd

I think a better avenue would be vector primitives, which bring much-needed convenience features while offering implementors the opportunity to accelerate their operations with SIMD if possible.

Take for example graphical or mathematical applications, who make heavy use of classes like Vector2/Vector4, these are very slow often requiring extra allocations and object-related bookkeeping. It is not far-fetched to see a speedup of an order of magnitude or more if these could be made into primitives, not to mention the improved convenience of not needing to use methods everywhere (i.e a.add(b.times(c)) would become a + b*c with broadcasting-like semantics)

@blob

As long as the vector primitives are compatible with existing primitives with those operands, I think that approach is good too.

Also, if it turns out using the existing operators is not viable for some reason, I think we should stick to static (non-instance) functions instead of member functions. So it’d be something like this:

const A = Vector.make([1, 3, 5, 7]);
const B = Vector.make([2, 4, 6, 8]);
const added = Vector.add(A, B); 
/*A+B should be equivalent to B+A, so I see no reason to have duplicate functions for A and B.*/

Question: Do you think the udea of using booleans for indicating the existence and usability of hardware acceleration should be used here, or should that idea be scrapped?