Skip to content
Closed
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
4 changes: 4 additions & 0 deletions packages/h3blast/src/h3blast.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ static void die(const char *fmt, ...) {
exit(1);
}

// The lshpack objects report a failed allocation of their Huffman tables through this function
// (LS_HPACK_OOM in scripts/build/deps/lshpack.ts). Bun's runtime defines it, and this program links without that.
void Bun__outOfMemory(void) { die("out of memory"); }

static uint64_t now_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
Expand Down
146 changes: 0 additions & 146 deletions patches/lshpack/bss-huff-tables.patch

This file was deleted.

198 changes: 198 additions & 0 deletions patches/lshpack/lazy-huff-tables.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
--- a/lshpack.c 2026-10-28 13:31:41.835028034 +0000
+++ b/lshpack.c 2026-10-28 22:20:54.396188565 +0000
@@ -52,6 +52,133 @@

#include "huff-tables.h"

+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_LAZY_LARGE_TABLES
+/* Bun: hencs[65536] (512 KB) and hdecs[65536] (256 KB) are pure functions of
+ * encode_table[257]. They are left out of .rodata, and built once, at the
+ * first lshpack/lsqpack init, in memory that is allocated then (~250 us).
+ * A function that reads them loads their address once, into a local with
+ * the table's name, so its loops are those of a static table. As zero-filled
+ * data of the executable (.bss) they made it large enough for a transparent
+ * huge page.
+ * Non-static so lsqpack.c can use the same tables instead of carrying its
+ * own copy. */
+struct henc { unsigned lens; uint32_t code; };
+struct hdec { uint8_t lens; uint8_t out[3]; };
+struct lshpack_huff_tables { struct henc encs[65536]; struct hdec decs[65536]; };
+struct lshpack_huff_tables *lshpack_huff_tables;
+#ifdef LS_HPACK_OOM
+/* Reports out of memory and does not return. */
+void LS_HPACK_OOM(void);
+#endif
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define HENC_I(i,j) (((j)<<8)|(i))
+#else
+#define HENC_I(i,j) (((i)<<8)|(j))
+#endif
+
+/* Out of line: an init that finds the tables is a load and a test. */
+#if __GNUC__ || __clang__
+__attribute__((noinline))
+#endif
+static void
+build_huff_tables (void)
+{
+ struct lshpack_huff_tables *tables, *none = NULL;
+ struct { uint8_t bits, sym; } *lut;
+
+ /* Both start zeroed: an entry of decs that stays zero takes the slow path. */
+ tables = calloc(1, sizeof(*tables));
+ lut = calloc(1 << 15, sizeof(*lut));
+ if (!tables || !lut)
+ {
+#ifdef LS_HPACK_OOM
+ LS_HPACK_OOM();
+#endif
+ abort();
Comment thread
robobun marked this conversation as resolved.
Resolved
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
+ }
+
+ /* hencs: combine two encode_table entries. {64,0} marks pairs whose
+ * combined width overflows 32 bits (handled by the slow loop). */
+ for (unsigned i = 0; i < 256; ++i)
+ {
+ const unsigned bi = encode_table[i].bits;
+ const uint32_t ci = encode_table[i].code;
+ for (unsigned j = 0; j < 256; ++j)
+ {
+ const unsigned bj = encode_table[j].bits;
+ struct henc *h = &tables->encs[HENC_I(i, j)];
+ if (bi + bj <= 32)
+ {
+ h->lens = bi + bj;
+ h->code = (ci << bj) | encode_table[j].code;
+ }
+ else
+ {
+ h->lens = 64;
+ h->code = 0;
+ }
+ }
+ }
+
+ /* hdecs: for each 16-bit prefix, decode up to 3 symbols using a 15-bit
+ * direct LUT built from encode_table (codes <= 15 bits cover the common
+ * ASCII set; longer codes fall through to the slow path with lens==0). */
+ for (unsigned sym = 0; sym < 256; ++sym)
+ {
+ const unsigned b = encode_table[sym].bits;
+ if (b > 15)
+ continue;
+ const unsigned base = encode_table[sym].code << (15 - b);
+ for (unsigned k = 0; k < (1u << (15 - b)); ++k)
+ {
+ lut[base + k].bits = (uint8_t) b;
+ lut[base + k].sym = (uint8_t) sym;
+ }
+ }
+ for (unsigned idx = 0; idx < 65536; ++idx)
+ {
+ unsigned bits_left = 16, n = 0;
+ uint8_t out[3] = {0,0,0};
+ while (bits_left >= 5 && n < 3)
+ {
+ const unsigned rem = idx & ((1u << bits_left) - 1);
+ const unsigned key = bits_left >= 15
+ ? (rem >> (bits_left - 15))
+ : (rem << (15 - bits_left));
+ const unsigned b = lut[key].bits;
+ if (!b || b > bits_left)
+ break;
+ out[n++] = lut[key].sym;
+ bits_left -= b;
+ }
+ if (n)
+ {
+ tables->decs[idx].lens = (uint8_t)(((16 - bits_left) << 2) | n);
+ tables->decs[idx].out[0] = out[0];
+ tables->decs[idx].out[1] = out[1];
+ tables->decs[idx].out[2] = out[2];
+ }
+ /* else: leave zeroed — lens==0 triggers slow path */
+ }
+ free(lut);
+
+ /* Two threads can get here at once, and each has built the same tables.
+ * The first to publish keeps its own; a later encode/decode load on any
+ * thread comes after that thread's own call returned, so it sees them. */
+ if (!__atomic_compare_exchange_n(&lshpack_huff_tables, &none, tables, 0,
+ __ATOMIC_RELEASE, __ATOMIC_ACQUIRE))
+ free(tables);
+}
+
+void
+lshpack_huff_tables_init (void)
+{
+ if (!__atomic_load_n(&lshpack_huff_tables, __ATOMIC_ACQUIRE))
+ build_huff_tables();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
+}
+#endif
+
#define HPACK_STATIC_TABLE_SIZE 61
#define INITIAL_DYNAMIC_TABLE_SIZE 4096

