Skip to content

Commit

Permalink
Support SharedArrayBuffers
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Oct 1, 2023
1 parent ee4c77f commit a60148c
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 60 deletions.
120 changes: 74 additions & 46 deletions lib/serialize/buffers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
const t = require('@babel/types');

// Imports
const {createDependency, createAssignment} = require('./records.js'),
{ARRAY_BUFFER_TYPE, registerSerializer} = require('./types.js'),
const {createDependency} = require('./records.js'),
{ARRAY_BUFFER_TYPE, SHARED_ARRAY_BUFFER_TYPE, registerSerializer} = require('./types.js'),
{isNumberKey} = require('./utils.js');

// Exports
Expand Down Expand Up @@ -96,50 +96,16 @@ module.exports = {
return ARRAY_BUFFER_TYPE;
},

serializeSharedArrayBuffer(buf, record) {
// `new SharedArrayBuffer(8)`
const uint = new Uint8Array(buf),
len = uint.length;
const sharedArrayBufferRecord = this.serializeValue(SharedArrayBuffer);
const node = t.newExpression(sharedArrayBufferRecord.varNode, [t.numericLiteral(len)]);
createDependency(record, sharedArrayBufferRecord, node, 'callee');

const firstNonZeroByte = uint.findIndex(byte => byte !== 0);
if (firstNonZeroByte !== -1) {
// Assign values
// `new Uint8Array(buffer).set([...])`
const byteNodes = [];
let numTrailingZeros = 0;
for (let pos = firstNonZeroByte; pos < len; pos++) {
const byte = uint[pos];
byteNodes.push(t.numericLiteral(byte));
if (byte === 0) {
numTrailingZeros++;
} else {
numTrailingZeros = 0;
}
}
if (numTrailingZeros > 0) byteNodes.length -= numTrailingZeros;

const uintRecord = this.serializeValue(Uint8Array);
const assignmentNode = t.callExpression(
t.memberExpression(
t.newExpression(uintRecord.varNode, [record.varNode]),
t.identifier('set')
),
[
t.arrayExpression(byteNodes),
...(firstNonZeroByte > 0 ? [t.numericLiteral(firstNonZeroByte)] : [])
]
);
const assignment = createAssignment(
record, assignmentNode, assignmentNode.callee.object.arguments, 0
);
createDependency(assignment, uintRecord, assignmentNode.callee.object, 'callee');
}

// Wrap in properties
return this.wrapWithProperties(buf, record, node, SharedArrayBuffer.prototype);
/**
* Trace SharedArrayBuffer.
* @param {SharedArrayBuffer} buf - SharedArrayBuffer
* @param {Object} record - Record
* @returns {number} - Type ID
*/
traceSharedArrayBuffer(buf, record) {
this.traceProperties(buf, record, undefined);
record.extra = {buf};
return SHARED_ARRAY_BUFFER_TYPE;
}
};

Expand Down Expand Up @@ -180,3 +146,65 @@ function serializeArrayBuffer(record) {
return this.wrapWithProperties(node, record, this.arrayBufferPrototypeRecord, null);
}
registerSerializer(ARRAY_BUFFER_TYPE, serializeArrayBuffer);

