Skip to content

Commit

Permalink
Support TypedArrays
Browse files Browse the repository at this point in the history
  • Loading branch information
overlookmotel committed Oct 2, 2023
1 parent d977e90 commit 82ef51c
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 68 deletions.
112 changes: 46 additions & 66 deletions lib/serialize/buffers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ const t = require('@babel/types'),
{isNumber} = require('is-it-type');

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

// Exports

Expand All @@ -27,7 +25,6 @@ const bufferToString = Buffer.prototype.toString,
typedArrayOffsetGetter = Object.getOwnPropertyDescriptor(TypedArrayPrototype, 'byteOffset').get;

const bitsRegex = /^(Ui|I)nt(\d+)Array$/;
// TODO: Handle float TypedArrays

module.exports = {
/**
Expand All @@ -53,69 +50,9 @@ module.exports = {
}

record.extra = {type, bytes, offset, len};
// TODO: Serializer for TypedArrays
return TYPED_ARRAY_TYPE;
},

// TODO: Delete this
serializeBuffer(buf, type, record) {
// Create node
let node, defaultProto;
if (type === 'Uint8Array' && buf instanceof Buffer) {
// `Buffer.from('...', 'base64')`
const base64Str = bufferToString.call(buf, 'base64');
const bufferFromRecord = this.serializeValue(Buffer.from);
node = t.callExpression(
bufferFromRecord.varNode,
[t.stringLiteral(base64Str), t.stringLiteral('base64')]
);
createDependency(record, bufferFromRecord, node, 'callee');
defaultProto = Buffer.prototype;
} else {
// `new Uint16Array([...])`
const match = type.match(bitsRegex),
isSigned = match[1] === 'I',
bytesPerChar = match[2] / 8;

const maxByteValue = 256 ** bytesPerChar,
signedMaxByteValue = maxByteValue / 2;

// Convert to Buffer
const offset = typedArrayOffsetGetter.call(buf),
len = typedArrayLengthGetter.call(buf);
const buffer = Buffer.from(typedArrayBufferGetter.call(buf).slice(offset, offset + len));

const byteNodes = [];
for (let pos = 0; pos < buffer.length; pos += bytesPerChar) {
let byte = 0,
byteMultiplier = 1;
for (let byteNum = 0; byteNum < bytesPerChar; byteNum++) {
byte += buffer[pos + byteNum] * byteMultiplier;
byteMultiplier <<= 8;
}

if (isSigned && byte >= signedMaxByteValue) byte -= maxByteValue;

byteNodes.push(t.numericLiteral(byte));
}

const ctor = global[type],
ctorRecord = this.serializeValue(ctor);
node = t.newExpression(ctorRecord.varNode, [t.arrayExpression(byteNodes)]);

createDependency(record, ctorRecord, node, 'callee');

defaultProto = ctor.prototype;
}

// Wrap in properties.
// NB: No need to check for non-standard descriptors on elements
// as buffers are created with all properties non-configurable.
// All numerical keys can be skipped, even ones above max safe integer key as Buffers
// only allow writing to numerical keys within bounds of buffer length.
return this.wrapWithProperties(buf, record, node, defaultProto, undefined, isNumberKey);
},

/**
* Trace ArrayBuffer.
* @param {ArrayBuffer} buf - ArrayBuffer
Expand Down Expand Up @@ -164,6 +101,49 @@ function serializeBuffer(record) {
}
registerSerializer(BUFFER_TYPE, serializeBuffer);

/**
* Serialize TypedArray.
* @this {Object} Serializer
* @param {Object} record - Record
* @param {Object} record.extra - Extra props object
* @param {ArrayBuffer} record.extra.bytes - ArrayBuffer
* @param {number} record.extra.offset - Offset of start of data in `bytes`
* @param {number} record.extra.len - Length of buffer in bytes
* @returns {Object} - AST node
*/
function serializeTypedArray(record) {
const {type, bytes, offset, len} = record.extra,
match = type.match(bitsRegex),
isSigned = match[1] === 'I',
bytesPerChar = match[2] / 8,
maxByteValue = 256 ** bytesPerChar,
signedMaxByteValue = maxByteValue / 2;

const buf = Buffer.from(bytes).subarray(offset, offset + len);

const byteNodes = [];
for (let pos = 0; pos < buf.length; pos += bytesPerChar) {
let byte = 0,
byteMultiplier = 1;
for (let byteNum = 0; byteNum < bytesPerChar; byteNum++) {
byte += buf[pos + byteNum] * byteMultiplier;
byteMultiplier <<= 8;
}

if (isSigned && byte >= signedMaxByteValue) byte -= maxByteValue;

byteNodes.push(t.numericLiteral(byte));
}

// `new Uint16Array([...])`
const ctor = global[type],
node = t.newExpression(this.traceAndSerializeGlobal(ctor), [t.arrayExpression(byteNodes)]);

const ctorPrototypeRecord = this.traceValue(ctor.prototype, null, null);
return this.wrapWithProperties(node, record, ctorPrototypeRecord, null);
}
registerSerializer(TYPED_ARRAY_TYPE, serializeTypedArray);

/**
* Serialize ArrayBuffer.
* @this {Object} Serializer
Expand Down
96 changes: 94 additions & 2 deletions test/buffers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('Buffers', () => {
});
});

describe.skip('typedArrays', () => {
describe('typedArrays', () => {
describe('standard', () => {
itSerializesEqual('Uint8Array', {
in: () => new Uint8Array([100, 200]),
Expand Down Expand Up @@ -121,6 +121,98 @@ describe('Buffers', () => {
});
});

describe('in middle of ArrayBuffer', () => {
itSerializesEqual('Uint8Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Uint8Array(arr, 4, 2);
buf[0] = 100;
buf[1] = 200;
return buf;
},
out: 'new Uint8Array([100,200])',
validate(buf) {
expect(buf).toBeInstanceOf(Uint8Array);
expect([...buf]).toEqual([100, 200]);
}
});

itSerializesEqual('Int8Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Int8Array(arr, 4, 2);
buf[0] = 100;
buf[1] = -56;
return buf;
},
out: 'new Int8Array([100,-56])',
validate(buf) {
expect(buf).toBeInstanceOf(Int8Array);
expect([...buf]).toEqual([100, -56]);
}
});

itSerializesEqual('Uint16Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Uint16Array(arr, 4, 2);
buf[0] = 1000;
buf[1] = 40000;
return buf;
},
out: 'new Uint16Array([1000,40000])',
validate(buf) {
expect(buf).toBeInstanceOf(Uint16Array);
expect([...buf]).toEqual([1000, 40000]);
}
});

itSerializesEqual('Int16Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Int16Array(arr, 4, 2);
buf[0] = 1000;
buf[1] = -1000;
return buf;
},
out: 'new Int16Array([1000,-1000])',
validate(buf) {
expect(buf).toBeInstanceOf(Int16Array);
expect([...buf]).toEqual([1000, -1000]);
}
});

itSerializesEqual('Uint32Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Uint32Array(arr, 4, 2);
buf[0] = 100000;
buf[1] = 40000000;
return buf;
},
out: 'new Uint32Array([100000,40000000])',
validate(buf) {
expect(buf).toBeInstanceOf(Uint32Array);
expect([...buf]).toEqual([100000, 40000000]);
}
});

itSerializesEqual('Int32Array', {
in() {
const arr = new ArrayBuffer(16);
const buf = new Int32Array(arr, 4, 2);
buf[0] = 100000;
buf[1] = -100000;
return buf;
},
out: 'new Int32Array([100000,-100000])',
validate(buf) {
expect(buf).toBeInstanceOf(Int32Array);
expect([...buf]).toEqual([100000, -100000]);
}
});
});

describe('typedArrays with prototype altered', () => {
itSerializes('to null', {
in() {
Expand Down Expand Up @@ -165,7 +257,7 @@ describe('Buffers', () => {
});
});

itSerializes('TypedArray subclass', {
itSerializes.skip('TypedArray subclass', {
in() {
class B extends Uint8Array {}
return new B([100, 200]);
Expand Down

0 comments on commit 82ef51c

Please sign in to comment.