diff --git a/packages/bundle-size/README.md b/packages/bundle-size/README.md index 4cea0c954..2d8ae6c4e 100644 --- a/packages/bundle-size/README.md +++ b/packages/bundle-size/README.md @@ -16,11 +16,11 @@ usually do. We repeat this for an increasing number of files. | code generator | files | bundle size | minified | compressed | | ------------------- | ----: | ----------: | --------: | ---------: | -| Protobuf-ES | 1 | 133,976 b | 69,171 b | 15,890 b | -| Protobuf-ES | 4 | 136,165 b | 70,678 b | 16,587 b | -| Protobuf-ES | 8 | 138,927 b | 72,449 b | 17,134 b | -| Protobuf-ES | 16 | 149,377 b | 80,430 b | 19,442 b | -| Protobuf-ES | 32 | 177,168 b | 102,448 b | 24,922 b | +| Protobuf-ES | 1 | 134,690 b | 69,523 b | 15,945 b | +| Protobuf-ES | 4 | 136,879 b | 71,027 b | 16,621 b | +| Protobuf-ES | 8 | 139,641 b | 72,798 b | 17,182 b | +| Protobuf-ES | 16 | 150,091 b | 80,779 b | 19,489 b | +| Protobuf-ES | 32 | 177,882 b | 102,800 b | 24,953 b | | protobuf-javascript | 1 | 314,120 b | 244,024 b | 35,999 b | | protobuf-javascript | 4 | 340,137 b | 258,996 b | 37,473 b | | protobuf-javascript | 8 | 360,931 b | 270,573 b | 38,585 b | diff --git a/packages/bundle-size/chart.svg b/packages/bundle-size/chart.svg index 8d0cfef80..10f2e255e 100644 --- a/packages/bundle-size/chart.svg +++ b/packages/bundle-size/chart.svg @@ -43,14 +43,14 @@ 0 KiB - + Protobuf-ES -Protobuf-ES 15.52 KiB for 1 files -Protobuf-ES 16.2 KiB for 4 files -Protobuf-ES 16.73 KiB for 8 files -Protobuf-ES 18.99 KiB for 16 files -Protobuf-ES 24.34 KiB for 32 files +Protobuf-ES 15.57 KiB for 1 files +Protobuf-ES 16.23 KiB for 4 files +Protobuf-ES 16.78 KiB for 8 files +Protobuf-ES 19.03 KiB for 16 files +Protobuf-ES 24.37 KiB for 32 files diff --git a/packages/protobuf/src/wire/binary-encoding.ts b/packages/protobuf/src/wire/binary-encoding.ts index 1d0ed0122..4266910e3 100644 --- a/packages/protobuf/src/wire/binary-encoding.ts +++ b/packages/protobuf/src/wire/binary-encoding.ts @@ -386,24 +386,45 @@ export class BinaryReader { } /** - * Reads a tag - field number and wire type. Tags are uint32 varints; values - * that do not fit in uint32 are rejected. + * Reads a tag - field number and wire type. + * + * Inlined for the hot path. Tags are uint32 varints: at most 5 bytes, and + * the 5th byte can carry only 4 value bits (its upper 4 bits and the + * continuation bit must all be zero). Rejects overlong or overflowing + * varints. */ tag(): [number, WireType] { - const start = this.pos; - const tag = this.uint32(); - const bytesRead = this.pos - start; - if (bytesRead > 5 || (bytesRead == 5 && this.buf[this.pos - 1] > 0x0f)) { - throw new Error("illegal tag: varint overflows uint32"); + let b = this.buf[this.pos++]; + if ((b & 0x80) == 0) { + this.assertBounds(); + return parseTag(b); + } + let tag = b & 0x7f; + b = this.buf[this.pos++]; + tag |= (b & 0x7f) << 7; + if ((b & 0x80) == 0) { + this.assertBounds(); + return parseTag(tag); } - const fieldNo = tag >>> 3; - const wireType = tag & 7; - if (fieldNo <= 0 || wireType > 5) { - throw new Error( - "illegal tag: field no " + fieldNo + " wire type " + wireType, - ); + b = this.buf[this.pos++]; + tag |= (b & 0x7f) << 14; + if ((b & 0x80) == 0) { + this.assertBounds(); + return parseTag(tag); + } + b = this.buf[this.pos++]; + tag |= (b & 0x7f) << 21; + if ((b & 0x80) == 0) { + this.assertBounds(); + return parseTag(tag); + } + b = this.buf[this.pos++]; + if (b > 0x0f) { + throw new Error("illegal tag: varint overflows uint32"); } - return [fieldNo, wireType]; + tag = (tag | (b << 28)) >>> 0; + this.assertBounds(); + return parseTag(tag); } /** @@ -579,6 +600,21 @@ export class BinaryReader { } } +/** + * Split a decoded tag varint into field number and wire type, rejecting + * field number 0 and wire types outside 0-5. + */ +function parseTag(tag: number): [number, WireType] { + const fieldNo = tag >>> 3; + const wireType = tag & 7; + if (fieldNo <= 0 || wireType > 5) { + throw new Error( + "illegal tag: field no " + fieldNo + " wire type " + wireType, + ); + } + return [fieldNo, wireType]; +} + /** * Assert a valid signed protobuf 32-bit integer as a number or string. */ diff --git a/packages/protobuf/src/wire/varint.ts b/packages/protobuf/src/wire/varint.ts index 334f22056..3f5a13c5b 100644 --- a/packages/protobuf/src/wire/varint.ts +++ b/packages/protobuf/src/wire/varint.ts @@ -306,6 +306,10 @@ export function varint32write(value: number, bytes: number[]): void { /** * Read an unsigned 32 bit varint. * + * A uint32 value fits in 5 varint bytes, but this reader accepts up to 10 + * bytes and discards the extra high bits. Negative `int32` values encode as + * 10-byte varints and must decode back to the original 32-bit value. + * * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 */ export function varint32read(this: T): number {