/**
* Serialize SharedArrayBuffer.
* @this {Object} Serializer
* @param {Object} record - Record
* @param {Object} record.extra - Extra props object
* @param {ArrayBuffer} record.extra.buf - ArrayBuffer
* @returns {Object} - AST node
*/
function serializeSharedArrayBuffer(record) {
const uint = new Uint8Array(record.extra.buf);

let valNodes = [],
allZero = true,
numTrailingZeros = 0,
firstNonZeroIndex = 0;
uint.forEach((val, index) => {
if (val === 0) {
valNodes.push(null);
numTrailingZeros++;
} else {
valNodes.push(t.numericLiteral(val));
numTrailingZeros = 0;
if (allZero) {
allZero = false;
firstNonZeroIndex = index;
}
}
});

// `new SharedArrayBuffer(8)`
const node = t.newExpression(
this.traceAndSerializeGlobal(SharedArrayBuffer),
[t.numericLiteral(uint.length)]
);

if (!allZero) {
// `new Uint8Array(arr).set([1, 2, 3]);`
if (numTrailingZeros > 0) valNodes.length -= numTrailingZeros;
if (firstNonZeroIndex > 0) valNodes = valNodes.slice(firstNonZeroIndex);

this.assignmentNodes.push(
t.expressionStatement(
t.callExpression(
t.memberExpression(
t.newExpression(this.traceAndSerializeGlobal(Uint8Array), [record.varNode]),
t.identifier('set')
),
[
t.arrayExpression(valNodes),
...(firstNonZeroIndex > 0 ? [t.numericLiteral(firstNonZeroIndex)] : [])
]
)
)
);

record.usageCount++;
}

return this.wrapWithProperties(node, record, this.sharedArrayBufferPrototypeRecord, null);
}
registerSerializer(SHARED_ARRAY_BUFFER_TYPE, serializeSharedArrayBuffer);
3 changes: 2 additions & 1 deletion lib/serialize/trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = {
this.bigIntPrototypeRecord = this.traceValue(BigInt.prototype, null, null);
this.symbolPrototypeRecord = this.traceValue(Symbol.prototype, null, null);
this.arrayBufferPrototypeRecord = this.traceValue(ArrayBuffer.prototype, null, null);
this.sharedArrayBufferPrototypeRecord = this.traceValue(SharedArrayBuffer.prototype, null, null);

this.minusZeroRecord = null;

Expand Down Expand Up @@ -162,7 +163,7 @@ module.exports = {
if (objType === 'FinalizationRegistry') return this.traceFinalizationRegistry(val, record);
// if (typedArrayRegex.test(objType)) return this.traceBuffer(val, objType, record);
if (objType === 'ArrayBuffer') return this.traceArrayBuffer(val, record);
// if (objType === 'SharedArrayBuffer') return this.traceSharedArrayBuffer(val, record);
if (objType === 'SharedArrayBuffer') return this.traceSharedArrayBuffer(val, record);
if (objType === 'String') return this.traceBoxedString(val, record);
if (objType === 'Boolean') return this.traceBoxedBoolean(val, record);
if (objType === 'Number') return this.traceBoxedNumber(val, record);
Expand Down
2 changes: 2 additions & 0 deletions lib/serialize/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const NO_TYPE = 0,
SYMBOL_FOR_TYPE = SYMBOL_TYPE | 1,
BUFFER_TYPE = 512,
ARRAY_BUFFER_TYPE = BUFFER_TYPE | 1,
SHARED_ARRAY_BUFFER_TYPE = BUFFER_TYPE | 2,
EXPORT_JS_TYPE = 1,
EXPORT_COMMONJS_TYPE = 2,
EXPORT_ESM_TYPE = 3,
Expand Down Expand Up @@ -98,6 +99,7 @@ module.exports = {
SYMBOL_FOR_TYPE,
BUFFER_TYPE,
ARRAY_BUFFER_TYPE,
SHARED_ARRAY_BUFFER_TYPE,
EXPORT_JS_TYPE,
EXPORT_COMMONJS_TYPE,
EXPORT_ESM_TYPE,
Expand Down
46 changes: 33 additions & 13 deletions test/buffers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ describe('Buffers', () => {
});
});

describe.skip('sharedArrayBuffers', () => {
describe('sharedArrayBuffers', () => {
itSerializes('entirely zeroed', {
in: () => new SharedArrayBuffer(8),
out: 'new SharedArrayBuffer(8)',
Expand All @@ -266,18 +266,38 @@ describe('Buffers', () => {
}
});

itSerializes('not zeroed', {
in() {
const buf = new SharedArrayBuffer(8);
new Uint8Array(buf).set([2, 3, 0, 15], 2);
return buf;
},
out: '(()=>{const a=new SharedArrayBuffer(8);new Uint8Array(a).set([2,3,0,15],2);return a})()',
validate(buf) {
expect(buf).toHavePrototype(SharedArrayBuffer.prototype);
expect(buf.byteLength).toBe(8);
expect([...new Uint8Array(buf)]).toEqual([0, 0, 2, 3, 0, 15, 0, 0]);
}
describe('not zeroed', () => {
itSerializes('with all bytes non-zero', {
in() {
const buf = new SharedArrayBuffer(8);
new Uint8Array(buf).set([1, 2, 3, 4, 5, 6, 7, 16]);
return buf;
},
out: `(()=>{
const a=new SharedArrayBuffer(8);
new Uint8Array(a).set([1,2,3,4,5,6,7,16]);
return a
})()`,
validate(buf) {
expect(buf).toHavePrototype(SharedArrayBuffer.prototype);
expect(buf.byteLength).toBe(8);
expect([...new Uint8Array(buf)]).toEqual([1, 2, 3, 4, 5, 6, 7, 16]);
}
});

itSerializes('with zero bytes start and end', {
in() {
const buf = new SharedArrayBuffer(8);
new Uint8Array(buf).set([0, 0, 2, 3, 0, 15, 0, 0]);
return buf;
},
out: '(()=>{const a=new SharedArrayBuffer(8);new Uint8Array(a).set([2,3,,15],2);return a})()',
validate(buf) {
expect(buf).toHavePrototype(SharedArrayBuffer.prototype);
expect(buf.byteLength).toBe(8);
expect([...new Uint8Array(buf)]).toEqual([0, 0, 2, 3, 0, 15, 0, 0]);
}
});
});

describe('with prototype set to', () => {
Expand Down

0 comments on commit a60148c

Please sign in to comment.