Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions bench/runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,4 @@ export function run(opts = {}) {
}

export const bench = Mitata.bench;

export function group(_name, fn) {
return Mitata.group(fn);
}
export const group = Mitata.group;
156 changes: 156 additions & 0 deletions bench/snippets/compression-streams.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { bench, group, run } from "../runner.mjs";

const runAll = !process.argv.includes("--simple");

const small = new Uint8Array(1024);
const medium = new Uint8Array(1024 * 100);
const large = new Uint8Array(1024 * 1024);

for (let i = 0; i < large.length; i++) {
const value = Math.floor(Math.sin(i / 100) * 128 + 128);
if (i < small.length) small[i] = value;
if (i < medium.length) medium[i] = value;
large[i] = value;
}

const format = new Intl.NumberFormat("en-US", { notation: "compact", unit: "byte" });

async function compress(data, format) {
const cs = new CompressionStream(format);
const writer = cs.writable.getWriter();
const reader = cs.readable.getReader();

writer.write(data);
writer.close();

const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}

const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}

async function decompress(data, format) {
const ds = new DecompressionStream(format);
const writer = ds.writable.getWriter();
const reader = ds.readable.getReader();

writer.write(data);
writer.close();

const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}

const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}

async function roundTrip(data, format) {
const compressed = await compress(data, format);
return await decompress(compressed, format);
}

const formats = ["deflate", "gzip", "deflate-raw"];
if (runAll) formats.push("brotli", "zstd");

// Small data benchmarks (1KB)
group(`CompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(small, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});

// Medium data benchmarks (100KB)
group(`CompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(medium, fmt));
} catch (e) {}
}
});

// Large data benchmarks (1MB)
group(`CompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});

const compressedData = {};
for (const fmt of formats) {
try {
compressedData[fmt] = {
small: await compress(small, fmt),
medium: await compress(medium, fmt),
large: await compress(large, fmt),
};
} catch (e) {
// Skip unsupported formats
}
}

group(`DecompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].small, fmt));
}
}
});

group(`DecompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].medium, fmt));
}
}
});

group(`DecompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].large, fmt));
}
}
});

group(`roundtrip ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await roundTrip(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});

await run();
4 changes: 2 additions & 2 deletions docs/runtime/nodejs-compat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa

### [`CompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/CompressionStream)

🔴 Not implemented.
🟢 Fully implemented.

### [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)

Expand Down Expand Up @@ -273,7 +273,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa

### [`DecompressionStream`](https://developer.mozilla.org/en-US/docs/Web/API/DecompressionStream)

🔴 Not implemented.
🟢 Fully implemented.

### [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)

Expand Down
139 changes: 139 additions & 0 deletions src/bun.js/bindings/JSCompressionStream.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include "config.h"
#include "JSCompressionStream.h"

#include "JSDOMBuiltinConstructor.h"
#include "JSDOMGlobalObjectInlines.h"
#include "WebCoreJSClientData.h"
#include "WebCoreJSBuiltins.h"
#include <JavaScriptCore/FunctionPrototype.h>
#include <JavaScriptCore/JSCInlines.h>

namespace WebCore {

using namespace JSC;

static JSC_DECLARE_CUSTOM_GETTER(jsCompressionStreamConstructor);

class JSCompressionStreamPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSCompressionStreamPrototype* create(JSC::VM& vm, JSDOMGlobalObject* globalObject, JSC::Structure* structure)
{
JSCompressionStreamPrototype* ptr = new (NotNull, JSC::allocateCell<JSCompressionStreamPrototype>(vm)) JSCompressionStreamPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}

DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, Base);
return &vm.plainObjectSpace();
}

static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}

private:
JSCompressionStreamPrototype(JSC::VM& vm, JSC::JSGlobalObject*, JSC::Structure* structure)
: JSC::JSNonFinalObject(vm, structure)
{
}

void finishCreation(JSC::VM&);
};
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSCompressionStreamPrototype, JSCompressionStreamPrototype::Base);

using JSCompressionStreamDOMConstructor = JSDOMBuiltinConstructor<JSCompressionStream>;

template<> const ClassInfo JSCompressionStreamDOMConstructor::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamDOMConstructor) };

