Skip to content

Commit

Permalink
Implement Buffer swap16, swap32, swap64 (#1659)
Browse files Browse the repository at this point in the history
* Implement Buffer swap16, swap32, swap64

* Initial incorporation of feedback

- Use constexpr
- Clean up the indexing
- Check for detached
- Use suggested text for exception text
  • Loading branch information
malcolmstill authored Jan 11, 2023
1 parent 56a6cf7 commit f1e6ea2
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 3 deletions.
88 changes: 85 additions & 3 deletions src/bun.js/bindings/JSBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1114,17 +1114,99 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_lastIndexOfBody(JSC:
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap16Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
return JSC::JSValue::encode(jsUndefined());
auto scope = DECLARE_THROW_SCOPE(vm);

constexpr int elemSize = 2;
int64_t length = static_cast<int64_t>(castedThis->byteLength());
if (length % elemSize != 0) {
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 16-bits"_s);
return JSC::JSValue::encode(jsUndefined());
}

if (UNLIKELY(castedThis->isDetached())) {
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
return JSValue::encode(jsUndefined());
}

uint8_t* typedVector = castedThis->typedVector();

for (size_t elem = 0; elem < length; elem += elemSize) {
const size_t right = elem + 1;

uint8_t temp = typedVector[elem];
typedVector[elem] = typedVector[right];
typedVector[right] = temp;
}

return JSC::JSValue::encode(castedThis);
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap32Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
return JSC::JSValue::encode(jsUndefined());
auto scope = DECLARE_THROW_SCOPE(vm);

constexpr int elemSize = 4;
int64_t length = static_cast<int64_t>(castedThis->byteLength());
if (length % elemSize != 0) {
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 32-bits"_s);
return JSC::JSValue::encode(jsUndefined());
}

if (UNLIKELY(castedThis->isDetached())) {
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
return JSValue::encode(jsUndefined());
}

uint8_t* typedVector = castedThis->typedVector();

constexpr size_t swaps = elemSize/2;
for (size_t elem = 0; elem < length; elem += elemSize) {
const size_t right = elem + elemSize - 1;
for (size_t k = 0; k < swaps; k++) {
const size_t i = right - k;
const size_t j = elem + k;

uint8_t temp = typedVector[i];
typedVector[i] = typedVector[j];
typedVector[j] = temp;
}
}

return JSC::JSValue::encode(castedThis);
}
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
{
auto& vm = JSC::getVM(lexicalGlobalObject);
return JSC::JSValue::encode(jsUndefined());
auto scope = DECLARE_THROW_SCOPE(vm);

constexpr size_t elemSize = 8;
int64_t length = static_cast<int64_t>(castedThis->byteLength());
if (length % elemSize != 0) {
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 64-bits"_s);
return JSC::JSValue::encode(jsUndefined());
}

if (UNLIKELY(castedThis->isDetached())) {
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
return JSValue::encode(jsUndefined());
}

uint8_t* typedVector = castedThis->typedVector();

constexpr size_t swaps = elemSize/2;
for (size_t elem = 0; elem < length; elem += elemSize) {
const size_t right = elem + elemSize - 1;
for (size_t k = 0; k < swaps; k++) {
const size_t i = right - k;
const size_t j = elem + k;

uint8_t temp = typedVector[i];
typedVector[i] = typedVector[j];
typedVector[j] = temp;
}
}

return JSC::JSValue::encode(castedThis);
}

static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
Expand Down
78 changes: 78 additions & 0 deletions test/bun.js/buffer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,84 @@ it("Buffer.from(base64)", () => {
).toBe('console.log("hello world")\n');
});

it("Buffer.swap16", () => {
const examples = [
["", ""],
["a1", "1a"],
["a1b2", "1a2b"]
];

for (let i = 0; i < examples.length; i++) {
const input = examples[i][0];
const output = examples[i][1];
const buf = Buffer.from(input, "utf-8");

const ref = buf.swap16();
expect(ref instanceof Buffer).toBe(true);
expect(buf.toString()).toBe(output);
}

const buf = Buffer.from("123", "utf-8");
try {
buf.swap16();
expect(false).toBe(true);
} catch (exception) {
expect(exception.message).toBe("Buffer size must be a multiple of 16-bits");
}
});

it("Buffer.swap32", () => {
const examples = [
["", ""],
["a1b2", "2b1a"],
["a1b2c3d4", "2b1a4d3c"]
];

for (let i = 0; i < examples.length; i++) {
const input = examples[i][0];
const output = examples[i][1];
const buf = Buffer.from(input, "utf-8");

const ref = buf.swap32();
expect(ref instanceof Buffer).toBe(true);
expect(buf.toString()).toBe(output);
}

const buf = Buffer.from("12345", "utf-8");
try {
buf.swap32();
expect(false).toBe(true);
} catch (exception) {
expect(exception.message).toBe("Buffer size must be a multiple of 32-bits");
}
});

it("Buffer.swap64", () => {
const examples = [
["", ""],
["a1b2c3d4", "4d3c2b1a"],
["a1b2c3d4e5f6g7h8", "4d3c2b1a8h7g6f5e"],
];

for (let i = 0; i < examples.length; i++) {
const input = examples[i][0];
const output = examples[i][1];
const buf = Buffer.from(input, "utf-8");

const ref = buf.swap64();
expect(ref instanceof Buffer).toBe(true);
expect(buf.toString()).toBe(output);
}

const buf = Buffer.from("123456789", "utf-8");
try {
buf.swap64();
expect(false).toBe(true);
} catch (exception) {
expect(exception.message).toBe("Buffer size must be a multiple of 64-bits");
}
});

it("Buffer.toString regessions", () => {
expect(
Buffer.from([65, 0])
Expand Down

0 comments on commit f1e6ea2

Please sign in to comment.