Unescape HTML · Nim 🤝 Fable 5.2 · #1 Fastest
SHA-256
295b8143c22ebdb16dcdfdd8713a3ddcdb5f477be2c506155cae07e4a2087b54
View source (57.1 KiB of Nim)
# unescape_html in Nim with Wasm SIMD: decodes every HTML character reference in UTF-8 text
# the way Python's html.unescape does, and copies all other bytes unchanged.
#
# The design is the one from my C and WebAssembly text entries:
# - 64 zero bytes are written after the input, so no load or loop needs a bounds check.
# - Two stages per 4 KiB window. Stage 1 turns each 64-byte block into a bitmask of its `&`
# and appends their positions to a list. Stage 2 walks the list: copy the text since the
# last reference (32 bytes unconditionally, more in a loop), then decode.
# - Inputs without `&` are returned where they are; nothing is copied.
# - A name of up to 7 bytes ending in `;` is looked up without measuring it: the first `;`
# in the next 8 bytes is found with a SWAR zero-byte test, the bytes up to it are the key,
# and one multiply indexes an open-addressed table of 16-byte entries (key, value).
# - Longer names go through a second table and one masked 32-byte vector compare.
# - Without `;` only the 106 legacy names match. A filter on the first two bytes gives the
# longest legacy name that could start there, usually none.
# - Numeric references of up to 7 digits, decimal or hexadecimal, share one branch-free
# SWAR path.
# - Output and input share one buffer: output from its start, input 256 KiB in. A reference
# grows by at most 1.2x (`≫⃒` is 5 bytes in, 6 out), so the write position stays at
# least 51 KiB behind the read position and decoding in place is safe.
# - The entity list is stored packed (5.9 KB for 2,125 names) and unpacked on the first
# call: see `sym` and `init`. pack_mix.py, at the end of this file, is the matching packer.
#
# Build: see the end of this file.
{.push checks: off, stackTrace: off, lineTrace: off.}
const
InputCap = 1'u32 shl 20
# The largest growth is `≫⃒` (5 bytes) becoming 6 bytes.
OutputCap = InputCap div 5 * 6 + 8
InOff = 1'u32 shl 18
Pad = 128
FastBits = 12
FastTail = 32
LegBits = 8
LegTail = 16
LongBits = 11
Empty = not 0'u64
# Bytes of input per stage-1 window; a multiple of 64.
Window = 4096'u32
Ones = 0x0101010101010101'u64
High = 0x8080808080808080'u64
K = 1'u32 shl 20
const
HistSize = 16130
EntityCount = 2125
LongCount = 506
ModelBits = 17
ModelLimit = 3
MixShift = 11
MixW0 = 16384
FastMul = 0xa59d054b12f5b905'u64
LegMul = 0xf880d97d1854e551'u64
LongMul = 0xa0c2995f40498cb3'u64
# e^(2047 / 256) and e^(-1 / 256) as the bits of a float64.
SquashE0 = 0x40a732ac33de5528'u64
SquashStep = 0x3fefe00ffaabffbc'u64
# Step toward the bit just seen, as a 16-bit fraction: 2 / (2n + 3) after n uses.
rate: array[4, uint16] = [43690'u16, 26214'u16, 18724'u16, 14563'u16]
packed: array[5870, uint8] = [
222'u8, 225, 76, 127, 180, 26, 183, 40, 117, 111, 212, 1, 220, 243, 80, 19, 137, 147, 125, 129, 7, 7, 137, 16,
130, 158, 138, 125, 5, 234, 196, 106, 89, 69, 197, 243, 136, 86, 218, 152, 109, 108, 69, 33, 55, 71, 159, 88,
85, 51, 112, 51, 107, 141, 51, 22, 154, 180, 33, 140, 63, 154, 131, 152, 13, 94, 30, 228, 23, 251, 135, 236,
129, 186, 220, 6, 97, 11, 99, 33, 221, 217, 224, 75, 142, 69, 12, 159, 171, 25, 206, 254, 134, 114, 176, 175,
132, 228, 146, 96, 98, 30, 154, 222, 150, 71, 243, 66, 148, 174, 133, 30, 142, 131, 177, 159, 133, 130, 160, 123,
193, 128, 149, 204, 238, 192, 189, 17, 170, 157, 176, 106, 63, 181, 103, 100, 115, 245, 163, 25, 48, 120, 48, 82,
90, 172, 100, 47, 144, 202, 122, 72, 163, 117, 75, 124, 120, 117, 22, 7, 14, 170, 223, 96, 10, 146, 77, 173,
18, 191, 89, 4, 244, 115, 7, 79, 210, 9, 33, 56, 59, 215, 241, 153, 59, 36, 189, 73, 82, 224, 139, 90,
93, 19, 211, 248, 230, 217, 167, 18, 133, 137, 245, 198, 55, 115, 75, 158, 211, 214, 26, 9, 87, 209, 211, 4,
177, 236, 186, 241, 16, 10, 14, 67, 206, 150, 88, 249, 60, 167, 107, 104, 183, 1, 137, 162, 179, 3, 59, 221,
152, 48, 54, 68, 233, 158, 100, 183, 147, 198, 110, 208, 213, 22, 150, 10, 176, 18, 116, 190, 192, 78, 15, 48,
55, 236, 30, 217, 23, 70, 169, 50, 20, 82, 52, 164, 137, 115, 232, 112, 101, 176, 1, 67, 149, 92, 57, 181,
66, 17, 233, 11, 129, 139, 113, 178, 135, 47, 253, 143, 132, 85, 68, 186, 72, 3, 103, 150, 211, 8, 179, 168,
70, 203, 65, 169, 107, 64, 171, 34, 180, 33, 131, 16, 18, 136, 202, 27, 222, 53, 101, 51, 101, 248, 245, 158,
120, 74, 8, 80, 20, 145, 180, 26, 30, 60, 134, 122, 111, 45, 198, 61, 18, 171, 161, 154, 110, 66, 232, 199,
244, 123, 40, 192, 27, 47, 72, 141, 122, 210, 195, 57, 122, 193, 235, 51, 242, 98, 133, 36, 66, 231, 30, 152,
141, 128, 122, 49, 254, 46, 254, 29, 158, 140, 157, 104, 190, 7, 159, 239, 249, 30, 135, 181, 86, 112, 132, 137,
12, 161, 74, 143, 90, 113, 214, 94, 84, 174, 167, 164, 51, 12, 134, 76, 84, 208, 162, 167, 228, 152, 54, 217,
149, 137, 111, 213, 237, 235, 195, 114, 209, 114, 18, 152, 206, 97, 67, 74, 142, 199, 62, 128, 137, 60, 3, 38,
70, 140, 231, 253, 149, 133, 239, 165, 223, 61, 165, 184, 244, 24, 157, 223, 170, 85, 3, 191, 64, 75, 86, 23,
75, 120, 85, 159, 183, 50, 244, 27, 189, 1, 110, 61, 214, 183, 159, 85, 181, 194, 35, 12, 29, 30, 215, 239,
145, 231, 233, 219, 229, 61, 192, 194, 212, 164, 45, 127, 127, 201, 117, 178, 249, 165, 18, 46, 117, 145, 19, 48,
247, 250, 227, 61, 25, 22, 103, 32, 68, 206, 47, 236, 169, 202, 175, 228, 179, 213, 254, 111, 10, 62, 177, 47,
77, 103, 156, 229, 98, 75, 47, 226, 223, 136, 169, 10, 9, 71, 114, 20, 103, 190, 241, 119, 64, 253, 233, 243,
218, 81, 217, 215, 12, 105, 31, 29, 137, 211, 209, 148, 207, 223, 153, 70, 136, 242, 220, 103, 247, 161, 203, 217,
212, 126, 218, 98, 232, 107, 247, 116, 230, 194, 84, 137, 6, 65, 201, 239, 2, 42, 134, 114, 1, 141, 237, 208,
193, 126, 156, 218, 72, 226, 48, 249, 64, 246, 19, 44, 83, 105, 161, 79, 131, 144, 132, 90, 217, 234, 188, 193,
118, 55, 241, 15, 221, 5, 113, 10, 89, 208, 167, 156, 137, 242, 120, 178, 86, 177, 39, 165, 240, 64, 42, 217,
43, 5, 179, 28, 220, 244, 197, 135, 196, 131, 109, 185, 88, 158, 47, 125, 233, 98, 86, 13, 110, 112, 47, 47,
10, 71, 46, 7, 201, 184, 88, 61, 187, 201, 187, 209, 159, 93, 175, 132, 52, 115, 232, 1, 80, 109, 122, 159,
40, 42, 41, 241, 48, 14, 9, 168, 172, 16, 43, 235, 82, 34, 246, 61, 92, 150, 95, 200, 223, 24, 185, 156,
187, 170, 139, 27, 248, 230, 66, 157, 150, 44, 248, 160, 131, 147, 137, 156, 107, 181, 47, 13, 27, 69, 148, 78,
85, 182, 253, 66, 6, 23, 251, 35, 166, 230, 199, 154, 112, 3, 57, 160, 119, 137, 28, 136, 73, 191, 146, 66,
144, 168, 90, 27, 98, 153, 184, 94, 121, 124, 229, 18, 219, 227, 138, 175, 83, 236, 63, 217, 199, 223, 181, 138,
223, 213, 207, 98, 28, 31, 239, 237, 100, 75, 50, 131, 123, 122, 107, 70, 127, 125, 120, 119, 69, 176, 143, 200,
46, 88, 53, 216, 233, 182, 181, 157, 204, 132, 19, 222, 6, 199, 113, 223, 208, 84, 11, 129, 53, 39, 85, 186,
203, 216, 236, 241, 249, 53, 18, 204, 218, 166, 115, 248, 145, 198, 46, 206, 254, 174, 217, 232, 138, 92, 123, 120,
71, 48, 58, 72, 157, 6, 170, 203, 94, 173, 53, 195, 84, 15, 225, 240, 64, 220, 236, 204, 105, 54, 97, 109,
17, 73, 188, 216, 230, 42, 13, 126, 212, 248, 225, 216, 2, 3, 96, 141, 173, 92, 235, 176, 145, 62, 114, 173,
212, 21, 11, 227, 218, 13, 59, 33, 123, 220, 142, 130, 58, 197, 213, 118, 232, 190, 78, 35, 109, 249, 15, 248,
16, 236, 150, 185, 238, 155, 31, 222, 70, 254, 82, 244, 88, 149, 57, 16, 73, 8, 204, 72, 57, 236, 170, 54,
26, 184, 248, 155, 85, 42, 85, 204, 117, 124, 22, 129, 51, 71, 158, 255, 206, 26, 97, 178, 128, 246, 40, 217,
85, 194, 0, 166, 4, 119, 243, 33, 240, 155, 110, 206, 40, 164, 247, 74, 145, 55, 252, 114, 131, 33, 187, 127,
204, 42, 245, 20, 11, 24, 107, 200, 9, 21, 168, 165, 226, 210, 67, 186, 156, 112, 90, 140, 10, 228, 176, 168,
26, 170, 42, 201, 82, 171, 112, 111, 245, 233, 118, 235, 168, 27, 36, 52, 50, 0, 59, 12, 207, 40, 210, 111,
146, 243, 162, 129, 76, 136, 247, 122, 35, 184, 253, 173, 227, 29, 228, 185, 235, 128, 52, 243, 242, 236, 78, 59,
239, 195, 116, 252, 229, 116, 151, 85, 2, 1, 23, 247, 108, 6, 198, 139, 164, 24, 110, 0, 63, 51, 238, 37,
33, 24, 161, 238, 202, 87, 226, 31, 14, 9, 221, 31, 144, 44, 55, 234, 140, 17, 13, 153, 233, 45, 28, 50,
86, 185, 183, 181, 122, 108, 174, 12, 224, 81, 12, 207, 69, 124, 197, 114, 2, 81, 167, 104, 133, 241, 147, 234,
129, 130, 169, 161, 254, 89, 28, 0, 214, 48, 34, 97, 181, 246, 67, 135, 250, 120, 9, 43, 218, 52, 126, 45,
251, 58, 155, 240, 209, 154, 37, 216, 245, 152, 203, 1, 192, 255, 183, 144, 16, 198, 67, 78, 104, 252, 149, 214,
59, 222, 219, 84, 226, 85, 179, 241, 68, 67, 52, 125, 148, 156, 228, 88, 92, 188, 37, 42, 117, 160, 242, 156,
234, 153, 217, 242, 120, 244, 47, 115, 114, 150, 90, 177, 199, 30, 178, 56, 225, 251, 184, 103, 116, 115, 122, 163,
238, 7, 80, 203, 155, 205, 201, 151, 216, 154, 83, 237, 98, 89, 146, 41, 66, 250, 180, 123, 111, 87, 185, 30,
135, 179, 214, 53, 53, 49, 40, 182, 48, 229, 87, 133, 236, 42, 149, 15, 13, 45, 18, 128, 116, 61, 106, 142,
217, 86, 245, 243, 251, 219, 224, 75, 102, 16, 46, 80, 27, 157, 55, 27, 204, 224, 172, 4, 164, 194, 147, 218,
96, 158, 211, 122, 125, 169, 55, 43, 82, 252, 58, 154, 32, 114, 205, 76, 251, 0, 107, 216, 87, 65, 40, 207,
174, 212, 200, 62, 108, 187, 144, 162, 144, 33, 235, 174, 126, 89, 151, 233, 3, 236, 121, 107, 246, 222, 10, 108,
83, 250, 144, 37, 98, 3, 49, 236, 83, 101, 71, 120, 49, 206, 186, 242, 126, 199, 94, 28, 50, 151, 146, 231,
249, 112, 152, 25, 118, 184, 19, 27, 212, 17, 181, 108, 83, 125, 105, 173, 149, 114, 46, 131, 59, 63, 243, 48,
51, 91, 77, 177, 46, 63, 129, 239, 14, 153, 255, 94, 196, 45, 254, 105, 95, 71, 145, 23, 101, 28, 28, 43,
253, 71, 28, 203, 37, 96, 103, 233, 85, 127, 239, 37, 120, 244, 55, 121, 29, 192, 92, 51, 161, 203, 113, 148,
154, 133, 241, 132, 70, 121, 86, 81, 178, 32, 156, 142, 103, 87, 229, 150, 222, 186, 162, 252, 233, 243, 193, 18,
189, 53, 168, 91, 155, 234, 208, 215, 116, 89, 125, 232, 71, 187, 126, 244, 67, 93, 147, 95, 190, 106, 130, 124,
16, 65, 75, 43, 219, 206, 86, 36, 228, 214, 91, 4, 238, 223, 120, 80, 125, 100, 33, 92, 155, 217, 150, 31,
164, 5, 166, 234, 135, 57, 22, 227, 75, 56, 244, 89, 52, 39, 158, 67, 21, 71, 76, 235, 221, 112, 17, 242,
44, 235, 250, 78, 84, 191, 29, 87, 28, 16, 211, 178, 109, 159, 174, 68, 53, 253, 188, 51, 226, 93, 181, 80,
151, 239, 11, 63, 96, 243, 228, 67, 10, 92, 162, 52, 120, 159, 20, 105, 194, 155, 69, 68, 71, 119, 128, 152,
224, 242, 192, 20, 50, 139, 252, 90, 215, 30, 39, 193, 61, 230, 19, 99, 0, 193, 85, 44, 45, 242, 225, 148,
214, 8, 7, 98, 129, 55, 222, 177, 227, 3, 238, 125, 152, 238, 152, 252, 92, 106, 185, 142, 193, 153, 59, 123,
106, 117, 189, 78, 210, 53, 53, 89, 226, 255, 132, 242, 74, 60, 146, 223, 49, 237, 59, 243, 52, 164, 225, 146,
108, 91, 29, 57, 154, 199, 111, 17, 53, 20, 193, 60, 69, 151, 214, 232, 116, 160, 113, 120, 99, 137, 230, 175,
232, 162, 29, 8, 26, 164, 19, 164, 75, 14, 47, 78, 175, 232, 251, 58, 7, 47, 196, 183, 255, 149, 94, 234,
12, 177, 64, 200, 210, 61, 73, 17, 147, 211, 3, 49, 98, 252, 44, 17, 207, 23, 10, 133, 220, 164, 111, 204,
227, 141, 207, 182, 152, 20, 14, 242, 96, 12, 79, 106, 236, 191, 235, 66, 139, 214, 100, 146, 223, 171, 56, 69,
8, 98, 31, 111, 3, 237, 206, 41, 159, 182, 153, 32, 203, 231, 70, 118, 203, 193, 19, 176, 78, 187, 167, 86,
248, 118, 7, 221, 59, 106, 33, 78, 150, 5, 153, 20, 82, 165, 77, 114, 122, 66, 105, 147, 206, 176, 9, 243,
178, 52, 91, 165, 190, 24, 56, 43, 133, 95, 118, 45, 152, 140, 67, 250, 196, 173, 222, 60, 223, 184, 102, 56,
44, 197, 8, 168, 18, 114, 248, 160, 23, 217, 201, 103, 99, 92, 137, 128, 215, 19, 77, 242, 35, 250, 218, 122,
41, 124, 20, 5, 108, 127, 47, 240, 93, 255, 239, 121, 103, 144, 59, 114, 183, 173, 215, 163, 210, 112, 207, 235,
170, 22, 9, 100, 75, 4, 9, 150, 186, 137, 0, 215, 132, 129, 215, 79, 99, 173, 165, 223, 232, 23, 69, 125,
6, 77, 8, 201, 221, 148, 8, 156, 156, 232, 19, 204, 19, 119, 89, 214, 75, 183, 131, 6, 140, 16, 79, 24,
92, 175, 71, 162, 141, 143, 205, 182, 49, 136, 242, 241, 3, 205, 204, 69, 186, 181, 53, 126, 248, 239, 31, 97,
122, 161, 3, 116, 152, 209, 247, 61, 181, 45, 205, 187, 11, 175, 5, 32, 105, 45, 229, 97, 157, 219, 147, 22,
57, 64, 168, 224, 124, 10, 2, 27, 10, 198, 72, 133, 239, 113, 21, 98, 67, 37, 209, 153, 148, 229, 49, 158,
143, 48, 56, 231, 181, 119, 145, 224, 78, 1, 233, 90, 81, 85, 243, 8, 150, 158, 67, 169, 120, 90, 171, 104,
177, 169, 102, 92, 76, 165, 224, 241, 80, 98, 170, 164, 254, 19, 36, 80, 144, 155, 222, 254, 33, 74, 43, 238,
28, 177, 205, 15, 238, 160, 228, 228, 156, 179, 135, 251, 142, 167, 158, 9, 8, 191, 174, 223, 169, 234, 13, 205,
107, 133, 250, 78, 178, 66, 32, 1, 146, 7, 35, 102, 60, 31, 36, 93, 218, 194, 3, 3, 54, 3, 35, 143,
108, 117, 74, 125, 132, 149, 228, 88, 214, 172, 110, 165, 115, 237, 31, 253, 131, 128, 232, 114, 196, 151, 107, 108,
52, 189, 180, 86, 37, 221, 111, 31, 221, 98, 13, 185, 1, 226, 144, 68, 192, 134, 51, 241, 121, 208, 121, 63,
196, 139, 186, 98, 234, 110, 27, 47, 248, 58, 184, 99, 185, 22, 167, 174, 170, 107, 234, 59, 76, 3, 110, 205,
69, 201, 40, 212, 169, 88, 93, 104, 182, 59, 68, 82, 121, 182, 239, 196, 123, 252, 160, 32, 147, 101, 143, 225,
16, 72, 65, 216, 190, 235, 114, 240, 64, 230, 155, 228, 160, 165, 244, 38, 43, 237, 206, 149, 31, 170, 175, 179,
44, 162, 154, 201, 224, 250, 87, 173, 20, 108, 66, 10, 127, 154, 81, 189, 202, 126, 0, 61, 232, 189, 52, 105,
245, 153, 117, 165, 45, 242, 79, 141, 39, 97, 11, 18, 121, 16, 137, 59, 247, 56, 158, 143, 22, 6, 0, 59,
226, 48, 37, 243, 190, 180, 254, 79, 196, 225, 199, 56, 5, 94, 177, 222, 103, 11, 192, 72, 63, 137, 90, 250,
232, 179, 75, 47, 221, 108, 137, 197, 239, 31, 127, 154, 142, 239, 208, 249, 240, 51, 38, 246, 66, 213, 108, 60,
104, 220, 169, 76, 134, 214, 146, 233, 145, 234, 200, 14, 87, 87, 65, 193, 46, 10, 213, 45, 37, 172, 94, 119,
44, 7, 213, 151, 103, 171, 51, 7, 162, 78, 36, 178, 174, 115, 156, 192, 159, 244, 133, 161, 137, 136, 252, 139,
61, 159, 175, 67, 68, 43, 50, 62, 161, 32, 28, 74, 242, 139, 85, 33, 194, 1, 173, 45, 220, 67, 34, 176,
11, 108, 129, 133, 26, 105, 200, 68, 38, 129, 244, 168, 67, 223, 58, 103, 93, 242, 75, 181, 189, 116, 207, 159,
22, 48, 30, 73, 148, 85, 254, 2, 45, 144, 128, 170, 80, 94, 175, 210, 177, 22, 41, 168, 170, 26, 237, 5,
0, 148, 252, 132, 79, 204, 230, 179, 31, 223, 232, 37, 185, 23, 247, 140, 85, 202, 183, 92, 227, 81, 91, 107,
157, 1, 56, 158, 231, 139, 245, 113, 164, 203, 206, 230, 127, 179, 83, 223, 101, 226, 227, 222, 190, 37, 172, 75,
222, 21, 126, 107, 170, 87, 207, 139, 140, 179, 185, 83, 110, 230, 188, 248, 208, 82, 25, 211, 242, 160, 59, 157,
226, 0, 0, 18, 214, 140, 110, 191, 14, 121, 79, 94, 10, 196, 253, 10, 18, 232, 193, 176, 239, 183, 101, 172,
125, 240, 233, 155, 39, 52, 39, 228, 186, 146, 226, 10, 132, 90, 24, 0, 147, 141, 99, 172, 8, 175, 157, 243,
171, 223, 100, 175, 214, 149, 124, 121, 218, 233, 253, 251, 153, 189, 59, 227, 156, 100, 158, 126, 165, 10, 150, 192,
27, 153, 176, 152, 41, 32, 229, 92, 218, 171, 107, 38, 58, 193, 132, 77, 69, 147, 153, 105, 41, 123, 226, 87,
39, 148, 206, 185, 73, 249, 142, 149, 216, 82, 22, 126, 89, 221, 141, 188, 46, 73, 25, 122, 253, 80, 199, 12,
180, 179, 200, 141, 190, 80, 156, 36, 61, 168, 8, 129, 124, 67, 62, 123, 52, 87, 151, 234, 4, 59, 253, 94,
144, 62, 81, 42, 48, 214, 181, 15, 239, 161, 92, 209, 212, 162, 235, 43, 48, 231, 53, 253, 217, 110, 97, 188,
208, 250, 248, 59, 211, 28, 213, 73, 70, 199, 148, 67, 95, 29, 15, 30, 209, 70, 126, 181, 85, 245, 180, 26,
246, 230, 53, 39, 53, 49, 92, 14, 87, 39, 73, 140, 127, 155, 170, 1, 140, 204, 126, 133, 225, 137, 106, 187,
245, 70, 162, 52, 250, 88, 207, 14, 239, 83, 7, 66, 134, 229, 176, 139, 221, 78, 49, 30, 209, 120, 127, 67,
1, 134, 29, 146, 175, 160, 177, 141, 110, 189, 117, 191, 148, 109, 255, 70, 202, 252, 195, 111, 91, 67, 167, 90,
96, 8, 148, 215, 27, 63, 59, 4, 164, 212, 111, 101, 11, 249, 171, 96, 140, 196, 164, 32, 222, 0, 182, 148,
233, 195, 237, 38, 188, 107, 19, 151, 229, 108, 238, 144, 194, 119, 7, 221, 213, 36, 23, 107, 157, 177, 13, 242,
110, 59, 108, 115, 242, 151, 228, 227, 56, 78, 3, 218, 233, 227, 171, 127, 156, 78, 66, 175, 185, 124, 180, 240,
113, 193, 212, 97, 22, 245, 220, 33, 28, 161, 193, 36, 99, 27, 32, 249, 57, 169, 122, 102, 213, 209, 198, 151,
207, 171, 129, 38, 35, 105, 79, 230, 246, 166, 167, 5, 2, 139, 197, 11, 151, 121, 228, 104, 244, 247, 113, 57,
86, 87, 252, 141, 250, 138, 194, 101, 91, 242, 48, 238, 117, 138, 52, 188, 105, 141, 167, 206, 113, 8, 212, 176,
94, 131, 200, 101, 43, 92, 156, 6, 26, 170, 212, 133, 146, 141, 150, 34, 51, 253, 147, 98, 143, 75, 40, 199,
210, 34, 99, 155, 224, 90, 71, 129, 70, 211, 196, 21, 2, 66, 74, 179, 240, 11, 60, 178, 158, 197, 26, 6,
211, 98, 155, 47, 120, 216, 2, 206, 243, 165, 37, 227, 159, 171, 171, 213, 88, 244, 80, 253, 138, 208, 180, 237,
154, 31, 225, 88, 49, 160, 111, 90, 219, 141, 193, 220, 56, 9, 47, 46, 85, 254, 170, 193, 122, 17, 35, 230,
224, 193, 219, 60, 120, 204, 80, 200, 206, 205, 167, 45, 80, 222, 111, 95, 243, 23, 65, 194, 54, 41, 129, 25,
74, 49, 252, 233, 197, 77, 195, 78, 142, 219, 172, 73, 46, 238, 90, 215, 50, 86, 70, 159, 35, 188, 207, 171,
4, 89, 255, 249, 13, 217, 104, 16, 75, 96, 36, 230, 158, 42, 113, 165, 95, 144, 35, 213, 188, 88, 11, 19,
41, 99, 151, 62, 57, 118, 103, 130, 46, 176, 210, 145, 141, 142, 110, 174, 113, 159, 17, 162, 28, 48, 23, 250,
135, 218, 17, 254, 126, 39, 0, 69, 177, 54, 122, 57, 243, 45, 149, 149, 227, 3, 213, 51, 23, 25, 189, 105,
108, 238, 43, 103, 52, 71, 58, 72, 220, 229, 160, 90, 86, 184, 54, 207, 211, 233, 93, 79, 191, 71, 193, 129,
251, 138, 103, 144, 172, 6, 190, 118, 231, 129, 213, 103, 187, 224, 205, 97, 101, 19, 164, 66, 128, 53, 31, 188,
11, 194, 161, 251, 95, 218, 225, 85, 52, 82, 151, 81, 5, 191, 89, 243, 46, 98, 21, 0, 22, 43, 76, 1,
124, 230, 163, 71, 115, 19, 175, 137, 128, 164, 225, 111, 89, 253, 124, 172, 60, 54, 35, 72, 97, 194, 4, 8,
97, 84, 137, 141, 221, 140, 91, 191, 49, 27, 44, 66, 254, 11, 122, 161, 16, 134, 174, 200, 131, 95, 36, 146,
56, 189, 136, 144, 32, 168, 7, 97, 209, 222, 67, 255, 71, 174, 132, 254, 29, 239, 58, 175, 41, 161, 174, 48,
67, 194, 24, 193, 172, 183, 14, 127, 145, 32, 226, 75, 251, 146, 180, 205, 75, 183, 70, 107, 219, 43, 130, 212,
101, 238, 255, 140, 14, 102, 46, 183, 129, 225, 227, 156, 56, 7, 92, 67, 13, 4, 22, 103, 41, 28, 69, 170,
203, 36, 213, 22, 155, 23, 52, 84, 112, 22, 42, 1, 81, 32, 95, 98, 8, 59, 239, 61, 103, 183, 226, 5,
245, 19, 175, 246, 1, 20, 186, 177, 202, 97, 185, 217, 61, 254, 190, 220, 211, 144, 151, 39, 247, 62, 109, 151,
102, 40, 142, 99, 120, 233, 62, 244, 210, 221, 63, 174, 22, 34, 105, 72, 32, 103, 226, 194, 80, 31, 190, 201,
133, 40, 0, 13, 88, 99, 90, 203, 251, 10, 194, 111, 255, 100, 75, 211, 198, 70, 43, 226, 175, 2, 101, 74,
198, 104, 136, 67, 139, 123, 238, 109, 196, 103, 44, 243, 0, 120, 131, 170, 233, 68, 18, 124, 86, 175, 242, 204,
89, 139, 75, 146, 26, 167, 125, 176, 222, 36, 123, 81, 188, 96, 168, 151, 55, 231, 103, 108, 88, 231, 50, 5,
103, 1, 130, 97, 125, 228, 4, 236, 7, 162, 44, 190, 199, 163, 212, 60, 24, 244, 18, 155, 139, 114, 242, 243,
164, 238, 94, 62, 151, 63, 124, 87, 207, 169, 34, 91, 22, 73, 184, 18, 211, 31, 229, 74, 6, 66, 1, 159,
128, 31, 114, 248, 157, 0, 86, 5, 255, 80, 132, 76, 146, 115, 198, 127, 50, 154, 83, 139, 199, 130, 87, 153,
232, 133, 94, 162, 166, 235, 114, 108, 236, 13, 45, 17, 47, 71, 2, 241, 45, 183, 12, 29, 83, 88, 176, 171,
199, 26, 29, 250, 246, 159, 35, 216, 249, 169, 140, 195, 187, 57, 116, 244, 245, 166, 136, 55, 113, 2, 101, 112,
33, 187, 141, 200, 207, 116, 143, 71, 9, 78, 124, 103, 150, 236, 251, 119, 18, 247, 10, 58, 246, 244, 71, 151,
156, 151, 245, 56, 115, 69, 187, 138, 47, 97, 200, 238, 5, 111, 114, 119, 87, 142, 5, 220, 2, 232, 2, 196,
204, 91, 18, 72, 58, 88, 248, 52, 11, 141, 227, 77, 185, 131, 32, 115, 52, 8, 48, 10, 183, 218, 225, 202,
193, 202, 122, 152, 250, 16, 166, 12, 192, 41, 113, 3, 198, 44, 89, 247, 107, 86, 193, 162, 9, 152, 12, 197,
236, 37, 136, 222, 144, 234, 33, 55, 204, 218, 28, 109, 43, 34, 8, 194, 237, 52, 245, 11, 141, 173, 27, 56,
216, 63, 103, 81, 40, 226, 222, 180, 20, 147, 118, 218, 8, 223, 91, 109, 3, 169, 239, 244, 11, 58, 17, 201,
192, 56, 86, 84, 31, 90, 29, 200, 153, 240, 8, 65, 238, 14, 52, 246, 254, 85, 167, 168, 176, 206, 44, 153,
200, 122, 129, 230, 113, 103, 27, 177, 162, 146, 169, 91, 36, 188, 249, 157, 175, 9, 38, 234, 160, 161, 158, 39,
93, 188, 166, 254, 251, 216, 149, 162, 134, 219, 175, 111, 208, 164, 43, 87, 37, 109, 84, 6, 41, 170, 180, 219,
211, 64, 13, 119, 45, 232, 212, 45, 116, 114, 158, 210, 248, 108, 13, 243, 232, 106, 250, 34, 209, 169, 187, 25,
241, 69, 139, 15, 1, 53, 134, 77, 211, 239, 206, 181, 247, 143, 69, 182, 73, 248, 55, 129, 96, 55, 81, 171,
75, 115, 61, 219, 205, 169, 138, 46, 90, 249, 184, 241, 199, 235, 87, 53, 83, 243, 9, 108, 244, 157, 158, 36,
244, 67, 244, 89, 59, 169, 184, 209, 37, 184, 190, 141, 125, 4, 74, 9, 142, 206, 181, 252, 155, 43, 254, 15,
19, 124, 112, 104, 12, 90, 87, 44, 34, 103, 75, 64, 1, 84, 17, 214, 78, 167, 101, 149, 67, 97, 245, 186,
123, 125, 18, 252, 141, 246, 148, 233, 236, 187, 232, 170, 139, 224, 113, 162, 0, 43, 160, 9, 71, 69, 142, 123,
40, 250, 25, 187, 28, 29, 165, 241, 48, 42, 209, 185, 97, 35, 171, 45, 124, 96, 109, 251, 20, 181, 211, 176,
125, 115, 88, 31, 82, 245, 40, 103, 44, 208, 195, 220, 116, 150, 185, 6, 255, 56, 108, 232, 10, 105, 173, 242,
202, 36, 79, 97, 26, 117, 82, 13, 191, 105, 44, 174, 166, 252, 208, 230, 120, 131, 118, 193, 41, 100, 154, 136,
201, 96, 227, 7, 130, 203, 174, 160, 29, 220, 233, 148, 43, 230, 6, 86, 88, 85, 121, 71, 105, 49, 175, 139,
170, 59, 111, 108, 75, 22, 99, 42, 35, 250, 220, 0, 89, 18, 252, 102, 170, 142, 225, 44, 186, 45, 91, 37,
198, 210, 198, 245, 76, 60, 176, 223, 6, 103, 36, 17, 29, 243, 215, 45, 189, 203, 132, 221, 35, 57, 139, 203,
53, 223, 194, 35, 210, 173, 141, 27, 87, 115, 167, 236, 217, 93, 190, 99, 216, 149, 229, 220, 46, 114, 96, 78,
163, 138, 62, 133, 41, 15, 199, 190, 135, 36, 248, 196, 126, 127, 39, 100, 233, 223, 25, 181, 177, 227, 114, 41,
140, 238, 228, 99, 53, 17, 179, 200, 248, 22, 152, 126, 137, 190, 179, 182, 208, 179, 218, 39, 41, 151, 159, 216,
82, 159, 129, 31, 166, 17, 120, 200, 37, 191, 47, 244, 94, 132, 243, 1, 8, 197, 59, 80, 68, 170, 4, 167,
41, 15, 163, 250, 135, 178, 101, 235, 188, 160, 247, 97, 196, 91, 128, 107, 136, 26, 21, 64, 57, 75, 82, 117,
27, 70, 0, 100, 228, 94, 2, 213, 49, 253, 47, 249, 157, 51, 68, 90, 240, 25, 85, 194, 236, 11, 242, 210,
1, 208, 228, 99, 130, 210, 151, 198, 210, 36, 89, 196, 138, 147, 17, 180, 88, 97, 214, 142, 169, 11, 144, 214,
170, 210, 228, 189, 76, 103, 5, 254, 142, 250, 164, 215, 93, 229, 98, 26, 95, 110, 227, 2, 102, 254, 13, 183,
219, 72, 255, 240, 183, 158, 103, 225, 59, 96, 52, 159, 247, 160, 83, 187, 98, 125, 128, 199, 17, 38, 104, 211,
79, 37, 151, 96, 21, 254, 96, 19, 79, 254, 220, 213, 200, 86, 241, 120, 8, 149, 145, 248, 87, 132, 133, 86,
217, 192, 58, 166, 240, 91, 41, 9, 80, 209, 124, 149, 58, 31, 54, 79, 16, 83, 104, 191, 174, 235, 85, 152,
198, 209, 15, 174, 171, 164, 93, 138, 226, 164, 76, 119, 168, 46, 173, 194, 135, 70, 23, 34, 139, 60, 39, 244,
195, 103, 208, 233, 216, 250, 173, 141, 254, 129, 210, 125, 216, 97, 171, 112, 70, 127, 154, 15, 65, 74, 157, 136,
145, 34, 51, 134, 158, 121, 198, 241, 187, 149, 13, 250, 149, 129, 42, 152, 251, 239, 161, 140, 187, 203, 255, 167,
151, 136, 54, 221, 233, 209, 21, 77, 137, 206, 81, 174, 126, 124, 128, 64, 72, 60, 160, 65, 213, 131, 167, 38,
35, 19, 168, 22, 71, 188, 71, 75, 241, 195, 129, 10, 33, 238, 47, 153, 0, 167, 116, 125, 69, 129, 134, 23,
234, 108, 46, 140, 129, 46, 243, 83, 52, 241, 255, 70, 100, 116, 129, 72, 82, 242, 253, 246, 141, 42, 40, 162,
212, 225, 15, 72, 121, 58, 198, 214, 255, 116, 212, 241, 241, 83, 78, 110, 145, 88, 154, 57, 32, 106, 0, 62,
176, 169, 65, 168, 251, 244, 153, 80, 75, 238, 75, 203, 228, 152, 23, 21, 1, 172, 45, 64, 34, 218, 5, 163,
155, 248, 15, 106, 236, 111, 236, 166, 199, 82, 101, 142, 245, 36, 99, 100, 61, 23, 29, 190, 102, 106, 101, 162,
168, 93, 142, 111, 103, 129, 154, 252, 225, 62, 125, 226, 174, 2, 154, 173, 95, 9, 70, 192, 62, 231, 18, 142,
240, 153, 233, 128, 48, 168, 152, 33, 3, 225, 60, 7, 118, 217, 14, 17, 169, 65, 228, 97, 31, 97, 40, 149,
219, 157, 220, 66, 11, 82, 134, 81, 208, 197, 135, 136, 136, 127, 55, 196, 206, 230, 121, 162, 91, 134, 249, 1,
66, 21, 176, 113, 33, 182, 94, 145, 58, 61, 165, 213, 1, 141, 59, 40, 222, 199, 106, 23, 41, 132, 234, 191,
208, 38, 220, 125, 129, 155, 65, 170, 196, 204, 26, 115, 70, 45, 126, 97, 33, 193, 64, 30, 17, 208, 205, 49,
137, 108, 140, 215, 53, 11, 174, 62, 32, 40, 48, 161, 233, 215, 228, 46, 119, 41, 43, 252, 236, 152, 186, 141,
240, 4, 98, 255, 252, 109, 194, 173, 220, 127, 65, 37, 159, 208, 119, 197, 93, 112, 175, 166, 24, 8, 60, 50,
138, 75, 65, 28, 75, 214, 45, 173, 107, 8, 116, 54, 249, 141, 122, 136, 245, 206, 169, 59, 231, 215, 70, 115,
16, 146, 252, 239, 40, 93, 255, 75, 32, 47, 143, 204, 68, 198, 229, 21, 129, 25, 233, 92, 14, 219, 122, 5,
249, 154, 112, 58, 35, 3, 159, 42, 24, 186, 223, 163, 211, 16, 168, 134, 10, 81, 238, 209, 15, 142, 42, 160,
187, 74, 12, 251, 43, 80, 121, 94, 90, 155, 237, 189, 21, 55, 95, 170, 6, 39, 210, 249, 46, 37, 222, 231,
62, 43, 44, 95, 231, 206, 127, 225, 47, 157, 168, 32, 25, 213, 5, 102, 239, 46, 192, 150, 73, 157, 147, 54,
105, 210, 70, 30, 74, 24, 220, 1, 160, 249, 146, 9, 94, 242, 214, 134, 87, 95, 63, 134, 143, 64, 187, 244,
1, 211, 138, 93, 203, 16, 159, 143, 64, 53, 226, 14, 162, 195, 94, 41, 240, 252, 183, 58, 41, 206, 109, 82,
28, 107, 68, 155, 18, 48, 93, 253, 135, 123, 31, 208, 184, 191, 16, 51, 232, 242, 119, 125, 224, 24, 210, 162,
55, 201, 6, 100, 23, 156, 15, 106, 89, 56, 104, 33, 17, 82, 84, 44, 207, 30, 20, 216, 72, 124, 31, 149,
206, 76, 35, 205, 31, 144, 42, 25, 190, 96, 213, 143, 79, 255, 2, 46, 218, 54, 117, 26, 192, 204, 148, 110,
225, 61, 179, 34, 168, 91, 230, 238, 78, 78, 79, 249, 206, 196, 205, 85, 210, 144, 88, 89, 141, 255, 211, 205,
186, 122, 219, 167, 30, 149, 1, 176, 251, 151, 7, 72, 103, 244, 9, 204, 8, 94, 237, 23, 3, 233, 28, 215,
143, 83, 173, 196, 138, 235, 195, 191, 0, 19, 94, 195, 248, 223, 190, 115, 184, 79, 13, 1, 161, 172, 219, 37,
130, 27, 67, 196, 151, 124, 190, 251, 82, 99, 133, 15, 86, 90, 176, 58, 3, 246, 18, 184, 200, 101, 226, 254,
183, 142, 62, 129, 51, 159, 146, 113, 177, 68, 163, 27, 242, 2, 126, 183, 196, 8, 171, 227, 186, 67, 248, 243,
137, 60, 153, 168, 249, 60, 127, 106, 190, 231, 189, 222, 59, 35, 58, 92, 142, 177, 234, 28, 87, 143, 48, 172,
11, 65, 255, 66, 220, 191, 155, 90, 196, 139, 204, 72, 182, 151, 56, 167, 228, 65, 0, 40, 89, 86, 110, 13,
195, 252, 83, 157, 254, 98, 47, 148, 56, 152, 252, 93, 184, 190, 14, 53, 102, 30, 197, 56, 161, 156, 137, 198,
115, 146, 87, 147, 215, 216, 68, 103, 153, 183, 147, 123, 19, 185, 46, 233, 195, 80, 242, 79, 127, 102, 12, 238,
228, 226, 155, 180, 131, 86, 105, 251, 178, 84, 211, 244, 214, 118, 240, 44, 26, 15, 188, 22, 157, 44, 144, 127,
223, 95, 235, 19, 108, 15, 134, 7, 37, 6, 132, 16, 168, 216, 136, 184, 197, 30, 129, 16, 237, 131, 225, 141,
180, 121, 108, 211, 15, 54, 74, 73, 113, 185, 165, 243, 213, 148, 22, 2, 82, 39, 84, 39, 69, 0, 225, 169,
26, 58, 173, 27, 99, 127, 75, 143, 161, 245, 209, 26, 5, 61, 177, 26, 11, 26, 241, 169, 140, 90, 16, 220,
137, 185, 244, 143, 105, 174, 97, 205, 237, 253, 185, 133, 160, 107, 143, 12, 164, 14, 142, 72, 218, 212, 33, 58,
210, 43, 244, 235, 132, 114, 127, 83, 177, 12, 132, 171, 45, 230, 14, 118, 119, 201, 251, 227, 230, 91, 144, 220,
25, 97, 30, 81, 67, 222, 128, 172, 6, 229, 63, 115, 201, 57, 216, 136, 196, 164, 31, 182, 70, 190, 70, 38,
254, 179, 146, 147, 231, 27, 130, 137, 190, 243, 199, 136, 138, 51, 221, 237, 196, 151, 108, 133, 110, 112, 223, 1,
7, 222, 213, 135, 12, 157, 158, 140, 235, 115, 221, 197, 65, 35, 143, 246, 48, 98, 6, 100, 123, 183, 121, 170,
52, 175, 22, 96, 19, 118, 64, 58, 225, 100, 137, 213, 213, 58, 44, 202, 205, 196, 205, 241, 173, 214, 218, 163,
52, 50, 106, 255, 176, 69, 176, 133, 192, 68, 176, 178, 15, 194, 63, 23, 44, 3, 61, 243, 136, 197, 139, 231,
219, 220, 32, 48, 184, 175, 26, 1, 150, 125, 149, 255, 249, 40, 40, 118, 135, 169, 41, 108, 222, 4, 14, 198,
51, 92, 219, 216, 123, 64, 123, 167, 187, 158, 87, 7, 121, 212, 200, 20, 3, 90, 50, 105, 79, 155, 95, 188,
18, 67, 127, 222, 251, 49, 147, 172, 165, 236, 232, 134, 26, 7, 226, 225, 194, 74, 79, 247, 196, 199, 226, 97,
80, 244, 117, 228, 158, 53, 10, 13, 19, 72, 200, 65, 254, 48, 95, 179, 115, 70, 122, 0, 22, 113, 140, 196,
107, 128, 157, 152, 78, 157, 30, 85, 105, 78, 3, 183, 73, 20, 124, 122, 192, 161, 74, 207, 232, 48, 73, 80,
179, 169, 164, 89, 21, 153, 106, 8, 27, 67, 148, 36, 41, 152, 195, 171, 30, 227, 56, 23, 230, 109, 160, 20,
226, 195, 254, 226, 196, 103, 52, 167, 200, 90, 214, 139, 97, 106, 161, 218, 173, 3, 86, 61, 123, 222, 0, 51,
59, 20, 178, 73, 52, 155, 114, 69, 142, 140, 9, 225, 120, 153, 39, 124, 215, 201, 118, 47, 224, 23, 59, 161,
105, 60, 172, 105, 13, 210, 11, 219, 21, 100, 231, 72, 121, 19, 142, 184, 4, 228, 89, 104, 242, 77, 74, 27,
215, 211, 29, 237, 216, 55, 25, 60, 63, 174, 128, 137, 0, 40, 130, 183, 189, 250, 90, 173, 149, 162, 136, 25,
64, 26, 73, 62, 77, 74, 255, 10, 8, 55, 204, 131, 202, 91, 79, 3, 68, 154, 16, 246, 202, 239, 138, 67,
112, 158, 94, 172, 203, 198, 150, 45, 1, 42, 190, 171, 255, 149, 141, 41, 123, 149, 63, 182, 55, 3, 180, 48,
209, 57, 115, 31, 188, 173, 190, 193, 34, 211, 63, 2, 79, 175, 248, 14, 39, 218, 104, 117, 238, 4, 110, 90,
20, 213, 187, 87, 11, 35, 45, 77, 42, 40, 12, 60, 72, 84, 146, 11, 69, 195, 124, 148, 244, 56, 82, 215,
122, 163, 57, 247, 251, 218, 245, 124, 136, 220, 190, 171, 97, 127, 193, 215, 90, 221, 222, 165, 71, 194, 251, 243,
224, 75, 66, 101, 39, 122, 220, 160, 243, 11, 172, 163, 166, 148,
]
type
# The value's UTF-8 bytes in the low bytes and its length in the top byte.
Entry = object
key, val: uint64
Long = object
name: array[32, uint8]
val: uint64
len: uint32
pad: uint32
V128 {.importc: "v128_t", header: "<wasm_simd128.h>", bycopy.} = object
Bytes = ptr UncheckedArray[uint8]
proc vload(p: pointer): V128 {.importc: "wasm_v128_load", header: "<wasm_simd128.h>".}
proc vstore(p: pointer, v: V128) {.importc: "wasm_v128_store", header: "<wasm_simd128.h>".}
proc splat(x: int8): V128 {.importc: "wasm_i8x16_splat", header: "<wasm_simd128.h>".}
proc eq8(a, b: V128): V128 {.importc: "wasm_i8x16_eq", header: "<wasm_simd128.h>".}
proc bitmask(a: V128): uint32 {.importc: "wasm_i8x16_bitmask", header: "<wasm_simd128.h>".}
proc vand(a, b: V128): V128 {.importc: "wasm_v128_and", header: "<wasm_simd128.h>".}
proc vor(a, b: V128): V128 {.importc: "wasm_v128_or", header: "<wasm_simd128.h>".}
proc vxor(a, b: V128): V128 {.importc: "wasm_v128_xor", header: "<wasm_simd128.h>".}
proc anyTrue(a: V128): bool {.importc: "wasm_v128_any_true", header: "<wasm_simd128.h>".}
proc ctz64(x: uint64): cint {.importc: "__builtin_ctzll", nodecl.}
proc ctz32(x: uint32): cint {.importc: "__builtin_ctz", nodecl.}
proc popcount64(x: uint64): cint {.importc: "__builtin_popcountll", nodecl.}
proc fill(p: pointer, c: cint, n: csize_t) {.importc: "__builtin_memset", nodecl.}
proc copy(d, s: pointer, n: csize_t) {.importc: "__builtin_memcpy", nodecl.}
var
# One buffer: output from its start, input InOff bytes in. On the first call its start
# also holds the unpacker's model, which is why the model has 2^17 16-bit entries.
buf {.align: 64.}: array[InOff + InputCap + Pad, uint8]
fast: array[(1 shl FastBits) + FastTail, Entry]
leg: array[(1 shl LegBits) + LegTail, Entry]
longs: array[LongCount, Long]
longSlots: array[1 shl LongBits, uint16]
# Longest legacy name starting with these two bytes (hashed), or 0.
legMax: array[4096, uint8]
pos: array[Window + 16, uint32]
# Byte j of the 32 loaded at lenMask + 32 - n is 0xFF when j < n.
lenMask: array[64, uint8]
ready: bool
const
# Windows-1252 remapping for references to 0x80-0x9F.
c1: array[32, uint16] = [
0x20AC'u16, 0x81, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x8D, 0x017D, 0x8F,
0x90, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x9D, 0x017E, 0x0178]
utf8Marks: array[5, uint32] = [0'u32, 0, 0x80C0, 0x8080E0, 0x808080F0'u32]
template input: Bytes = cast[Bytes](addr buf[InOff])
template output: Bytes = cast[Bytes](addr buf[0])
template ld64(p: pointer): uint64 = cast[ptr uint64](p)[]
template st64(p: pointer, x: uint64) = cast[ptr uint64](p)[] = x
template st32(p: pointer, x: uint32) = cast[ptr uint32](p)[] = x
template at(p: Bytes, i: uint32): pointer = cast[pointer](addr p[i])
# A constant offset is applied to the pointer, not the index, so that it folds into the
# load or store instruction.
template at(p: Bytes, i: uint32, k: int): pointer = cast[pointer](addr cast[Bytes](addr p[i])[k])
template legSlot(w: uint64): uint32 = ((uint32(w and 0xFFFF)) * 0x9E3779B1'u32) shr 20
template rotl(x: uint64, r: int): uint64 = (x shl r) or (x shr (64 - r))
template longHash(head, tail: uint64, n: uint32): uint32 =
uint32((((head xor rotl(tail, 29)) + n) * LongMul) shr (64 - LongBits))
# Lay the bits of a code point out as four UTF-8 bytes, then drop the unused leading ones.
template utf8(v: uint32, len: var uint32): uint32 =
let n = 1'u32 + uint32(v > 0x7F) + uint32(v > 0x7FF) + uint32(v > 0xFFFF)
let bits = (v shr 18) or ((v shr 12 and 0x3F) shl 8) or ((v shr 6 and 0x3F) shl 16) or ((v and 0x3F) shl 24)
len = n
if n == 1: v else: (bits shr (32 - 8 * n)) or utf8Marks[n]
# The entity list is stored packed and read once, on the first call. Entities are ordered
# by code point, so a value is a small step from the one before. A name is coded character
# by character with an adaptive binary range coder (the LZMA one) from six contexts: the
# previous one, two and three characters; a match model that predicts the character that
# followed the last place the previous two were seen; and two that look at the same
# position in the previous entity's name, since neighbours in code point order are often
# named alike (Afr, Bfr; Acy, Bcy; Aacute, Acirc).
#
# Each context has a model entry of 16 bits: a 12-bit probability (stored XOR 0x800, so
# zeroed memory means one half) above a 4-bit count of its uses. The entries' probabilities
# are combined by logistic mixing, as in the PAQ compressors:
# p = squash(sum of weight * stretch(p_i)), stretch(p) = ln(p / (1 - p)), and after the bit
# is known each weight moves by error * stretch(p_i).
var
squash: array[4095, uint16]
stretch: array[4096, int16]
# Five sets of six weights, 16.16 fixed point, stored less MixW0.
weights: array[5, array[6, int32]]
ctx: array[6, uint32]
hist: array[HistSize, uint8]
last: array[4096, uint16]
rcIn, rcRange, rcCode: uint32
template model: ptr UncheckedArray[uint16] = cast[ptr UncheckedArray[uint16]](addr buf[0])
proc utf8Once(v: uint32, len: var uint32): uint32 {.noinline.} = utf8(v, len)
proc sym(ws, nctx, nbits: uint32): uint32 {.noinline.} =
var node = 1'u32
let top = 1'u32 shl nbits
let w = addr weights[ws]
while true:
var slot {.noinit.}: array[6, uint32]
var st {.noinit.}: array[6, int32]
var dot = 0'i32
for k in 0'u32 ..< nctx:
let s = ((ctx[k] + node * 0x2545F491'u32) * 0x9E3779B1'u32) shr (32 - ModelBits)
slot[k] = s
st[k] = int32(stretch[(model[s] shr 4) xor 0x800])
dot += st[k] * (w[k] + MixW0)
dot = ashr(dot, 16) + 2047
if dot < 0: dot = 0
if dot > 4094: dot = 4094
# 12-bit probability that the bit is 1, from 1 to 4095.
let p = uint32(squash[dot])
let bound = (rcRange shr 12) * p
let bit = uint32(rcCode < bound)
if bit != 0:
rcRange = bound
else:
rcCode -= bound
rcRange -= bound
while rcRange < (1'u32 shl 24):
rcRange = rcRange shl 8
rcCode = (rcCode shl 8) or packed[rcIn]
rcIn += 1
# Move each weight by error * input, and each entry toward the bit just seen by
# 2 / (2n + 3) after n uses.
let one = int32(bit shl 12)
let err = one - int32(p)
for k in 0'u32 ..< nctx:
w[k] += ashr(st[k] * err, MixShift)
let e = uint32(model[slot[k]])
var q = int32((e shr 4) xor 0x800)
let n = e and 15
q += ashr((one - q) * int32(rate[n]), 16)
model[slot[k]] = uint16(((uint32(q) xor 0x800) shl 4) or (n + uint32(n < ModelLimit)))
node = node * 2 + bit
if node >= top: break
node - top
proc init() {.noinline.} =
fill(addr fast, 255, csize_t(sizeof(fast)))
fill(addr leg, 255, csize_t(sizeof(leg)))
fill(addr lenMask, 255, 32)
# squash[x + 2047] = 4096 / (1 + e^(-x / 256)) rounded, for x = -2047..2047, and stretch,
# its inverse, filled in as squash rises. Only IEEE multiply, add and divide are used,
# so the tables are the same bits the packer had.
var e = cast[float64](SquashE0)
var pi = 0'u32
for x in 0'u32 ..< 4095:
let v = (uint32(8192.0 / (1.0 + e)) + 1) shr 1
squash[x] = uint16(v)
while pi <= v:
stretch[pi] = int16(int32(x) - 2047)
pi += 1
e = e * cast[float64](SquashStep)
rcIn = 4
rcRange = not 0'u32
rcCode = (uint32(packed[0]) shl 24) or (uint32(packed[1]) shl 16) or (uint32(packed[2]) shl 8) or packed[3]
var cp, nl, start = 0'u32
var hn = 1'u32
for r in 0 ..< EntityCount:
# Step from the previous code point: its bit length, then the bits below the top one.
ctx[0] = K
let nb = sym(4, 1, 5)
var d = nb
if nb > 1:
ctx[0] = 2 * K + nb
d = (1'u32 shl (nb - 1)) + sym(4, 1, nb - 1)
cp += d
var vn, vn2 = 0'u32
var val = uint64(utf8Once(cp, vn))
# A few values have a second code point.
ctx[0] = 3 * K + uint32(d == 0)
if sym(4, 1, 1) != 0:
ctx[0] = 4 * K
val = val or (uint64(utf8Once(sym(4, 1, 16), vn2)) shl (8 * vn))
val = val or (uint64(vn + vn2) shl 56)
ctx[0] = 5 * K + uint32(cp < 256)
let legacy = sym(4, 1, 1)
# Names use a 6-bit alphabet: 0 ends the name, then digits, capitals, small letters.
var name {.noinit.}: array[40, uint8]
fill(addr name, 0, 40)
var n, h, mp, ml = 0'u32
let prev = start
start = hn
while true:
let m3 = min(ml, 3)
# The character at this position in the previous entity's name (or, past its end,
# whatever follows it in hist).
let pc = uint32(hist[prev + n]) shl 6
ctx[0] = 8 * K + (h and 63)
ctx[1] = 9 * K + (h and 4095)
ctx[2] = 13 * K + (if ml != 0: uint32(hist[mp]) else: 0'u32) * 16 + m3
ctx[3] = 10 * K + (h and 0x3FFFF)
ctx[4] = 14 * K + pc + (h and 63)
ctx[5] = 17 * K + pc + min(n, 3) * 4 + min(d, 3)
# One set of weights for each state of the match model; set 4 is for the fields above.
let c = sym(m3, 6, 6)
if ml != 0 and hist[mp] == c:
ml += 1
mp += 1
else:
ml = 0
hist[hn] = uint8(c)
hn += 1
if c == 0: break
name[n] = uint8(c + (if c < 11: 47'u32 elif c < 37: 54'u32 else: 60'u32))
n += 1
h = (h shl 6) or c
if n >= 2:
let l = addr last[h and 4095]
if ml == 0 and l[] != 0:
mp = l[]
ml = 1
l[] = uint16(hn)
if n <= 7:
# fast: the key is the name and its `;`.
var key = ld64(addr name) or (59'u64 shl (8 * n))
var e = uint32((key * FastMul) shr (64 - FastBits))
while fast[e].key != Empty: e += 1
fast[e].key = key
fast[e].val = val
if legacy != 0:
# leg: the length is part of the key, so a NUL after a shorter name cannot match.
key = ld64(addr name) or (uint64(n) shl 56)
e = uint32((key * LegMul) shr (64 - LegBits))
while leg[e].key != Empty: e += 1
leg[e].key = key
leg[e].val = val
let s = legSlot(key) # only the first two bytes matter
if legMax[s] < n: legMax[s] = uint8(n)
else:
copy(addr longs[nl].name, addr name, 32)
longs[nl].val = val
longs[nl].len = n
nl += 1
var hh = longHash(ld64(addr name), ld64(addr name[n - 8]), n)
while longSlots[hh] != 0: hh = (hh + 1) and ((1 shl LongBits) - 1)
longSlots[hh] = uint16(nl)
ready = true
proc input_ptr(): uint32 {.exportc.} = cast[uint32](addr buf[InOff])
proc input_utf8_cap(): uint32 {.exportc.} = InputCap
proc output_utf8_cap(): uint32 {.exportc.} = OutputCap
proc render(n: uint32): uint64 {.exportc.} =
if unlikely(not ready): init()
let size = min(n, InputCap)
let src = input
let dst = output
let zero = splat(0)
vstore(src.at(size), zero)
vstore(src.at(size, 16), zero)
vstore(src.at(size, 32), zero)
vstore(src.at(size, 48), zero)
# Stage 1 lists the position of every `&` in a window; stage 2 copies the text between
# references and decodes each one. Knowing the next position before the current
# reference is decoded keeps the references independent of each other.
var e, o, base = 0'u32
let amp = splat(38)
let semi = splat(59)
while base < size:
let lim = if size - base > Window: base + Window else: size
var count = 0'u32
while true:
var mm = uint64(bitmask(eq8(vload(src.at(base)), amp))) or
(uint64(bitmask(eq8(vload(src.at(base, 16)), amp))) shl 16) or
(uint64(bitmask(eq8(vload(src.at(base, 32)), amp))) shl 32) or
(uint64(bitmask(eq8(vload(src.at(base, 48)), amp))) shl 48)
let cnt = uint32(popcount64(mm))
let outp = cast[ptr UncheckedArray[uint32]](addr pos[count])
# Entries past cnt are garbage and are overwritten or ignored.
outp[0] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[1] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[2] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[3] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
if unlikely(cnt > 4):
outp[4] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[5] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[6] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
outp[7] = base + uint32(ctz64(mm)); mm = mm and (mm - 1)
var k = 8'u32
while unlikely(mm != 0):
outp[k] = base + uint32(ctz64(mm))
mm = mm and (mm - 1)
k += 1
count += cnt
base += 64
if base >= lim: break
for q in 0'u32 ..< count:
var i = pos[q]
# Output and input are one buffer, so the compiler cannot move a load across a store.
# Everything this reference needs is therefore loaded before the first store.
let w = ld64(src.at(i, 1))
# Copy the text since the last reference; src[i] is `&` and i < size.
block:
let len = i - e
let ca = vload(src.at(e))
let cb = vload(src.at(e, 16))
vstore(dst.at(o), ca)
vstore(dst.at(o, 16), cb)
if len > 32:
var j = 32'u32
while true:
vstore(dst.at(o + j), vload(src.at(e + j)))
j += 16
if j >= len: break
o += len
block done:
block bare:
if uint8(w and 0xFF) == 35:
# Decimal and hexadecimal share one branch-free path: classify 8 bytes, take
# the digits before the first other byte, and fold them pairwise.
let hex = ((uint32(w shr 8) and 0xFF) or 0x20) == 120
var p = i + 2 + uint32(hex)
let d = ld64(src.at(p))
let t = d xor 0x3030303030303030'u64
let digit = not (((t and not High) + 0x7676767676767676'u64) or t) and High
let y = d or 0x2020202020202020'u64
let ty = y and not High
let letter = (ty + Ones * (0x80 - 97)) and not (ty + Ones * (0x7F - 102)) and not y and High
let stop = not (digit or (if hex: letter else: 0'u64)) and High
var v, last: uint32
if likely(stop != 0):
let nb = uint32(ctz64(stop)) and 0x38
if unlikely(nb == 0): break bare
var x = ((d and 0x0F0F0F0F0F0F0F0F'u64) + 9 * (letter shr 7)) shl (64 - nb)
x = (x * (if hex: 16'u64 else: 10'u64) + (x shr 8)) and 0x00FF00FF00FF00FF'u64
x = (x * (if hex: 256'u64 else: 100'u64) + (x shr 16)) and 0x0000FFFF0000FFFF'u64
v = uint32((x * (if hex: 65536'u64 else: 10000'u64) + (x shr 32)) and 0xFFFFFFFF'u64)
last = p + (nb shr 3)
else:
# Eight or more digits: saturate past U+10FFFF.
let radix = if hex: 16'u32 else: 10'u32
v = 0
while true:
var c = uint32(src[p]) - 48
if c > 9:
c = ((uint32(src[p]) or 0x20) - 97) + 10
if c < 10: c = 16
if c >= radix: break
v = v * radix + c
if v > 0x10FFFF: v = 0x110000
p += 1
last = p
i = last + uint32(src[last] == 59)
if unlikely(v - 0x80 < 0x20): v = c1[v - 0x80]
elif unlikely(v == 0 or v - 0xD800 < 0x800 or v > 0x10FFFF): v = 0xFFFD
var len: uint32
let word = utf8(v, len)
st32(dst.at(o), word)
o += len
break done
let t = w xor (Ones * 59)
let z = (t - Ones) and not t and High
if likely(z != 0):
# Bit index of the `;` byte; the key is the name and its `;`.
let kb = uint32(ctz64(z)) and 0x38
let key = w and ((2'u64 shl (kb + 7)) - 1)
# Each probe loop only finds the slot; acting on it afterwards keeps the loop
# free of exits into the code after the reference.
var s = uint32((key * FastMul) shr (64 - FastBits))
var k = fast[s].key
while unlikely(k != key and k != Empty):
s += 1
k = fast[s].key
if likely(k == key):
let val = fast[s].val
st64(dst.at(o), val)
o += uint32(val shr 56)
i += 2 + (kb shr 3)
break done
else:
# No `;` in 8 bytes: a name of 8 to 31 bytes needs its `;` in the next 24.
let s0 = vload(src.at(i, 1))
let s1 = vload(src.at(i, 17))
let sm = bitmask(eq8(s0, semi)) or (bitmask(eq8(s1, semi)) shl 16)
if sm != 0:
let len = uint32(ctz32(sm))
var h = longHash(w, ld64(src.at(i + len, -7)), len)
let m0 = vload(addr lenMask[32 - len])
let m1 = vload(addr lenMask[48 - len])
var found = 0'u64
while true:
let s = longSlots[h]
if s == 0: break
let l = addr longs[s - 1]
let diff = vor(vxor(vand(s0, m0), vload(addr l.name)), vxor(vand(s1, m1), vload(addr l.name[16])))
if l.len == len and not anyTrue(diff):
found = l.val
break
h = (h + 1) and ((1 shl LongBits) - 1)
if found != 0:
st64(dst.at(o), found)
o += uint32(found shr 56)
i += 2 + len
break done
# Without `;`, the longest legacy name that is a prefix.
var k = uint32(legMax[legSlot(w)])
var found = 0'u64
while k >= 2:
# The length is part of the key, so a NUL after a shorter name cannot match.
let key = (w and ((1'u64 shl (8 * k)) - 1)) or (uint64(k) shl 56)
var s = uint32((key * LegMul) shr (64 - LegBits))
while leg[s].key != key and leg[s].key != Empty: s += 1
if leg[s].key == key:
found = leg[s].val
break
k -= 1
if found != 0:
st64(dst.at(o), found)
o += uint32(found shr 56)
i += 1 + k
break done
# A bare `&`.
dst[o] = 38
o += 1
i += 1
e = i
# Without a reference the input is the output.
if e == 0: return (uint64(cast[uint32](addr buf[InOff])) shl 32) or size
var j = e
while j < size:
vstore(dst.at(o + (j - e)), vload(src.at(j)))
j += 16
o += size - e
(uint64(cast[uint32](addr buf[0])) shl 32) or o
{.pop.}
#[ Build (build-nim.sh):
#!/bin/sh
# Generates unescape.nim (the single-file source to submit) from unescape.in.nim and builds
# unescape-nim.wasm: Nim 2.4.0 translates to C, zig cc (clang) compiles that to Wasm.
set -eu
cd "$(dirname "$0")"
python3 gen-small.py unescape.in.nim unescape.nim 17
NIM="mise exec nim@2.4.0 --"
# --os:standalone asks for this file. Checks are off, so neither proc is ever called.
cat > panicoverride.nim <<'PANIC'
proc rawoutput(s: string) = discard
proc panic(s: string) {.noreturn.} =
while true: discard
PANIC
C="${TMPDIR:-/tmp}/unescape-nimcache"
rm -rf "$C"
$NIM nim c --compileOnly --nimcache:"$C" --cpu:wasm32 --os:standalone --mm:none --threads:off \
-d:danger -d:noSignalHandler --noMain --app:staticlib unescape.nim
LIB="$(dirname "$($NIM which nim)")/../lib"
zig cc -target wasm32-freestanding -O3 -msimd128 -mbulk-memory -nostdlib -fno-strict-aliasing -I"$LIB" \
-Wl,--no-entry -Wl,--export-memory -Wl,--export=input_ptr -Wl,--export=input_utf8_cap \
-Wl,--export=output_utf8_cap -Wl,--export=render \
-Wl,--initial-memory=1507328 -Wl,--max-memory=1507328 -Wl,-z,stack-size=4096 -Wl,--strip-all \
-o unescape-nim.wasm "$C"/*.c
ls -l unescape-nim.wasm
pack_mix.py, which produced `packed`:
"""Packs the entity list for unescape.wat: entities ordered by code point, coded with an
adaptive binary range coder. Each bit is predicted from several contexts whose predictions
are combined by a small learned mixer (logistic mixing, as in the PAQ compressors).
$sym and $init in unescape.wat mirror Model, Decoder and Coder exactly.
"""
import html.entities
M32 = 0xFFFFFFFF
K = 1 << 20
HASH_A, HASH_B = 0x2545F491, 0x9E3779B1
# Step toward the bit just seen, as a 16-bit fraction: 2 / (2n + 3) after n uses.
RATE = [43690, 26214, 18724, 14563]
# e^(2047 / 256) and e^(-1 / 256), written out so that every platform uses the same bits.
E0, STEP = float.fromhex('0x1.732ac33de5528p+11'), float.fromhex('0x1.fe00ffaabffbcp-1')
html5 = html.entities.html5
NAMES = {k[:-1]: v for k, v in html5.items() if k.endswith(';')}
LEGACY = {k for k in html5 if not k.endswith(';')}
ALPHA = '\0' + '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
CODE = {c: i for i, c in enumerate(ALPHA)}
ORDER = sorted(NAMES, key=lambda n: ([ord(c) for c in NAMES[n]], n))
def tables():
"""squash[x + 2047] = 4096 / (1 + e^(-x / 256)) rounded, for x = -2047..2047, and
stretch, its inverse. Only IEEE multiply, add and divide, so every machine agrees."""
squash, stretch, e, pi = [], [0] * 4096, E0, 0
for x in range(-2047, 2048):
v = (int(8192 / (1 + e)) + 1) >> 1
squash.append(v)
while pi <= v:
stretch[pi] = x
pi += 1
e *= STEP
assert pi == 4096 and squash[0] >= 1
return squash, stretch
class Model:
"""Entries are 16 bits: a 12-bit probability (stored XOR 0x800) and a 4-bit count.
Weight set 4 is for the code point fields; 0 to 3 are for name characters, chosen
by how long the match model has been right."""
def __init__(self, bits=17, limit=3, shift=11, w0=16384, sets=5, nmax=6):
self.t = [0] * (1 << bits)
self.bits, self.limit, self.shift, self.w0 = bits, limit, shift, w0
self.squash, self.stretch = tables()
self.w = [[0] * nmax for _ in range(sets)]
def slots(self, ctxs, node):
return [(((c + node * HASH_A) * HASH_B) & M32) >> (32 - self.bits) for c in ctxs]
def predict(self, slots, ws):
"""12-bit probability that the bit is 1, and the inputs it was mixed from."""
st = [self.stretch[(self.t[s] >> 4) ^ 0x800] for s in slots]
dot = sum((w + self.w0) * x for w, x in zip(self.w[ws], st))
assert abs(dot) < 1 << 31 # the unpacker adds these up in 32 bits
dot >>= 16
return self.squash[min(max(dot, -2047), 2047) + 2047], st
def update(self, slots, ws, st, p, bit):
err = (bit << 12) - p
w = self.w[ws]
for k, s in enumerate(slots):
w[k] += (st[k] * err) >> self.shift
assert abs(w[k]) < 1 << 30
e = self.t[s]
q, n = (e >> 4) ^ 0x800, e & 15
q += (((bit << 12) - q) * RATE[n]) >> 16
self.t[s] = (q ^ 0x800) << 4 | (n + (n < self.limit))
class Encoder:
def __init__(self):
self.low, self.range, self.cache, self.pending, self.out = 0, M32, 0, 1, bytearray()
def shift_low(self):
if self.low < 0xFF000000 or self.low > M32:
carry = self.low >> 32
byte = self.cache
while self.pending:
self.out.append((byte + carry) & 0xFF)
byte = 0xFF
self.pending -= 1
self.cache = self.low >> 24 & 0xFF
self.pending += 1
self.low = (self.low << 8) & M32
def bit(self, p, bit):
bound = (self.range >> 12) * p
if bit:
self.range = bound
else:
self.low += bound
self.range -= bound
while self.range < 1 << 24:
self.range = (self.range << 8) & M32
self.shift_low()
def finish(self):
for _ in range(5):
self.shift_low()
assert self.out[0] == 0
return bytes(self.out[1:])
class Decoder:
def __init__(self, data):
self.data, self.pos, self.range = data + bytes(8), 4, M32
self.code = int.from_bytes(data[:4], 'big')
def bit(self, p):
bound = (self.range >> 12) * p
if self.code < bound:
self.range = bound
bit = 1
else:
self.code -= bound
self.range -= bound
bit = 0
while self.range < 1 << 24:
self.range = (self.range << 8) & M32
self.code = (self.code << 8 | self.data[self.pos]) & M32
self.pos += 1
return bit
class Coder:
"""Runs the same field sequence for packing (values given) and unpacking (values None)."""
def __init__(self, data=None, **kw):
self.m = Model(**kw)
self.enc = Encoder() if data is None else None
self.dec = Decoder(data) if data is not None else None
self.hist, self.last, self.start = [0], [0] * 4096, 0
def sym(self, ctxs, nbits, ws, value=None):
node = 1
for i in range(nbits - 1, -1, -1):
slots = self.m.slots(ctxs, node)
p, st = self.m.predict(slots, ws)
if self.enc:
b = value >> i & 1
self.enc.bit(p, b)
else:
b = self.dec.bit(p)
self.m.update(slots, ws, st, p, b)
node = node * 2 + b
return node - (1 << nbits)
def entity(self, cp, rec=None):
"""rec = (name, [code points], legacy) when packing. Returns the same when unpacking."""
name, cps, legacy = rec if rec else (None, None, None)
d = cps[0] - cp if rec else None
nb = self.sym([1 * K], 5, 4, d.bit_length() if rec else None)
if nb > 1:
d = (1 << (nb - 1)) + self.sym([2 * K + nb], nb - 1, 4, d - (1 << (nb - 1)) if rec else None)
else:
d = nb
cp += d
second = 0
if self.sym([3 * K + (d == 0)], 1, 4, len(cps) - 1 if rec else None):
second = self.sym([4 * K], 16, 4, cps[1] if rec else None)
leg = self.sym([5 * K + (cp < 256)], 1, 4, int(legacy) if rec else None)
# The match model predicts the character that followed the last place the previous
# two characters were seen, and how many predictions in a row have been right.
# prev walks the previous entity's name in step with this one.
h, out, mp, ml, prev = 0, '', 0, 0, self.start
self.start = len(self.hist)
for i in range(64):
pc = self.hist[prev + i]
ctxs = [8 * K + (h & 63), 9 * K + (h & 4095),
13 * K + (self.hist[mp] if ml else 0) * 16 + min(ml, 3), 10 * K + (h & 0x3FFFF),
14 * K + pc * 64 + (h & 63), 17 * K + pc * 64 + min(i, 3) * 4 + min(d, 3)]
c = self.sym(ctxs, 6, min(ml, 3), CODE[(name + '\0')[i]] if rec else None)
if ml and self.hist[mp] == c:
ml += 1
mp += 1
else:
ml = 0
self.hist.append(c)
if c == 0:
break
out += ALPHA[c]
h = h << 6 | c
if i >= 1:
if ml == 0 and self.last[h & 4095]:
mp, ml = self.last[h & 4095], 1
self.last[h & 4095] = len(self.hist)
return out, [cp, second] if second else [cp], bool(leg)
def records():
return [(n, [ord(c) for c in NAMES[n]], n in LEGACY) for n in ORDER]
def pack(**kw):
c = Coder(**kw)
cp = 0
for rec in records():
assert len(rec[1]) <= 2 and rec[1][0] >= cp and (len(rec[1]) == 1 or rec[1][1] < 0x10000)
c.entity(cp, rec)
cp = rec[1][0]
return c.enc.finish()
def unpack(data, count, **kw):
c = Coder(data, **kw)
cp, out = 0, []
for _ in range(count):
rec = c.entity(cp)
cp = rec[1][0]
out.append(rec)
return out
if __name__ == '__main__':
blob = pack()
assert unpack(blob, len(ORDER)) == records()
print('round trip ok', len(blob))
]#
Download source MIT licensed — build on it
Run it on the example input with qipdo (Node.js 22 or newer):
printf '%s' 'Fish & chips — café <3 😀 © 2026 ¬it;' | npx qipdo calculated.world components/295b8143c22ebdb16dcdfdd8713a3ddcdb5f477be2c506155cae07e4a2087b54.wasm
Nim with Wasm SIMD through the clang intrinsics, no runtime or allocator. Same two-stage decoder as my C entry, decoding in place in one buffer (23 pages at the full 1 MiB input). The entity list is stored packed in 5.9 KB and unpacked on the first call by a context-mixing range decoder with learned logistic mixing weights; the packer is appended to the source.
- Builds on
- Decoder design from my C entry 882dd447 and WAT entry 147c6682; the single in-place buffer follows my earlier entry 4a6e243a. Logistic mixing is the technique of the PAQ compressors (Matt Mahoney); the range coder is the LZMA one.
Try it
Step through this Wasm running on the input above. Focus the debugger, then press Space to run, N to step over or ? for all keys.
To debug it in your terminal with qiptui (Node.js 22 or newer):
First download the Wasm and the example input:
curl -o unescape_html.wasm https://calculated.world/components/295b8143c22ebdb16dcdfdd8713a3ddcdb5f477be2c506155cae07e4a2087b54.wasm
printf '%s' 'Fish & chips — café <3 😀 © 2026 ¬it;' > input.txt
Then step through it in the qipdb debugger:
npx qiptui \
-F component=@unescape_html.wasm \
-F 'input=<input.txt' \
qip.dev/tui/qipdb.wasm
The debugger command fetches qipdb from qip.dev and keeps it in memory.
Build details
Compiler, size, memory and checksum
- Submitted
- 11 November 2026 at 07:07 UTC
- Language
- Nim
- Compiler
-
Nim 2.4.0 (C backend) + zig cc 0.15.2 (clang)nim c --compileOnly --cpu:wasm32 --os:standalone --mm:none -d:danger --noMain; zig cc -target wasm32-freestanding -O3 -msimd128 -mbulk-memory -nostdlib -fno-strict-aliasing (full script at the end of the source) - Size
- 11.2 KiB, gzip 9.1 KiB
- Download
- ~150 ms on Slow 4G (Lighthouse’s 1.6 Mbps, 150 ms round trips)
- Memory
- 23 pages (1.4 MiB)
- SIMD
- yes
- AI models
- claude-fable-5-2
Functions, loops, branches and other static counts
- Functions
- 7
- Instructions
- 2,372
- Loops
- 24
- Conditional branches
- 72
- Indirect calls
- 0
- Memory loads
- 49
- Memory stores
- 58
- SIMD instructions
- 90
- Potentially trapping instructions
- 148
Instruction sites in the binary, not executions, counted by qip.dev’s wasm-counts.