template<> JSValue JSCompressionStreamDOMConstructor::prototypeForStructure(JSC::VM& vm, const JSDOMGlobalObject& globalObject)
{
UNUSED_PARAM(vm);
return globalObject.functionPrototype();
}

template<> void JSCompressionStreamDOMConstructor::initializeProperties(VM& vm, JSDOMGlobalObject& globalObject)
{
putDirect(vm, vm.propertyNames->length, jsNumber(0), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
JSString* nameString = jsNontrivialString(vm, "CompressionStream"_s);
m_originalName.set(vm, this, nameString);
putDirect(vm, vm.propertyNames->name, nameString, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum);
putDirect(vm, vm.propertyNames->prototype, JSCompressionStream::prototype(vm, globalObject), JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete);
}

template<> FunctionExecutable* JSCompressionStreamDOMConstructor::initializeExecutable(VM& vm)
{
return compressionStreamInitializeCompressionStreamCodeGenerator(vm);
}

static const HashTableValue JSCompressionStreamPrototypeTableValues[] = {
{ "constructor"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, jsCompressionStreamConstructor, 0 } },
{ "readable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamReadableCodeGenerator, 0 } },
{ "writable"_s, static_cast<unsigned>(JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::Accessor | JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinAccessorType, compressionStreamWritableCodeGenerator, 0 } },
};

const ClassInfo JSCompressionStreamPrototype::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStreamPrototype) };

void JSCompressionStreamPrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSCompressionStream::info(), JSCompressionStreamPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}

const ClassInfo JSCompressionStream::s_info = { "CompressionStream"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCompressionStream) };

JSC::GCClient::IsoSubspace* JSCompressionStream::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSCompressionStream, UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForCompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForCompressionStream = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForCompressionStream.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForCompressionStream = std::forward<decltype(space)>(space); });
}

JSCompressionStream::JSCompressionStream(Structure* structure, JSDOMGlobalObject& globalObject)
: JSDOMObject(structure, globalObject)
{
}

void JSCompressionStream::finishCreation(VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}

JSObject* JSCompressionStream::createPrototype(VM& vm, JSDOMGlobalObject& globalObject)
{
auto* structure = JSCompressionStreamPrototype::createStructure(vm, &globalObject, globalObject.objectPrototype());
structure->setMayBePrototype(true);
return JSCompressionStreamPrototype::create(vm, &globalObject, structure);
}

JSObject* JSCompressionStream::prototype(VM& vm, JSDOMGlobalObject& globalObject)
{
return getDOMPrototype<JSCompressionStream>(vm, globalObject);
}

JSValue JSCompressionStream::getConstructor(VM& vm, const JSGlobalObject* globalObject)
{
return getDOMConstructor<JSCompressionStreamDOMConstructor, DOMConstructorID::CompressionStream>(vm, *jsCast<const JSDOMGlobalObject*>(globalObject));
}

JSC_DEFINE_CUSTOM_GETTER(jsCompressionStreamConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
{
auto& vm = JSC::getVM(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
auto* prototype = jsDynamicCast<JSCompressionStreamPrototype*>(JSValue::decode(thisValue));
if (!prototype)
return throwVMTypeError(lexicalGlobalObject, throwScope);
return JSValue::encode(JSCompressionStream::getConstructor(vm, lexicalGlobalObject));
}

} // namespace WebCore
40 changes: 40 additions & 0 deletions src/bun.js/bindings/JSCompressionStream.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include "JSDOMWrapper.h"

namespace WebCore {

class JSCompressionStream : public JSDOMObject {
public:
using Base = JSDOMObject;
static JSCompressionStream* create(JSC::Structure* structure, JSDOMGlobalObject* globalObject)
{
JSCompressionStream* ptr = new (NotNull, JSC::allocateCell<JSCompressionStream>(globalObject->vm())) JSCompressionStream(structure, *globalObject);
ptr->finishCreation(globalObject->vm());
return ptr;
}

static JSC::JSObject* createPrototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSObject* prototype(JSC::VM&, JSDOMGlobalObject&);
static JSC::JSValue getConstructor(JSC::VM&, const JSC::JSGlobalObject*);

DECLARE_INFO;

static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}

template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);

JSCompressionStream(JSC::Structure*, JSDOMGlobalObject&);
void finishCreation(JSC::VM&);
};

} // namespace WebCore
Loading
Loading