@@ -290,6 +417,9 @@
int
lshpack_enc_init (struct lshpack_enc *enc)
{
+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_LAZY_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
struct lshpack_double_enc_head *buckets;
unsigned nbits = 2;
unsigned i;
@@ -712,6 +842,10 @@
#if LS_HPACK_USE_LARGE_TABLES
const struct henc *henc;
uint16_t idx;
+#if LS_HPACK_LAZY_LARGE_TABLES
+ const struct henc *const hencs =
+ __atomic_load_n(&lshpack_huff_tables, __ATOMIC_RELAXED)->encs;
+#endif

while (src + sizeof(bits) * 8 / 5 + sizeof(idx) < src_end
&& p_dst + sizeof(bits) <= dst_end)
@@ -1269,6 +1403,9 @@
void
lshpack_dec_init (struct lshpack_dec *dec)
{
+#if LS_HPACK_USE_LARGE_TABLES && LS_HPACK_LAZY_LARGE_TABLES
+ lshpack_huff_tables_init();
+#endif
memset(dec, 0, sizeof(*dec));
dec->hpd_max_capacity = INITIAL_DYNAMIC_TABLE_SIZE;
dec->hpd_cur_max_capacity = INITIAL_DYNAMIC_TABLE_SIZE;
@@ -1876,6 +2013,10 @@
struct hdec hdec;
uint16_t idx;
int r;
+#if LS_HPACK_LAZY_LARGE_TABLES
+ const struct hdec *const hdecs =
+ __atomic_load_n(&lshpack_huff_tables, __ATOMIC_RELAXED)->decs;
+#endif

#if __GNUC__
#pragma GCC diagnostic push
--- a/huff-tables.h 2026-10-28 13:31:41.843028034 +0000
+++ b/huff-tables.h 2026-10-28 13:32:33.559029924 +0000
@@ -266,7 +266,7 @@
};


-#if LS_HPACK_USE_LARGE_TABLES
+#if LS_HPACK_USE_LARGE_TABLES && !LS_HPACK_LAZY_LARGE_TABLES
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define I(i,j) ((j<<8)|i)
#else
@@ -70703,7 +70703,7 @@
};


-#if LS_HPACK_USE_LARGE_TABLES
+#if LS_HPACK_USE_LARGE_TABLES && !LS_HPACK_LAZY_LARGE_TABLES
static const struct hdec { uint8_t lens; uint8_t out[3]; } hdecs[] =
{
/*
Loading
Loading