Skip to content

Commit f1e6ea2

Browse files
authored
Implement Buffer swap16, swap32, swap64 (#1659)
* Implement Buffer swap16, swap32, swap64 * Initial incorporation of feedback - Use constexpr - Clean up the indexing - Check for detached - Use suggested text for exception text
1 parent 56a6cf7 commit f1e6ea2

File tree

2 files changed

+163
-3
lines changed

2 files changed

+163
-3
lines changed

src/bun.js/bindings/JSBuffer.cpp

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,17 +1114,99 @@ static inline JSC::EncodedJSValue jsBufferPrototypeFunction_lastIndexOfBody(JSC:
11141114
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap16Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
11151115
{
11161116
auto& vm = JSC::getVM(lexicalGlobalObject);
1117-
return JSC::JSValue::encode(jsUndefined());
1117+
auto scope = DECLARE_THROW_SCOPE(vm);
1118+
1119+
constexpr int elemSize = 2;
1120+
int64_t length = static_cast<int64_t>(castedThis->byteLength());
1121+
if (length % elemSize != 0) {
1122+
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 16-bits"_s);
1123+
return JSC::JSValue::encode(jsUndefined());
1124+
}
1125+
1126+
if (UNLIKELY(castedThis->isDetached())) {
1127+
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
1128+
return JSValue::encode(jsUndefined());
1129+
}
1130+
1131+
uint8_t* typedVector = castedThis->typedVector();
1132+
1133+
for (size_t elem = 0; elem < length; elem += elemSize) {
1134+
const size_t right = elem + 1;
1135+
1136+
uint8_t temp = typedVector[elem];
1137+
typedVector[elem] = typedVector[right];
1138+
typedVector[right] = temp;
1139+
}
1140+
1141+
return JSC::JSValue::encode(castedThis);
11181142
}
11191143
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap32Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
11201144
{
11211145
auto& vm = JSC::getVM(lexicalGlobalObject);
1122-
return JSC::JSValue::encode(jsUndefined());
1146+
auto scope = DECLARE_THROW_SCOPE(vm);
1147+
1148+
constexpr int elemSize = 4;
1149+
int64_t length = static_cast<int64_t>(castedThis->byteLength());
1150+
if (length % elemSize != 0) {
1151+
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 32-bits"_s);
1152+
return JSC::JSValue::encode(jsUndefined());
1153+
}
1154+
1155+
if (UNLIKELY(castedThis->isDetached())) {
1156+
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
1157+
return JSValue::encode(jsUndefined());
1158+
}
1159+
1160+
uint8_t* typedVector = castedThis->typedVector();
1161+
1162+
constexpr size_t swaps = elemSize/2;
1163+
for (size_t elem = 0; elem < length; elem += elemSize) {
1164+
const size_t right = elem + elemSize - 1;
1165+
for (size_t k = 0; k < swaps; k++) {
1166+
const size_t i = right - k;
1167+
const size_t j = elem + k;
1168+
1169+
uint8_t temp = typedVector[i];
1170+
typedVector[i] = typedVector[j];
1171+
typedVector[j] = temp;
1172+
}
1173+
}
1174+
1175+
return JSC::JSValue::encode(castedThis);
11231176
}
11241177
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_swap64Body(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
11251178
{
11261179
auto& vm = JSC::getVM(lexicalGlobalObject);
1127-
return JSC::JSValue::encode(jsUndefined());
1180+
auto scope = DECLARE_THROW_SCOPE(vm);
1181+
1182+
constexpr size_t elemSize = 8;
1183+
int64_t length = static_cast<int64_t>(castedThis->byteLength());
1184+
if (length % elemSize != 0) {
1185+
throwRangeError(lexicalGlobalObject, scope, "Buffer size must be a multiple of 64-bits"_s);
1186+
return JSC::JSValue::encode(jsUndefined());
1187+
}
1188+
1189+
if (UNLIKELY(castedThis->isDetached())) {
1190+
throwVMTypeError(lexicalGlobalObject, scope, "Buffer is detached"_s);
1191+
return JSValue::encode(jsUndefined());
1192+
}
1193+
1194+
uint8_t* typedVector = castedThis->typedVector();
1195+
1196+
constexpr size_t swaps = elemSize/2;
1197+
for (size_t elem = 0; elem < length; elem += elemSize) {
1198+
const size_t right = elem + elemSize - 1;
1199+
for (size_t k = 0; k < swaps; k++) {
1200+
const size_t i = right - k;
1201+
const size_t j = elem + k;
1202+
1203+
uint8_t temp = typedVector[i];
1204+
typedVector[i] = typedVector[j];
1205+
typedVector[j] = temp;
1206+
}
1207+
}
1208+
1209+
return JSC::JSValue::encode(castedThis);
11281210
}
11291211

11301212
static inline JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)

test/bun.js/buffer.test.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,84 @@ it("Buffer.from(base64)", () => {
556556
).toBe('console.log("hello world")\n');
557557
});
558558

559+
it("Buffer.swap16", () => {
560+
const examples = [
561+
["", ""],
562+
["a1", "1a"],
563+
["a1b2", "1a2b"]
564+
];
565+
566+
for (let i = 0; i < examples.length; i++) {
567+
const input = examples[i][0];
568+
const output = examples[i][1];
569+
const buf = Buffer.from(input, "utf-8");
570+
571+
const ref = buf.swap16();
572+
expect(ref instanceof Buffer).toBe(true);
573+
expect(buf.toString()).toBe(output);
574+
}
575+
576+
const buf = Buffer.from("123", "utf-8");
577+
try {
578+
buf.swap16();
579+
expect(false).toBe(true);
580+
} catch (exception) {
581+
expect(exception.message).toBe("Buffer size must be a multiple of 16-bits");
582+
}
583+
});
584+
585+
it("Buffer.swap32", () => {
586+
const examples = [
587+
["", ""],
588+
["a1b2", "2b1a"],
589+
["a1b2c3d4", "2b1a4d3c"]
590+
];
591+
592+
for (let i = 0; i < examples.length; i++) {
593+
const input = examples[i][0];
594+
const output = examples[i][1];
595+
const buf = Buffer.from(input, "utf-8");
596+
597+
const ref = buf.swap32();
598+
expect(ref instanceof Buffer).toBe(true);
599+
expect(buf.toString()).toBe(output);
600+
}
601+
602+
const buf = Buffer.from("12345", "utf-8");
603+
try {
604+
buf.swap32();
605+
expect(false).toBe(true);
606+
} catch (exception) {
607+
expect(exception.message).toBe("Buffer size must be a multiple of 32-bits");
608+
}
609+
});
610+
611+
it("Buffer.swap64", () => {
612+
const examples = [
613+
["", ""],
614+
["a1b2c3d4", "4d3c2b1a"],
615+
["a1b2c3d4e5f6g7h8", "4d3c2b1a8h7g6f5e"],
616+
];
617+
618+
for (let i = 0; i < examples.length; i++) {
619+
const input = examples[i][0];
620+
const output = examples[i][1];
621+
const buf = Buffer.from(input, "utf-8");
622+
623+
const ref = buf.swap64();
624+
expect(ref instanceof Buffer).toBe(true);
625+
expect(buf.toString()).toBe(output);
626+
}
627+
628+
const buf = Buffer.from("123456789", "utf-8");
629+
try {
630+
buf.swap64();
631+
expect(false).toBe(true);
632+
} catch (exception) {
633+
expect(exception.message).toBe("Buffer size must be a multiple of 64-bits");
634+
}
635+
});
636+
559637
it("Buffer.toString regessions", () => {
560638
expect(
561639
Buffer.from([65, 0])

0 commit comments

Comments
 (0)