Unescape HTML · WAT 🤝 Fable 5.2 · #3 Smallest

SHA-256 bea3ba1914a0cd74506507a07e9b1b2318db19d32bb38485ee755e8b78a35d71

View source (62.5 KiB of WAT)
;; unescape_html in hand-written WebAssembly text with 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 entry, written directly so that nothing a compiler adds
;; is left: no stack, no stack pointer, every table at a fixed address.
;; - 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.
;; - The entity list is stored packed (6.4 KB for 2,125 names) and unpacked on the first
;;   call: see $sym and $init. pack.py, at the end of this file, is the matching packer.
;;
;; Memory map (bytes):
;;   0  packed entity list, then the Windows-1252 table, model rates, UTF-8 marks
;;   6592  ctx: the contexts of the symbol being unpacked; 6608 slot: their model entries
;;   6624  name: the name being unpacked, zero padded
;;   6672  leg_max: longest legacy name starting with two bytes (hashed), or 0
;;   10768  long_slots: hash of a long name -> its number in `longs`, or 0
;;   14864  last: match model, where two characters were last seen
;;   23056  pos: positions of `&` in the current window
;;   39504  hist: every name character unpacked so far
;;   55648  longs: 48-byte records (name[32], value, length) for names of 8 bytes or more
;;   79936  fast: (key, value) entries for names of up to 7 bytes; empty key = all ones
;;   145984  leg: the same for legacy names, keyed with the length in the top byte
;;   150336  len_mask: 32 bytes of 0xFF, then 32 of 0
;;   150400  output, and 262144 bytes into the same buffer:
;;   412544  input, 1048576 bytes and padding
;; Output and input share one buffer, a technique from my earlier entries (4a6e243a and
;; others): a reference grows by at most 1.2x (`≫⃒` is 5 bytes in, 6 out), so the write
;; position stays at least 52429 bytes behind the read position and decoding in
;; place is safe. The bytes before the input also hold the unpacker's model on the first call.
;; A value is its UTF-8 bytes in the low bytes of an i64 and its length in the top byte.
;;
;; Build: wat2wasm unescape.wat -o unescape-wat.wasm
(module
  (memory (export "memory") 23 23)
  ;; Range coder state: the first four bytes of the list are folded into $code here.
  (global $range (mut i32) (i32.const -1))
  (global $code (mut i32) (i32.const 0xDEE11AE6))
  (global $in (mut i32) (i32.const 4))
  (global $ulen (mut i32) (i32.const 0))
  (data (i32.const 0) "\de\e1\1a\e6\73\31\90\be\3a\8c\cc\da\91\d3\67\29\3c\e4\d7\59\c6\46\88\1f\78\6b\a1\3c\91\75\17\e0\3b\32\52\99\a6\ba\b9\e7\64\5b\5c\85\19\9e\a6\8d\dd\b5\df\5e\54\50\90\2c\6d\0d\64\c2\b1\5f\42\be\ac\b0\b7\b1\0e\d9\5a\7b\7a\89\43\e7\7a\43\ae\25\35\c1\68\c5\9c\c0\91\85\7c\c9\cb\96\45\97\54\78\45\f9\2f\c0\b1\94\59\f4\de\de\57\85\56\55\fb\be\cf\4a\20\69\a2\64\ae\04\9c\b6\20\68\65\29\aa\fc\fe\14\ca\02\4a\df\2a\4d\38\9f\47\01\49\35\5a\9b\32\55\af\3c\9b\a8\c4\9c\ca\20\ad\05\6d\51\1a\65\33\34\95\a3\cb\9c\bb\e9\7f\bf\8e\2c\5b\4c\4f\1b\e9\22\9e\cf\0a\79\3b\92\4b\21\de\e1\52\2a\aa\a3\10\14\f2\c0\28\e8\5c\7d\00\91\2d\ee\1c\46\59\1a\3d\1a\59\1f\e9\ec\3b\3e\92\7a\8b\e4\0f\47\d0\54\a6\62\b7\00\44\12\b6\b3\7e\67\02\bf\59\02\d9\b6\d3\a1\87\4b\19\43\bd\ac\25\07\37\8b\6f\00\bc\69\46\b3\8d\77\e8\38\c7\fa\96\a6\3d\d9\a0\2f\4e\4c\ac\66\f3\01\1e\f8\18\c0\e7\1a\6e\bf\48\c0\61\b3\3d\49\23\4a\93\93\db\bc\8d\73\20\d5\dd\87\15\d8\26\ad\50\4e\af\b5\be\51\ce\a8\8f\a2\46\41\6c\62\7a\79\53\6f\69\28\ad\eb\91\ca\cc\60\3e\d6\de\91\1e\f7\5a\df\fd\2c\2b\24\02\74\40\c3\c3\c2\90\39\f4\9f\b5\23\07\c5\18\b7\cd\47\46\f0\e1\4f\99\06\ea\f5\66\f3\aa\d9\3e\e8\12\27\f3\d6\b5\be\f7\47\1a\dc\cc\58\a1\d7\9a\f2\6e\fc\f7\32\84\da\5f\77\34\da\ed\cd\0f\15\a3\d7\41\36\02\48\7e\e6\ff\89\cc\5c\b6\4a\1a\c6\62\f6\10\95\78\3e\f4\b8\6d\59\b0\a1\6f\25\a2\14\ee\ac\77\98\df\b2\09\09\45\b9\10\82\bf\6e\27\2e\50\ff\ab\d7\f7\a7\1b\c7\8b\d6\a7\7c\98\79\07\0e\df\4f\53\c3\e7\61\30\81\8d\af\25\7d\87\1b\5d\f0\dc\f9\db\36\4d\3c\24\54\fa\21\7a\48\0d\d1\7a\0a\e9\5a\20\4d\95\49\1e\62\e3\0c\c6\b8\eb\b9\24\16\7b\6e\41\bd\6c\0e\0c\11\09\91\a2\70\60\2e\ce\12\43\41\9f\2b\c9\53\84\d3\54\06\05\bc\7d\37\ff\9e\30\fe\b6\ca\8d\e3\c2\ad\d3\79\1a\94\91\72\a8\54\26\79\0e\b5\9a\46\9d\cc\2f\82\56\a2\10\76\7a\46\e6\b0\1b\4e\c3\c3\4c\b1\ad\1d\7e\02\d9\b4\fb\11\62\35\5a\e8\47\3e\de\f2\93\c3\55\19\af\bf\2e\5a\f7\d4\ee\75\2e\3b\99\7d\92\1c\ae\ca\ea\23\3b\ac\39\87\d5\22\ba\12\4f\ff\74\0a\36\03\da\09\eb\24\86\a2\a4\17\46\ec\6c\8b\48\70\86\67\dc\bc\81\eb\7a\06\df\69\96\0b\28\c8\0d\7d\f6\b3\6a\34\e8\02\c0\21\43\52\98\05\c3\64\24\3b\95\62\ec\a2\83\78\7e\85\34\46\f8\74\ae\39\77\43\8b\ea\12\88\97\19\e7\78\e3\56\62\ed\b5\76\4d\a7\ad\dd\bd\84\e0\a5\99\bb\17\32\1e\77\a9\41\99\97\88\93\b9\7d\3d\f6\a3\e4\f2\b8\da\14\96\cc\ca\fd\d8\cf\f6\7d\fb\22\cb\15\83\53\80\f0\57\ed\93\95\59\2a\5f\a9\6d\d9\55\80\43\ab\24\c5\48\de\10\70\ca\09\56\bf\7a\28\59\45\76\20\fd\98\f4\53\3a\a1\65\f5\6e\38\5a\2a\37\39\2d\76\39\1f\87\0b\92\6d\05\49\69\dc\10\20\ef\cc\f3\ba\fd\85\ab\eb\97\f9\d5\1e\1a\b8\1a\4d\6f\fd\a2\b8\65\96\de\5b\2a\25\01\b1\b4\1e\97\d9\83\32\86\8c\8b\36\52\3d\2c\00\e1\b1\8f\47\ad\df\f5\ff\dd\b3\b4\1a\46\34\96\c1\8b\e7\86\75\5f\14\4f\c8\00\b1\18\ce\7f\42\f7\cc\a0\59\a2\13\5a\1e\3f\4c\29\98\7b\61\80\66\02\8c\58\9e\9f\e0\a0\0d\b6\1e\be\54\80\fd\c5\6d\72\05\e0\54\84\73\47\fc\24\42\c7\5e\d8\bd\51\67\1b\3e\66\08\f0\66\0c\15\4c\24\fc\24\6d\6f\ff\3b\74\52\0a\4e\75\99\35\17\d2\66\24\65\b6\9f\11\76\0f\1e\09\ab\d4\40\5c\e1\11\1e\e7\bf\e1\78\c1\9d\d2\ef\bd\a2\03\f0\c9\a0\e6\18\53\b7\0c\a3\76\9a\2b\e7\0d\9d\fb\6a\0b\77\8c\28\5d\0c\60\84\eb\85\12\c7\8b\cd\b6\7f\d7\e4\d0\d7\80\32\e1\cb\34\96\0f\83\e7\db\12\07\14\95\18\b7\2c\14\33\93\2b\a2\95\43\77\3e\79\12\76\59\d8\10\14\e8\23\d8\0a\b4\71\14\60\f2\2f\74\e5\33\ed\0f\11\a4\98\4e\56\f5\b5\90\9a\61\d5\52\9e\0c\b2\aa\0d\4d\f2\98\2a\b6\1d\b6\1a\d6\ea\33\0a\c5\24\4e\ef\ba\fd\9e\92\a4\e0\18\24\78\da\07\c0\bb\78\6f\6d\b2\1b\f4\04\07\88\c5\49\40\f0\ed\8c\f3\76\1d\43\56\1d\55\d5\88\85\77\7a\c3\e2\90\34\63\e0\9a\d8\9b\22\a2\ef\fd\fd\cd\af\62\0f\a6\34\e9\ab\fe\b6\4a\89\c8\d6\5f\d9\b7\9f\da\1f\5c\ac\62\6d\47\73\5f\5c\40\ef\80\fe\39\70\15\41\d7\db\e4\99\bf\76\27\98\70\c8\9b\7d\7a\38\71\1b\a8\4e\d8\f9\27\86\bd\8a\25\98\55\74\39\0d\23\6a\fb\f6\18\08\3e\ec\77\a6\fe\01\d4\31\90\5c\fe\fd\3a\8e\10\85\86\48\ea\2d\a0\3b\d0\99\52\a1\1a\29\d3\76\b7\d6\84\7b\87\ea\bc\9b\3b\3f\8d\65\08\d8\6d\86\68\10\74\f3\4b\73\c1\05\27\36\7a\7c\c9\3a\83\fc\f7\0b\98\1d\fe\7a\e5\19\ee\08\3a\3b\6e\5e\1f\2e\63\36\37\18\0f\19\99\55\06\51\b8\6f\ab\7c\18\e2\39\03\3f\8b\23\c3\6d\13\1b\cd\9a\10\44\7c\07\52\64\6a\8a\23\58\63\c7\8f\4f\72\e6\d7\91\58\a0\be\6a\31\e7\67\e8\ac\5c\47\1d\fc\b4\cc\e0\50\61\2c\16\66\16\66\c5\07\5a\38\f8\32\bb\88\95\76\fa\25\5c\d1\a0\de\70\c7\9a\73\c1\47\8f\36\ec\3f\a2\28\14\96\d9\a3\6d\a0\7b\29\9b\4f\d2\63\b2\c2\ea\f3\93\b5\0d\03\c3\30\6a\36\ec\70\4e\70\8d\93\2e\2b\39\23\37\f2\e4\c9\3c\ad\8f\99\f6\fe\88\5f\e5\40\2b\78\19\c1\21\58\79\09\75\d4\06\1e\3d\f7\26\14\cd\67\5e\c6\da\80\d6\b4\e8\66\ff\b1\1a\b3\06\b7\01\22\f6\e8\ce\7d\b2\fc\25\a4\82\0d\96\25\92\9f\60\8b\c4\19\eb\d7\7d\83\c5\ef\48\55\cb\f9\ac\64\31\ce\0f\a2\99\da\18\39\54\16\7b\3e\b6\92\3f\09\90\e1\46\ca\82\19\84\71\89\2a\ce\73\54\cc\bc\c5\d0\d6\b7\5b\a2\6a\04\25\59\05\4d\fa\d1\08\ea\93\1e\4a\88\2a\b7\ad\8e\c9\92\a9\20\0d\95\e5\21\25\ae\51\f9\6f\63\54\a7\da\45\e6\72\4f\ba\af\98\8b\4f\ff\80\43\04\e6\3f\5e\c2\4d\99\15\88\87\a7\ca\e1\42\eb\1a\a1\0b\7b\3a\5a\5b\91\fc\93\50\12\93\a3\a1\47\7c\d7\19\4b\0e\90\a5\3f\de\be\50\12\99\51\95\8d\9d\98\6c\48\32\24\86\b5\d9\e9\5f\50\b0\2d\8c\84\6d\a4\4e\ee\3c\d2\a5\9e\98\40\5e\8c\8c\b2\7b\ac\45\db\78\17\10\a9\a0\ae\50\88\4f\d8\f5\df\69\eb\a3\8b\f2\ba\a9\55\9e\73\21\b3\61\14\ac\5b\69\b0\68\12\65\94\99\0a\24\fc\9d\0a\9f\fc\aa\22\fb\5b\c8\ec\8f\4d\da\75\7e\f3\d1\4f\47\76\d9\74\e2\e6\8d\00\9c\dd\9e\38\46\1b\24\d1\dd\61\c3\ea\81\84\2a\2a\88\a1\f1\2e\a3\88\25\89\bb\97\5f\d4\24\e7\66\e8\1b\ec\ea\5d\96\4a\f5\4f\7c\43\6a\5a\e6\4d\a8\59\27\7b\5a\86\4d\ad\54\f3\ab\6a\f1\56\7d\c8\7b\4f\df\50\c9\73\3b\2c\85\53\8c\39\c5\6c\75\54\b6\78\71\79\a4\4a\fd\19\fb\b8\1d\de\c9\6e\41\c7\1d\4d\4e\11\7a\43\88\a2\ef\42\d6\28\ba\b7\82\a7\e0\61\88\d7\9d\03\4f\da\df\be\9f\37\5c\7f\fa\b3\1f\b4\ff\2e\0b\11\2f\f5\5b\3a\07\4d\6d\4d\3b\5a\20\d9\a6\34\26\4b\dd\e4\53\3a\e6\d6\e3\86\dd\31\5d\8a\5f\ef\c5\8d\be\48\f8\c3\07\57\33\31\b3\f8\50\3c\d2\bd\63\90\df\63\e4\5e\b1\52\ed\2d\75\d0\d5\b7\cb\a6\ce\bb\d3\46\3d\32\1b\0d\3f\87\89\0f\fa\c3\f0\34\04\d2\ca\8b\b0\fa\ee\a8\c6\7d\d5\18\a3\55\55\06\3b\7f\ce\a2\8b\f5\eb\ed\ba\02\53\f6\f0\2c\ca\75\55\07\d6\9b\9b\c6\97\2b\32\53\58\86\9d\b8\31\32\a9\b0\ac\bd\f6\92\43\b1\d1\2c\b0\7d\5e\d1\28\cd\9a\34\4b\fe\f9\c4\d6\d8\8c\95\4f\5a\a0\45\c0\2d\ce\ee\6f\c1\6f\df\da\51\78\ff\33\10\fb\0b\fc\ca\a9\10\39\f9\ff\3b\a0\98\0a\46\ef\68\94\f4\d2\69\4f\b1\8b\17\fe\90\d1\eb\b5\74\98\29\6d\33\b8\e6\a5\88\d7\97\92\90\bd\67\ac\65\ca\1f\d0\10\0b\23\73\b7\7e\b6\d5\a4\7b\96\58\0e\31\ae\1c\29\87\47\6b\c1\5b\f5\3b\96\dc\97\9f\0c\86\37\d7\fc\f4\52\60\0b\27\64\1b\6c\85\90\85\7e\63\8b\85\fa\04\97\a4\33\ee\c2\0f\28\4f\7a\fe\4c\d9\a3\8e\cb\bd\d2\d7\b7\cc\4d\58\de\49\25\5d\69\49\02\15\59\6b\04\eb\42\d4\f8\24\e6\17\8e\4b\d1\66\96\19\a2\9f\a2\2f\a5\42\a1\5d\96\d1\ed\68\61\25\38\68\fe\50\60\ac\24\09\21\db\7c\cc\06\36\bc\16\5b\f0\e2\e5\c0\12\19\6c\2a\87\6d\9d\1e\a6\82\86\5c\c8\9d\64\67\5f\2b\d3\01\c8\60\b7\94\17\4c\90\3f\2e\ec\b2\61\39\dc\ef\9c\3f\49\2a\fc\9d\e7\b8\17\e4\8e\58\8a\1d\cc\6b\fe\6a\ea\0a\a5\cb\40\b4\57\65\02\67\76\a1\8e\13\fd\3a\ee\d0\af\53\57\66\1f\60\51\36\be\af\35\a2\2e\e2\73\10\46\16\12\92\b6\4c\3c\c2\b8\54\62\de\8b\82\0b\bc\08\82\18\d4\05\ce\14\6f\58\74\1f\dd\7a\5b\e7\93\d5\4e\f3\5c\5e\56\b9\a2\c1\56\65\45\6f\97\f8\e9\bd\15\18\d0\cf\4d\5c\9b\f4\1e\25\06\3d\61\14\8a\7c\75\53\d3\3a\b4\42\d5\1b\12\25\1c\d7\d1\04\3b\be\c7\1d\1f\85\2e\06\bf\57\96\4b\2d\68\e3\7d\f3\27\e4\70\d0\6c\7a\a5\f8\70\99\59\d0\23\e1\85\13\00\e4\92\9b\80\ad\dd\86\1d\4b\00\fe\11\7e\0f\96\e1\25\ef\92\85\db\95\1d\cc\cb\ea\04\00\23\fd\2f\36\f7\a0\08\d3\88\36\f8\c1\bb\b3\9e\bb\e7\38\5b\10\e6\7a\b7\48\87\0b\b1\67\50\6a\98\43\13\ad\95\b2\9a\fe\6a\3d\dc\6a\2c\e8\7a\d3\13\74\af\3f\41\7b\bc\92\92\ed\cc\23\2a\e8\e3\17\16\84\af\34\72\a3\f4\e7\cc\3d\d2\18\39\89\aa\23\93\09\da\5f\c9\e3\8a\45\2c\1c\14\8c\29\4e\2f\ff\56\04\2e\38\c8\8e\4f\f4\5a\41\92\98\c8\f1\8a\af\bd\b3\2c\be\b7\ca\2f\ea\f0\95\55\55\5a\32\93\48\bf\22\62\65\70\12\ae\0c\fc\67\7d\88\90\d0\e9\39\50\e0\49\f1\5c\bf\5f\48\fc\1d\07\e6\77\df\cd\73\80\c1\90\a2\12\8c\c7\00\56\f7\75\1d\98\9e\9c\95\5c\40\4a\af\e3\af\c7\a9\88\29\3c\14\2c\a8\b8\d2\e7\14\b3\1b\3e\b5\dd\64\35\f2\77\ac\28\6d\33\5f\aa\23\23\03\d0\0f\98\a8\50\21\44\37\2a\14\27\0e\cd\eb\a8\91\18\79\b2\0a\ea\5c\26\15\f7\36\cd\9f\a7\38\93\c1\94\78\c8\b2\a0\a5\04\93\9e\ab\0a\bb\5b\f0\5e\ec\84\8e\7c\d2\d9\74\41\7b\8e\36\72\83\75\a0\b8\7a\98\87\1d\1a\7b\d3\9a\29\90\fa\50\28\de\bf\a7\c6\3d\de\d9\99\b2\2b\37\90\97\ee\85\95\8f\ff\fb\97\ba\86\ae\a0\99\44\1e\06\93\30\d1\bf\61\39\c4\2b\56\0b\0f\54\0e\d1\50\8a\8e\52\40\ce\25\24\a8\ed\9f\f0\99\59\24\e4\fc\2f\21\cf\eb\b3\55\b1\f4\22\5b\2d\41\64\08\b8\ae\d4\fb\a5\12\8c\1c\3d\77\9f\e9\a0\26\13\e7\a3\d4\89\5e\87\f2\e3\c1\1c\2c\51\0e\7d\c3\95\2e\5c\1b\02\f3\0b\92\b3\df\39\17\3d\61\8e\00\8e\d4\31\8d\5a\a5\f3\3b\4a\0b\4d\2a\87\1e\22\78\1a\ad\1a\9f\d8\71\cf\4f\11\16\a0\33\4c\4a\ab\00\d6\17\4b\c0\55\3b\0d\8f\e4\8c\fc\ce\dc\9f\a4\fc\ca\97\53\02\51\ad\83\78\49\86\aa\e2\5d\f8\e4\6d\6b\25\9d\19\ee\98\8f\b5\df\0c\d1\49\c4\76\b4\84\d0\48\e4\43\ac\0b\6a\0d\7f\b4\df\72\f2\3f\91\f2\54\55\ea\95\a6\4f\9b\8c\b8\de\05\36\6f\bf\33\26\8a\d0\e9\8b\c8\23\7b\36\fd\6b\f5\e4\59\e8\95\65\a1\4f\30\91\5a\7a\9f\d7\6e\8a\a5\38\8f\0b\e5\fe\d5\31\e7\1a\e0\8a\96\7f\19\01\e7\ec\19\59\52\8a\75\8c\c7\ef\fe\8c\2c\69\c2\72\cc\f9\c7\36\8e\33\71\1a\1c\69\ef\b1\87\7f\9f\92\14\81\0e\fa\dc\13\15\d0\1f\30\e7\61\c2\7b\49\37\8c\0c\37\79\20\02\37\6c\44\bf\ce\bb\03\c1\84\68\e6\3d\a9\96\a4\ea\94\4f\4f\4a\f6\50\7a\3d\3c\1a\8e\73\7e\5d\3e\5c\4b\7f\3f\f7\b9\b8\ad\58\9f\9b\e0\9b\56\50\72\36\7b\96\eb\76\e3\ea\de\11\60\8a\26\1b\9f\17\ea\aa\df\b3\60\80\a2\df\28\0e\bc\fe\46\2c\21\2d\ab\a8\34\bb\49\46\7b\09\c0\36\d4\3c\66\b1\c2\48\85\dd\21\bb\05\72\05\a0\9f\2c\49\c2\aa\1e\8b\45\eb\b2\53\f3\39\bd\92\5e\06\1e\b7\21\4c\f0\a3\84\50\9b\ee\3e\4d\f2\02\bd\4e\9e\59\88\77\e5\cb\c7\3a\0b\2a\59\78\cf\b3\59\5b\7d\6d\bd\c8\8c\73\73\49\1c\5a\ee\11\66\78\6f\0b\41\7f\44\5d\83\74\3b\5c\ec\17\fc\30\69\29\0d\ce\cc\0a\81\37\c9\6f\ac\50\eb\4f\85\3f\19\df\19\86\1b\45\8d\9e\75\c9\9e\c3\ab\f0\6a\e4\be\56\a1\21\63\71\7f\fc\67\91\0f\db\2b\74\1b\20\e1\ff\60\5f\6b\47\59\25\73\2d\cc\72\cf\ed\a8\de\64\c3\fd\42\0b\f3\2f\a5\d1\be\72\6e\2d\b1\c6\ef\48\20\05\ec\db\c4\87\22\d6\a2\92\dd\90\cd\fc\d9\4a\47\00\32\11\58\27\1a\74\e0\92\77\89\6e\32\34\fe\93\a3\08\b7\8c\23\be\7a\a6\fa\9e\c4\f2\b5\53\bb\0e\92\2a\69\a8\cf\93\fe\12\e1\77\da\5f\f2\9f\d2\a1\7e\4b\44\02\91\11\a7\90\55\b5\1c\b7\d6\04\56\f3\e3\ea\96\e4\21\d4\8c\c6\76\cd\06\ee\c0\fa\08\e9\89\6b\04\9c\47\4a\60\db\67\27\09\90\e3\3e\cc\15\cf\85\7d\e6\8e\54\18\db\97\5f\85\4a\34\dc\a3\f0\5c\0e\e2\4a\7f\84\72\be\fd\c3\d7\bf\64\2a\cf\b3\50\77\8d\eb\5d\6d\97\61\22\a4\e8\9d\c2\67\1c\b1\45\21\69\bc\ff\0d\f2\0b\2b\34\eb\b2\cb\20\1b\73\9b\a4\00\36\43\d0\79\52\f3\68\de\d8\79\fd\0b\bb\de\90\4e\2b\ea\a4\5f\18\dd\f8\52\7b\f7\d8\9b\08\13\7e\9c\e2\cf\83\bf\cc\8c\f0\88\b0\cb\0d\cd\6c\9a\38\29\55\57\bf\74\17\93\4e\b2\8f\52\37\68\71\79\f2\b9\6a\48\57\6a\82\bf\58\16\cc\36\02\12\ec\0e\1d\25\b9\95\ea\91\cb\e5\64\97\8d\9e\4d\57\0d\8c\5a\10\77\eb\34\23\72\c7\83\a4\34\ee\09\0a\f4\b4\75\5f\72\64\74\09\cf\22\f4\a4\8d\73\03\ca\de\3e\d1\aa\ab\92\99\95\7d\61\6a\56\09\b5\01\85\c8\d9\58\b0\ca\23\7c\b8\a1\15\4c\80\f3\15\ad\c8\0c\01\f8\a7\77\a4\05\b1\69\91\4f\b4\e6\2c\d2\b9\c3\e8\b5\31\39\6a\d5\e8\f9\0a\8a\e7\90\81\7c\4f\ae\52\ba\27\c0\60\f8\06\ca\b2\b2\de\ab\3d\e4\09\bf\4b\13\c6\b4\ec\09\7a\50\45\f9\ba\95\75\05\c0\f7\4a\43\b5\fe\c9\98\ea\45\4c\e6\52\59\c8\4d\4c\04\17\88\5f\5c\b9\4c\e5\98\40\b5\02\f9\68\31\e6\39\99\26\fd\57\76\31\53\67\41\02\5a\81\07\65\a4\08\93\a2\9a\56\0e\e5\3f\8e\ae\60\ee\d5\1d\33\55\09\e4\dd\eb\84\ea\11\8c\d3\0a\d2\0b\71\cf\aa\6f\ea\2a\84\9a\47\76\3a\5f\6c\1b\11\92\aa\49\68\ad\b4\d7\47\f3\c2\96\e7\fa\22\74\d6\ed\c7\6f\1f\c7\cc\3b\e5\24\f2\43\5e\14\ae\3c\7c\40\73\93\1c\5c\7d\6c\60\1b\ea\fa\2f\d1\83\c1\07\b1\01\be\56\cf\1f\7d\ab\41\e2\71\88\bc\b1\93\6e\dd\3e\71\07\be\66\83\81\81\16\fa\a8\a5\73\23\da\91\20\c6\4f\af\ca\b8\c8\12\7b\ff\53\74\a6\e4\6f\c5\4e\50\45\d0\59\26\14\73\a8\82\30\d0\e5\df\4f\83\2b\9a\bb\a7\ac\08\cd\4a\f2\6a\9d\6c\e5\34\74\2e\f9\e5\fb\36\46\8e\f6\10\44\9f\8c\7c\78\4b\f8\52\91\6c\8e\6a\28\f1\44\b6\10\4c\fc\64\f1\39\6c\42\05\38\bf\5b\5f\27\3e\6e\7a\92\9a\cd\8e\26\46\34\53\db\fe\de\0f\b7\58\97\16\23\aa\b2\2a\36\0a\cd\5c\11\31\a9\1d\d0\32\32\fa\60\cc\8a\69\de\57\dd\9e\08\6c\f0\8e\bb\83\4c\54\ea\12\2c\7e\2a\10\f3\4a\db\b6\ab\50\e4\15\91\10\f4\7d\ac\0b\26\d9\12\10\fb\96\68\02\10\87\29\60\60\bb\63\7b\66\1b\8b\ab\fb\db\9f\f7\d7\cc\0c\85\eb\03\df\11\28\08\7b\06\67\b7\30\88\7e\94\69\26\04\b4\aa\05\a9\89\de\3a\6f\68\4c\a2\6d\c1\d3\19\62\4c\5d\2e\6a\6d\60\41\44\42\e4\b6\69\5e\d6\54\68\3f\63\76\c7\09\a0\e6\c6\92\a3\58\63\d8\ba\22\9a\e7\44\6c\67\a2\88\77\06\ec\b5\b3\47\ab\da\4c\9a\a6\7f\1d\8a\7c\c4\71\59\2e\70\d0\50\a0\b1\75\ba\d6\56\6f\62\c3\82\aa\d7\2c\ef\cb\cb\02\81\e8\83\0e\a7\25\6d\56\61\d8\4d\d2\58\9f\ed\ce\e3\95\5d\81\db\b3\af\32\2a\a7\0d\ca\1b\04\9c\a6\6c\8b\05\cc\db\70\0a\26\6d\c0\b2\57\2a\9e\54\61\cf\06\e9\a8\70\33\57\7c\75\f7\51\0d\3a\1b\f8\17\3a\57\e0\c8\4b\5f\98\f4\15\78\1f\a2\cb\44\e5\a1\19\76\1e\2c\dd\76\9c\5f\ef\e8\9e\f7\c8\4a\bc\dd\75\1d\10\65\85\17\d5\8b\c0\88\0f\ea\5e\61\89\d5\90\01\83\8c\7e\ec\be\03\03\d0\7f\49\0a\4d\e2\f3\c3\ba\b4\e6\92\fa\c6\27\09\22\1e\d3\e3\f6\39\11\62\1a\96\9a\18\a1\75\72\f0\72\6d\cd\d3\34\f8\03\e8\1c\43\3d\16\c3\bb\0c\81\b6\e6\7e\6d\89\c2\22\c5\39\68\db\99\ea\1b\58\7a\e9\08\d3\c8\67\51\6b\49\34\37\aa\7c\d8\71\b4\8d\3b\ac\8e\6c\84\07\e4\c2\16\c9\98\80\39\03\d4\2d\fe\8f\2d\af\2d\10\c7\52\de\bc\0b\2b\75\2e\31\c3\d0\9f\75\b3\c2\ab\d3\b9\c5\fe\62\3e\fd\13\2e\b4\2f\96\09\d8\8d\3e\fc\e5\b1\bd\8e\ab\f7\7e\94\65\6e\01\fa\cf\95\f9\76\d7\5f\35\32\ee\82\19\3a\e8\3a\0f\db\a9\c9\f6\f3\34\b9\e9\2b\0a\8f\bd\a5\e0\d2\69\f7\0b\d0\1d\2d\67\56\d2\cf\9f\0b\97\a1\4f\13\40\70\44\50\fa\5f\77\d1\73\56\5c\7e\ac\17\e3\6f\20\97\54\c4\e1\62\fa\41\e3\82\f6\77\24\23\11\9c\84\0c\6a\82\a6\4e\d4\5e\5f\69\82\d1\1e\30\87\fd\ac\85\b7\d5\40\44\bb\ae\b2\71\f2\98\23\7a\1b\49\86\2b\64\86\b8\07\26\41\2c\f4\38\cd\cb\23\71\5a\cb\c9\78\83\a1\ef\55\2b\29\67\1e\9b\69\52\94\ad\72\21\e8\67\56\32\ff\b1\f3\29\61\2c\1d\55\e8\75\9c\0b\91\0b\9d\04\c2\42\60\a3\2a\ba\67\fd\2d\c4\8e\e3\33\cc\fa\df\25\08\e1\06\bb\f9\45\38\d1\2a\ab\99\56\db\73\07\81\ba\51\66\12\af\af\68\eb\a8\8c\e9\af\2a\45\14\07\23\b8\b0\c3\e4\1e\a9\8f\02\5f\98\13\c6\19\68\b7\a6\0d\b0\41\da\2f\65\88\7a\81\b8\a1\2d\5a\90\4b\b4\e9\86\41\70\62\df\f3\f4\49\e1\12\0b\d9\bc\fe\c6\7f\ee\53\70\a1\b3\e7\bb\5b\99\11\84\a3\1b\12\d2\bd\96\e2\f3\b2\a4\a0\2e\c0\ce\8d\a3\ea\36\39\ff\55\e8\7b\7a\3a\16\3c\73\7b\ee\c7\74\90\68\00\80\6f\80\c4\fb\7d\42\1e\ca\90\ff\fc\03\cc\73\bb\d5\f9\e8\49\1a\9f\05\d0\59\75\c3\83\56\ff\65\4e\e2\b3\6e\8f\ab\39\ef\41\eb\c3\b2\81\d1\26\ed\74\01\0b\a7\9a\07\34\bc\aa\54\1f\7f\9d\a8\f4\7a\21\0e\43\fd\c0\b7\da\18\58\7d\2d\7b\9e\74\1d\69\18\20\93\bc\9b\18\c0\e1\7a\3b\9a\fd\0f\5a\8e\a3\82\d2\e9\b4\df\40\ca\3d\68\0a\7d\ed\46\7c\fc\b9\3d\8c\ac\c5\2e\ac\ba\38\98\14\96\88\3d\52\a7\4a\1d\f4\7c\9a\04\2b\c7\61\15\69\6c\a6\67\38\09\46\1d\22\78\21\44\50\3e\ee\f4\59\85\f7\24\6c\01\ca\8b\c1\7d\c2\65\9a\d6\e0\b1\df\b9\3c\0e\43\04\4c\e6\69\f5\fa\12\98\d1\b1\b7\c9\92\ab\c7\13\11\3c\f4\48\68\d8\34\a2\2f\64\0b\89\a9\0e\97\38\16\7f\89\94\bc\ee\93\96\a8\b3\e4\0f\26\44\93\1e\99\e8\f7\5f\50\dd\da\13\24\ae\6b\53\19\16\83\80\7d\ad\1f\8a\80\40\16\eb\de\b3\4c\a0\25\4f\a3\bb\6e\7f\83\36\e5\04\cf\bc\05\b5\13\0b\2b\c4\e2\fc\06\c4\75\4b\a8\76\04\46\9b\c7\aa\bc\ee\c5\79\05\82\64\68\4d\5e\97\b3\25\f5\d3\8a\86\40\8c\b5\a3\79\3c\da\e2\a9\65\dc\4c\dc\34\6d\7a\de\26\a2\a1\e0\02\9d\dd\c2\d1\77\05\09\bf\51\fd\83\5c\40\06\a2\65\05\53\5e\a1\33\c4\24\73\63\92\00\cd\4c\7a\ae\de\b4\e6\22\22\18\38\88\27\74\23\c4\9b\29\80\ab\be\3f\11\97\6e\9a\53\b4\3d\16\a8\83\45\ea\7e\b4\de\39\3d\42\e2\69\af\be\24\4d\61\87\f2\a4\93\62\cf\c8\eb\15\25\aa\0b\bf\5f\9c\c2\65\77\b1\2d\62\63\81\1a\ab\c2\ea\2a\35\4a\21\0a\ef\aa\cd\09\95\b3\dc\13\f4\51\26\cc\28\0c\d4\1b\77\36\cc\13\72\5e\5b\ec\8c\b7\30\5c\6b\cc\89\73\e4\0f\41\cf\78\1e\75\ad\f1\8c\5c\23\26\00\ae\c8\55\3c\b9\f0\18\29\8e\d7\30\be\86\28\bb\52\6e\2e\e0\09\8c\93\b2\8e\6c\0f\a6\fd\51\d7\20\d4\16\a0\c4\c1\62\01\51\6d\e4\2f\41\33\3b\d0\9a\51\7e\4b\bb\30\b5\a9\5a\af\75\1f\bc\3f\f9\fb\f1\4c\43\21\27\89\24\20\d3\a3\1c\44\21\98\74\e3\79\3d\d9\29\69\68\17\d9\0a\45\6a\a1\f4\e1\87\78\fe\76\10\a2\ea\7e\23\50\35\01\12\74\61\59\2d\ab\da\83\e0\ca\73\c8\3a\12\8f\a0\7b\3c\eb\1e\5d\2d\6c\1a\28\5c\9d\49\dd\8e\d1\d6\75\a8\49\11\92\44\d5\87\54\64\c9\0c\2a\a3\6d\2c\ce\51\d4\b5\99\3e\af\be\ac\a7\f2\21\38\75\46\a0\ef\9f\97\dd\be\54\ea\bb\21\9e\21\40\f6\1a\5f\6d\3a\a5\6f\fe\fb\2d\81\62\36\20\bc\b7\92\16\5d\ca\5a\b8\ec\df\e8\78\16\d3\54\f6\d6\d0\d2\8d\48\bb\fb\57\2b\50\d1\9d\a2\31\fb\cb\56\97\d6\53\44\86\7d\f5\8d\95\1b\ee\fd\c1\1e\7d\1f\68\bb\9d\21\f4\c7\2e\8d\a8\97\b3\71\54\f8\4e\08\b7\86\57\78\c7\6e\0d\69\e6\3b\84\e2\ad\c3\fc\2f\76\8e\d1\4f\fb\cd\60\a8\42\fe\91\49\fc\f2\82\28\b7\96\25\70\23\69\7b\17\da\ba\23\ad\b9\be\9a\b4\e9\60\bc\a7\36\41\9b\cd\6f\1b\67\fb\ab\76\f6\86\80\f7\db\fe\84\8f\eb\d4\6c\9b\06\c3\86\d2\b5\43\0a\cf\e6\a8\97\3d\e0\31\20\d7\88\68\2a\30\c3\48\4d\d2\8b\b7\43\9c\f7\21\c5\e3\af\7e\ef\76\b2\f7\bf\ed\a8\7b\69\34\4e\e5\43\df\e4\3e\87\fa\10\5d\c5\c9\14\ab\b1\d2\1b\83\2f\3e\ff\51\a4\94\4f\c2\bd\0f\d0\fa\6a\ba\de\a4\fd\4f\b7\2e\ed\fe\8c\dc\bb\90\fd\53\85\e2\3e\d5\78\63\78\57\9f\f4\50\c8\65\cb\2b\50\3d\4d\52\8b\ba\c5\d5\02\bf\ff\af\f6\90\6c\03\d7\11\68\19\68\3f\30\b6\08\bb\f5\cc\ae\50\0f\46\1f\83\d4\22\03\67\e1\60\57\93\f8\a8\d5\9d\c4\7d\3a\8f\5e\b0\f0\e4\e4\a4\e2\ba\d5\ce\59\7a\39\10\be\da\64\e1\9f\c5\d7\9f\c4\3e\39\82\8f\36\41\fc\04\aa\d1\03\e2\80\88\ed\08\3c\62\17\29\1a\cd\93\02\8f\a2\54\8f\4c\2f\93\d4\57\73\6c\2c\fd\d5\12\a5\e8\4c\39\02\0d\35\e5\20\fb\9a\c4\8a\9d\54\95\64\0e\f9\3c\a2\f6\c2\28\42\01\a5\a2\d3\5a\2f\e7\eb\be\39\5e\eb\d9\6d\00\8d\60\07\4d\5e\cc\97\cc\02\44\33\ea\9a\6b\7f\af\e7\71\49\3f\6c\09\30\c5\e5\02\80\c7\af\a9\cb\ae\00\e0\93\cc\b5\e6\d9\d4\c2\f7\cf\91\4c\56\bd\bd\9d\cb\54\83\05\db\1a\60\92\dc\b4\df\67\6f\91\53\12\25\16\0d\1b\29\a9\97\96\c2\7b\8b\20\3e\62\3f\05\58\81\44\39\51\72\20\f6\b6\86\ab\76\08\71\73\b0\fd\17\12\d7\90\8f\66\77\33\ad\df\f1\a9\84\38\3f\dc\7d\7d\a2\9f\84\07\ab\b9\e0\75\88\74\6f\7e\08\af\2f\34\8b\ec\9d\51\1d\28\14\9f\c5\cd\22\a8\51\5e\2b\79\1c\da\0c\51\f3\15\69\d8\e7\d8\ab\0d\a2\3e\90\24\47\a0\de\10\25\5a\a4\c0\fc\6a\b8\34\80\e1\f1\35\f1\cc\99\09\2e\21\0e\f5\77\60\b3\3d\56\8a\93\da\b3\79\e8\02\fc\60\4e\db\56\01\ce\50\f8\1c\7c\76\42\1d\40\cf\5a\20\25\62\88\7d\69\87\ec\fb\a9\cc\94\41\e6\18\dc\87\5c\00\2f\46\97\be\4b\de\24\31\1e\5a\75\ba\ed\3b\80\f4\b3\35\57\7a\2a\56\87\aa\3a\69\4a\6f\a7\38\18\3e\fa\6b\d1\a3\1d\53\8d\28\6e\34\30\15\bb\8a\1f\35\ef\7f\a3\f8\82\08\f2\b5\d2\bc\74\d7\b5\94\29\d9\a7\a9\70\cf\71\04\40\e2\2e\b5\f4\af\75\75\a4\f6\0f\1a\b0\0c\ca\fc\40\15\9c\9e\87\26\6c\83\b0\60\f1\41\43\ab\46\34\0a\6d\f6\5b\78\27\19\02\b8\5c\40\94\fe\ad\65\cc\ba\0e\e5\10\6d\55\5d\4e\8f\85\43\fe\51\d2\d5\83\08\61\9a\0e\a6\92\70\58\d6\09\c7\bd\9e\71\a7\c7\8c\36\c9\08\92\ca\3a\1a\99\2d\02\4c\a4\64\21\ba\b1\3c\59\d0\62\18\ae\f6\97\7d\c7\7e\d0\4f\b2\74\f4\51\50\b1\67\f3\fc\45\c0\88\97\b1\13\b2\58\0b\04\e2\b9\27\70\47\f3\a7\46\4b\e9\90\50\1c\b2\c1\2c\2c\54\6e\b2\70\50\df\bc\88\fe\c0\b9\58\b5\1d\2e\82\61\a5\9c\0b\e0\4e\f2\30\c7\ba\05\ae\89\b6\99\b5\6e\b4\06\09\bc\fd\c9\fe\00\22\39\04\92\52\11\60\c5\27\1e\e8\75\a8\55\67\56\8d\1b\d4\40\44\7d\4e\bf\c2\d7\0f\e5\16\7a\6e\37\93\ec\89\bd\dd\2f\8f\7f\31\f4\34\ed\ca\92\95\59\f1\f3\4e\ee\57\48\1b\55\49\61\da\71\01\50\ea\09\16\f2\96\51\3b\24\ae\5b\f2\2e\46\ba\cd\58\19\54\cf\93\bf\c7\47\c6\4b\ed\a8\59\08\c9\71\55\7e\af\84\7c\15\a9\54\01\07\ad\ba\ca\2d\80\aa\b5\bc\7a\b2\8a\04\d9\29\b7\e7\1e\ce\a7\06\2c\d9\6f\b5\2b\4a\10\19\47\f2\45\b9\a5\08\89\b3\1d\f2\ff\aa\6e\24\6d\51\95\e9\d6\13\79\bf\bf\5f\24\0f\08\8a\1e\00\ac\20\81\00\1a\20\92\01\1e\20\26\20\20\20\21\20\c6\02\30\20\60\01\39\20\52\01\8d\00\7d\01\8f\00\90\00\18\20\19\20\1c\20\1d\20\22\20\13\20\14\20\dc\02\22\21\61\01\3a\20\53\01\9d\00\7e\01\78\01\aa\aa\66\66\24\49\e3\38\8b\2e\62\27\c0\80\00\00\e0\80\80\00\f0\80\80\80")

  (func (export "input_ptr") (result i32) (i32.const 412544))
  (func (export "input_utf8_cap") (result i32) (i32.const 1048576))
  (func (export "output_utf8_cap") (result i32) (i32.const 1258298))

  ;; A code point as UTF-8 bytes in a word, low byte first; their count goes to $ulen.
  ;; The bits are laid out as four bytes, then the unused leading ones are shifted out.
  (func $utf8 (param $v i32) (result i32)
    (local $n i32)
    (global.set $ulen (local.tee $n
      (i32.add (i32.add (i32.add (i32.const 1)
        (i32.gt_u (local.get $v) (i32.const 0x7F)))
        (i32.gt_u (local.get $v) (i32.const 0x7FF)))
        (i32.gt_u (local.get $v) (i32.const 0xFFFF)))))
    (if (result i32) (i32.eq (local.get $n) (i32.const 1))
      (then (local.get $v))
      (else (i32.or
        (i32.shr_u
          (i32.or (i32.or (i32.or
            (i32.shr_u (local.get $v) (i32.const 18))
            (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 12)) (i32.const 0x3F)) (i32.const 8)))
            (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 6)) (i32.const 0x3F)) (i32.const 16)))
            (i32.shl (i32.and (local.get $v) (i32.const 0x3F)) (i32.const 24)))
          (i32.sub (i32.const 32) (i32.shl (local.get $n) (i32.const 3))))
        (i32.load offset=6564 (i32.shl (local.get $n) (i32.const 2)))))))

  ;; Unpacks one symbol of $nbits bits with an adaptive binary range coder (the LZMA one).
  ;; ctx holds $nctx4 / 4 contexts; each bit is predicted by mixing their model entries.
  ;; An entry is 16 bits: a 12-bit probability (stored XOR 0x800, so zeroed memory means
  ;; one half) above a 4-bit count of how often it was used.
  (func $sym (param $nctx4 i32) (param $nbits i32) (result i32)
    (local $node i32) (local $top i32) (local $k i32) (local $s i32) (local $e i32)
    (local $r i32) (local $num i32) (local $den i32) (local $bit i32)
    (local.set $node (i32.const 1))
    (local.set $top (i32.shl (i32.const 1) (local.get $nbits)))
    (loop $bits
      (local.set $num (i32.const 0))
      (local.set $den (i32.const 0))
      (local.set $k (i32.const 0))
      (loop $vote
        (i32.store offset=6608 (local.get $k) (local.tee $s
          (i32.and (i32.shr_u
            (i32.mul
              (i32.add (i32.load offset=6592 (local.get $k)) (i32.mul (local.get $node) (i32.const 0x2545F491)))
              (i32.const 0x9E3779B1))
            (i32.const 14)) (i32.const -2))))
        (local.set $r (i32.shr_u (local.tee $e (i32.load16_u offset=150400 (local.get $s))) (i32.const 4)))
        ;; Weigh a context by its order, how often it was seen and how sure it is.
        (local.set $e (i32.shl
          (i32.mul
            (i32.and (local.get $e) (i32.const 15))
            (i32.add (i32.const 1) (i32.shr_u
              (select (local.get $r) (i32.sub (i32.const 0x1000) (local.get $r))
                (i32.lt_u (local.get $r) (i32.const 0x800)))
              (i32.const 8))))
          (i32.shr_u (local.get $k) (i32.const 2))))
        (local.set $num (i32.add (local.get $num)
          (i32.mul (i32.xor (local.get $r) (i32.const 0x800)) (local.get $e))))
        (local.set $den (i32.add (local.get $den) (local.get $e)))
        (br_if $vote (i32.lt_u (local.tee $k (i32.add (local.get $k) (i32.const 4))) (local.get $nctx4))))
      ;; $r: 12-bit probability that the bit is 1, never 0.
      (local.set $r (if (result i32) (local.get $den)
        (then (i32.div_u (local.get $num) (local.get $den)))
        (else (i32.const 0x800))))
      (local.set $r (i32.mul (i32.shr_u (global.get $range) (i32.const 12))
        (select (local.get $r) (i32.const 1) (local.get $r))))
      (if (local.tee $bit (i32.lt_u (global.get $code) (local.get $r)))
        (then (global.set $range (local.get $r)))
        (else
          (global.set $code (i32.sub (global.get $code) (local.get $r)))
          (global.set $range (i32.sub (global.get $range) (local.get $r)))))
      (loop $norm
        (if (i32.lt_u (global.get $range) (i32.const 0x1000000)) (then
          (global.set $range (i32.shl (global.get $range) (i32.const 8)))
          (global.set $code (i32.or (i32.shl (global.get $code) (i32.const 8)) (i32.load8_u (global.get $in))))
          (global.set $in (i32.add (global.get $in) (i32.const 1)))
          (br $norm))))
      ;; Move each entry toward the bit just seen, by 2 / (2n + 3) after n uses.
      (local.set $k (i32.const 0))
      (loop $learn
        (local.set $r (i32.xor (i32.shr_u
          (local.tee $e (i32.load16_u offset=150400 (local.tee $s (i32.load offset=6608 (local.get $k)))))
          (i32.const 4)) (i32.const 0x800)))
        (local.set $e (i32.and (local.get $e) (i32.const 15)))
        (i32.store16 offset=150400 (local.get $s) (i32.or
          (i32.shl
            (i32.xor
              (i32.add (local.get $r) (i32.shr_s
                (i32.mul
                  (i32.sub (i32.shl (local.get $bit) (i32.const 12)) (local.get $r))
                  (i32.load16_u offset=6560 (i32.shl (local.get $e) (i32.const 1))))
                (i32.const 16)))
              (i32.const 0x800))
            (i32.const 4))
          (i32.add (local.get $e) (i32.lt_u (local.get $e) (i32.const 5)))))
        (br_if $learn (i32.lt_u (local.tee $k (i32.add (local.get $k) (i32.const 4))) (local.get $nctx4))))
      (br_if $bits (i32.lt_u
        (local.tee $node (i32.add (i32.shl (local.get $node) (i32.const 1)) (local.get $bit)))
        (local.get $top))))
    (i32.sub (local.get $node) (local.get $top)))

  ;; Unpacks the entity list into the lookup tables. Entities are ordered by code point, so
  ;; a value is a small step from the one before. A name is coded character by character
  ;; from four contexts: the previous one, two and three characters, and a match model that
  ;; predicts the character that followed the last place the previous two were seen.
  (func $init
    (local $r i32) (local $cp i32) (local $nl i32) (local $hn i32) (local $d i32) (local $vn i32)
    (local $legacy i32) (local $n i32) (local $h i32) (local $mp i32) (local $ml i32) (local $c i32)
    (local $ep i32) (local $val i64) (local $key i64)
    ;; Empty keys in fast and leg, and the 0xFF half of len_mask, in one fill.
    (memory.fill (i32.const 79936) (i32.const 255) (i32.const 70432))
    (local.set $r (i32.const 2125))
    (local.set $hn (i32.const 1))
    (loop $entity
      ;; Step from the previous code point: its bit length, then the bits below the top one.
      (i32.store (i32.const 6592) (i32.const 1048576))
      (if (i32.gt_u (local.tee $d (call $sym (i32.const 4) (i32.const 5))) (i32.const 1)) (then
        (i32.store (i32.const 6592) (i32.add (i32.const 2097152) (local.get $d)))
        (local.set $d (i32.add
          (i32.shl (i32.const 1) (local.tee $d (i32.sub (local.get $d) (i32.const 1))))
          (call $sym (i32.const 4) (local.get $d))))))
      (local.set $val (i64.extend_i32_u (call $utf8
        (local.tee $cp (i32.add (local.get $cp) (local.get $d))))))
      (local.set $vn (global.get $ulen))
      ;; A few values have a second code point.
      (i32.store (i32.const 6592) (i32.add (i32.const 3145728) (i32.eqz (local.get $d))))
      (if (call $sym (i32.const 4) (i32.const 1)) (then
        (i32.store (i32.const 6592) (i32.const 4194304))
        (local.set $val (i64.or (local.get $val) (i64.shl
          (i64.extend_i32_u (call $utf8 (call $sym (i32.const 4) (i32.const 16))))
          (i64.extend_i32_u (i32.shl (local.get $vn) (i32.const 3))))))
        (local.set $vn (i32.add (local.get $vn) (global.get $ulen)))))
      (local.set $val (i64.or (local.get $val)
        (i64.shl (i64.extend_i32_u (local.get $vn)) (i64.const 56))))
      (i32.store (i32.const 6592) (i32.add (i32.const 5242880) (i32.lt_u (local.get $cp) (i32.const 256))))
      (local.set $legacy (call $sym (i32.const 4) (i32.const 1)))
      ;; Names use a 6-bit alphabet: 0 ends the name, then digits, capitals, small letters.
      (memory.fill (i32.const 6624) (i32.const 0) (i32.const 40))
      (local.set $n (i32.const 0))
      (local.set $h (i32.const 0))
      (local.set $ml (i32.const 0))
      (loop $char
        (i32.store (i32.const 6592) (i32.add (i32.const 8388608) (i32.and (local.get $h) (i32.const 63))))
        (i32.store (i32.const 6596) (i32.add (i32.const 9437184) (i32.and (local.get $h) (i32.const 4095))))
        (i32.store (i32.const 6600) (i32.add (i32.add (i32.const 13631488)
          (i32.shl (select (i32.load8_u offset=39504 (local.get $mp)) (i32.const 0) (local.get $ml)) (i32.const 4)))
          (select (local.get $ml) (i32.const 3) (i32.lt_u (local.get $ml) (i32.const 3)))))
        (i32.store (i32.const 6604) (i32.add (i32.const 10485760) (i32.and (local.get $h) (i32.const 0x3FFFF))))
        (local.set $c (call $sym (i32.const 16) (i32.const 6)))
        (if (i32.and (i32.ne (local.get $ml) (i32.const 0))
              (i32.eq (i32.load8_u offset=39504 (local.get $mp)) (local.get $c)))
          (then
            (local.set $ml (i32.add (local.get $ml) (i32.const 1)))
            (local.set $mp (i32.add (local.get $mp) (i32.const 1))))
          (else (local.set $ml (i32.const 0))))
        (i32.store8 offset=39504 (local.get $hn) (local.get $c))
        (local.set $hn (i32.add (local.get $hn) (i32.const 1)))
        (if (local.get $c) (then
          (i32.store8 offset=6624 (local.get $n) (i32.add (local.get $c)
            (select (i32.const 47) (select (i32.const 54) (i32.const 60) (i32.lt_u (local.get $c) (i32.const 37)))
              (i32.lt_u (local.get $c) (i32.const 11)))))
          (local.set $h (i32.or (i32.shl (local.get $h) (i32.const 6)) (local.get $c)))
          (if (i32.ge_u (local.tee $n (i32.add (local.get $n) (i32.const 1))) (i32.const 2)) (then
            (local.set $c (i32.load16_u offset=14864
              (local.tee $ep (i32.shl (i32.and (local.get $h) (i32.const 4095)) (i32.const 1)))))
            (if (i32.and (i32.eqz (local.get $ml)) (i32.ne (local.get $c) (i32.const 0))) (then
              (local.set $mp (local.get $c))
              (local.set $ml (i32.const 1))))
            (i32.store16 offset=14864 (local.get $ep) (local.get $hn))))
          (br $char))))
      (if (i32.le_u (local.get $n) (i32.const 7))
        (then
          ;; fast: the key is the name and its `;`.
          (local.set $key (i64.or (i64.load (i32.const 6624))
            (i64.shl (i64.const 59) (i64.extend_i32_u (i32.shl (local.get $n) (i32.const 3))))))
          (local.set $ep (i32.shl (i32.wrap_i64 (i64.shr_u
            (i64.mul (local.get $key) (i64.const 0xA59D054B12F5B905)) (i64.const 52))) (i32.const 4)))
          (loop $probe
            (if (i64.ne (i64.load offset=79936 (local.get $ep)) (i64.const -1)) (then
              (local.set $ep (i32.add (local.get $ep) (i32.const 16)))
              (br $probe))))
          (i64.store offset=79936 (local.get $ep) (local.get $key))
          (i64.store offset=79944 (local.get $ep) (local.get $val))
          (if (local.get $legacy) (then
            ;; leg: the length is part of the key, so a NUL after a shorter name cannot match.
            (local.set $key (i64.or (i64.load (i32.const 6624))
              (i64.shl (i64.extend_i32_u (local.get $n)) (i64.const 56))))
            (local.set $ep (i32.shl (i32.wrap_i64 (i64.shr_u
              (i64.mul (local.get $key) (i64.const 0xF880D97D1854E551)) (i64.const 56))) (i32.const 4)))
            (loop $probe
              (if (i64.ne (i64.load offset=145984 (local.get $ep)) (i64.const -1)) (then
                (local.set $ep (i32.add (local.get $ep) (i32.const 16)))
                (br $probe))))
            (i64.store offset=145984 (local.get $ep) (local.get $key))
            (i64.store offset=145992 (local.get $ep) (local.get $val))
            (if (i32.lt_u
                  (i32.load8_u offset=6672 (local.tee $ep (i32.shr_u
                    (i32.mul (i32.load16_u (i32.const 6624)) (i32.const 0x9E3779B1)) (i32.const 20))))
                  (local.get $n))
              (then (i32.store8 offset=6672 (local.get $ep) (local.get $n)))))))
        (else
          ;; longs: record number $nl (from 1) is at 55600 + 48 * $nl.
          (memory.copy
            (i32.add (i32.const 55600) (local.tee $ep
              (i32.mul (local.tee $nl (i32.add (local.get $nl) (i32.const 1))) (i32.const 48))))
            (i32.const 6624) (i32.const 32))
          (i64.store offset=55632 (local.get $ep) (local.get $val))
          (i32.store offset=55640 (local.get $ep) (local.get $n))
          (local.set $ep (i32.wrap_i64 (i64.shr_u (i64.mul
            (i64.add
              (i64.xor (i64.load (i32.const 6624))
                (i64.rotl (i64.load offset=6616 (local.get $n)) (i64.const 29)))
              (i64.extend_i32_u (local.get $n)))
            (i64.const 0xA0C2995F40498CB3)) (i64.const 53))))
          (loop $probe
            (if (i32.load16_u offset=10768 (i32.shl (local.get $ep) (i32.const 1))) (then
              (local.set $ep (i32.and (i32.add (local.get $ep) (i32.const 1)) (i32.const 2047)))
              (br $probe))))
          (i32.store16 offset=10768 (i32.shl (local.get $ep) (i32.const 1)) (local.get $nl))))
      (br_if $entity (local.tee $r (i32.sub (local.get $r) (i32.const 1))))))

  (func (export "render") (param $n i32) (result i64)
    (local $size i32) (local $e i32) (local $o i32) (local $base i32) (local $lim i32)
    (local $out i32) (local $q i32) (local $i i32) (local $len i32) (local $j i32)
    (local $hex i32) (local $p i32) (local $v i32) (local $k i32) (local $ep i32) (local $h i32)
    (local $w i64) (local $mm i64) (local $d i64) (local $t i64) (local $letter i64)
    (local $x i64) (local $key i64) (local $val i64) (local $kb i64) (local $high i64)
    (local $zero v128) (local $amp v128) (local $s0 v128) (local $s1 v128) (local $m0 v128) (local $m1 v128)

    ;; len_mask is filled by $init, so its first byte says whether the tables exist yet.
    (if (i32.eqz (i32.load8_u (i32.const 150336))) (then (call $init)))
    (local.set $size (select (local.get $n) (i32.const 1048576) (i32.lt_u (local.get $n) (i32.const 1048576))))
    ;; 64 zero bytes after the input: nothing stale can be mistaken for part of a reference.
    (v128.store offset=412544 (local.get $size) (local.get $zero))
    (v128.store offset=412560 (local.get $size) (local.get $zero))
    (v128.store offset=412576 (local.get $size) (local.get $zero))
    (v128.store offset=412592 (local.get $size) (local.get $zero))
    (local.set $amp (i8x16.splat (i32.const 38)))
    (local.set $high (i64.const 0x8080808080808080))

    ;; $e: end of the last reference in the input. $o: bytes of output so far.
    (block $done
      (loop $window
        (br_if $done (i32.ge_u (local.get $base) (local.get $size)))
        (local.set $lim (select (i32.add (local.get $base) (i32.const 4096)) (local.get $size)
          (i32.gt_u (i32.sub (local.get $size) (local.get $base)) (i32.const 4096))))

        ;; Stage 1: list the position of every `&` in the window, 64 bytes at a time. The
        ;; first four positions of a block are written without looking at how many there
        ;; are; entries past the count are overwritten or ignored.
        (local.set $out (i32.const 0))
        (loop $scan
          (local.set $mm (i64.or (i64.or (i64.or
            (i64.extend_i32_u (i8x16.bitmask (i8x16.eq (v128.load offset=412544 (local.get $base)) (local.get $amp))))
            (i64.shl (i64.extend_i32_u (i8x16.bitmask (i8x16.eq (v128.load offset=412560 (local.get $base)) (local.get $amp)))) (i64.const 16)))
            (i64.shl (i64.extend_i32_u (i8x16.bitmask (i8x16.eq (v128.load offset=412576 (local.get $base)) (local.get $amp)))) (i64.const 32)))
            (i64.shl (i64.extend_i32_u (i8x16.bitmask (i8x16.eq (v128.load offset=412592 (local.get $base)) (local.get $amp)))) (i64.const 48))))
          (local.set $k (i32.add (local.get $out) (i32.shl (i32.wrap_i64 (i64.popcnt (local.get $mm))) (i32.const 2))))
          (i32.store offset=23056 (local.get $out) (i32.add (local.get $base) (i32.wrap_i64 (i64.ctz (local.get $mm)))))
          (local.set $mm (i64.and (local.get $mm) (i64.sub (local.get $mm) (i64.const 1))))
          (i32.store offset=23060 (local.get $out) (i32.add (local.get $base) (i32.wrap_i64 (i64.ctz (local.get $mm)))))
          (local.set $mm (i64.and (local.get $mm) (i64.sub (local.get $mm) (i64.const 1))))
          (i32.store offset=23064 (local.get $out) (i32.add (local.get $base) (i32.wrap_i64 (i64.ctz (local.get $mm)))))
          (local.set $mm (i64.and (local.get $mm) (i64.sub (local.get $mm) (i64.const 1))))
          (i32.store offset=23068 (local.get $out) (i32.add (local.get $base) (i32.wrap_i64 (i64.ctz (local.get $mm)))))
          (if (i64.ne (local.tee $mm (i64.and (local.get $mm) (i64.sub (local.get $mm) (i64.const 1)))) (i64.const 0)) (then
            (loop $more
              (i32.store offset=23072 (local.get $out) (i32.add (local.get $base) (i32.wrap_i64 (i64.ctz (local.get $mm)))))
              (local.set $out (i32.add (local.get $out) (i32.const 4)))
              (br_if $more (i64.ne (local.tee $mm (i64.and (local.get $mm) (i64.sub (local.get $mm) (i64.const 1)))) (i64.const 0))))))
          (local.set $out (local.get $k))
          (br_if $scan (i32.lt_u (local.tee $base (i32.add (local.get $base) (i32.const 64))) (local.get $lim))))

        ;; Stage 2: for each position, copy the text before it and decode the reference.
        (local.set $q (i32.const 0))
        (if (local.get $out) (then
          (loop $ref
            ;; $i: position of this `&`; it becomes the end of the reference.
            (local.set $len (i32.sub (local.tee $i (i32.load offset=23056 (local.get $q))) (local.get $e)))
            (local.set $q (i32.add (local.get $q) (i32.const 4)))
            (v128.store offset=150400 (local.get $o) (v128.load offset=412544 (local.get $e)))
            (v128.store offset=150416 (local.get $o) (v128.load offset=412560 (local.get $e)))
            (if (i32.gt_u (local.get $len) (i32.const 32)) (then
              (local.set $j (i32.const 32))
              (loop $copy
                (v128.store offset=150400 (i32.add (local.get $o) (local.get $j))
                  (v128.load offset=412544 (i32.add (local.get $e) (local.get $j))))
                (br_if $copy (i32.lt_u (local.tee $j (i32.add (local.get $j) (i32.const 16))) (local.get $len))))))
            (local.set $o (i32.add (local.get $o) (local.get $len)))
            ;; $w: the 8 bytes after the `&`.
            (local.set $w (i64.load offset=412545 (local.get $i)))
            (block $next
              (block $bare
                (block $legacy
                  (if (i32.eq (i32.and (i32.wrap_i64 (local.get $w)) (i32.const 255)) (i32.const 35)) (then
                    ;; Numeric. Decimal and hexadecimal share one path: classify 8 bytes,
                    ;; take the digits before the first other byte, and fold them pairwise.
                    (local.set $hex (i32.eq
                      (i32.or (i32.and (i32.wrap_i64 (i64.shr_u (local.get $w) (i64.const 8))) (i32.const 255)) (i32.const 32))
                      (i32.const 120)))
                    (local.set $t (i64.xor
                      (local.tee $d (i64.load offset=412544
                        (local.tee $p (i32.add (i32.add (local.get $i) (i32.const 2)) (local.get $hex)))))
                      (i64.const 0x3030303030303030)))
                    ;; $x: 0x80 in each byte that is a letter a-f or A-F.
                    (local.set $x (i64.and
                      (local.tee $key (i64.or (local.get $d) (i64.const 0x2020202020202020)))
                      (i64.xor (local.get $high) (i64.const -1))))
                    (local.set $letter (i64.and
                      (i64.and
                        (i64.add (local.get $x) (i64.const 0x1F1F1F1F1F1F1F1F))
                        (i64.xor
                          (i64.or (i64.add (local.get $x) (i64.const 0x1919191919191919)) (local.get $key))
                          (i64.const -1)))
                      (local.get $high)))
                    ;; $t: 0x80 in each byte that is not a digit of this reference.
                    (if (i64.ne
                          (local.tee $t (i64.and
                            (i64.and
                              (i64.or
                                (i64.add (i64.and (local.get $t) (i64.xor (local.get $high) (i64.const -1)))
                                  (i64.const 0x7676767676767676))
                                (local.get $t))
                              (local.get $high))
                            (i64.xor
                              (select (local.get $letter) (i64.const 0) (local.get $hex))
                              (i64.const -1))))
                          (i64.const 0))
                      (then
                        ;; $kb: bits of digits. None: the `&` stands for itself.
                        (br_if $bare (i64.eqz (local.tee $kb (i64.and (i64.ctz (local.get $t)) (i64.const 56)))))
                        (local.set $x (i64.shl
                          (i64.add (i64.and (local.get $d) (i64.const 0x0F0F0F0F0F0F0F0F))
                            (i64.mul (i64.shr_u (local.get $letter) (i64.const 7)) (i64.const 9)))
                          (i64.sub (i64.const 64) (local.get $kb))))
                        (local.set $x (i64.and
                          (i64.add (i64.mul (local.get $x) (select (i64.const 16) (i64.const 10) (local.get $hex)))
                            (i64.shr_u (local.get $x) (i64.const 8)))
                          (i64.const 0x00FF00FF00FF00FF)))
                        (local.set $x (i64.and
                          (i64.add (i64.mul (local.get $x) (select (i64.const 256) (i64.const 100) (local.get $hex)))
                            (i64.shr_u (local.get $x) (i64.const 16)))
                          (i64.const 0x0000FFFF0000FFFF)))
                        (local.set $v (i32.wrap_i64
                          (i64.add (i64.mul (local.get $x) (select (i64.const 65536) (i64.const 10000) (local.get $hex)))
                            (i64.shr_u (local.get $x) (i64.const 32)))))
                        (local.set $p (i32.add (local.get $p) (i32.wrap_i64 (i64.shr_u (local.get $kb) (i64.const 3))))))
                      (else
                        ;; Eight or more digits: one at a time, saturating past U+10FFFF.
                        (local.set $v (i32.const 0))
                        (local.set $k (select (i32.const 16) (i32.const 10) (local.get $hex)))
                        (block $end
                          (loop $digit
                            (if (i32.gt_u
                                  (local.tee $h (i32.sub (i32.load8_u offset=412544 (local.get $p)) (i32.const 48)))
                                  (i32.const 9))
                              (then
                                (if (i32.lt_u
                                      (local.tee $h (i32.sub (i32.or (i32.load8_u offset=412544 (local.get $p)) (i32.const 32)) (i32.const 87)))
                                      (i32.const 10))
                                  (then (local.set $h (i32.const 16))))))
                            (br_if $end (i32.ge_u (local.get $h) (local.get $k)))
                            (if (i32.gt_u
                                  (local.tee $v (i32.add (i32.mul (local.get $v) (local.get $k)) (local.get $h)))
                                  (i32.const 0x10FFFF))
                              (then (local.set $v (i32.const 0x110000))))
                            (local.set $p (i32.add (local.get $p) (i32.const 1)))
                            (br $digit)))))
                    (local.set $i (i32.add (local.get $p)
                      (i32.eq (i32.load8_u offset=412544 (local.get $p)) (i32.const 59))))
                    ;; 0x80-0x9F use the Windows-1252 table; 0, surrogates and values past
                    ;; U+10FFFF become U+FFFD.
                    (if (i32.lt_u (i32.sub (local.get $v) (i32.const 0x80)) (i32.const 0x20))
                      (then (local.set $v (i32.load16_u offset=6240 (i32.shl (local.get $v) (i32.const 1)))))
                      (else
                        (if (i32.or
                              (i32.ge_u (i32.sub (local.get $v) (i32.const 1)) (i32.const 0x10FFFF))
                              (i32.lt_u (i32.sub (local.get $v) (i32.const 0xD800)) (i32.const 0x800)))
                          (then (local.set $v (i32.const 0xFFFD))))))
                    ;; UTF-8, as in $utf8 but in line: a call here would cost the loop its registers.
                    (local.set $k
                      (i32.add (i32.add (i32.add (i32.const 1)
                        (i32.gt_u (local.get $v) (i32.const 0x7F)))
                        (i32.gt_u (local.get $v) (i32.const 0x7FF)))
                        (i32.gt_u (local.get $v) (i32.const 0xFFFF))))
                    (i32.store offset=150400 (local.get $o)
                      (select
                        (local.get $v)
                        (i32.or
                          (i32.shr_u
                            (i32.or (i32.or (i32.or
                              (i32.shr_u (local.get $v) (i32.const 18))
                              (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 12)) (i32.const 0x3F)) (i32.const 8)))
                              (i32.shl (i32.and (i32.shr_u (local.get $v) (i32.const 6)) (i32.const 0x3F)) (i32.const 16)))
                              (i32.shl (i32.and (local.get $v) (i32.const 0x3F)) (i32.const 24)))
                            (i32.sub (i32.const 32) (i32.shl (local.get $k) (i32.const 3))))
                          (i32.load offset=6564 (i32.shl (local.get $k) (i32.const 2))))
                        (i32.eq (local.get $k) (i32.const 1))))
                    (local.set $o (i32.add (local.get $o) (local.get $k)))
                    (br $next)))

                  ;; Named. $t: 0x80 in the first byte of $w that is `;`.
                  (local.set $t (i64.xor (local.get $w) (i64.const 0x3B3B3B3B3B3B3B3B)))
                  (if (i64.ne
                        (local.tee $t (i64.and
                          (i64.and (i64.sub (local.get $t) (i64.const 0x0101010101010101)) (i64.xor (local.get $t) (i64.const -1)))
                          (local.get $high)))
                        (i64.const 0))
                    (then
                      ;; Up to 7 bytes: the name and its `;` are the key into fast.
                      (local.set $key (i64.and (local.get $w)
                        (i64.sub
                          (i64.shl (i64.const 2) (i64.add (local.tee $kb (i64.and (i64.ctz (local.get $t)) (i64.const 56))) (i64.const 7)))
                          (i64.const 1))))
                      (local.set $ep (i32.shl (i32.wrap_i64 (i64.shr_u
                        (i64.mul (local.get $key) (i64.const 0xA59D054B12F5B905)) (i64.const 52))) (i32.const 4)))
                      (loop $probe
                        (if (i64.eq (local.tee $val (i64.load offset=79936 (local.get $ep))) (local.get $key)) (then
                          (i64.store offset=150400 (local.get $o) (local.tee $val (i64.load offset=79944 (local.get $ep))))
                          (local.set $o (i32.add (local.get $o) (i32.wrap_i64 (i64.shr_u (local.get $val) (i64.const 56)))))
                          (local.set $i (i32.add (i32.add (local.get $i) (i32.const 2))
                            (i32.wrap_i64 (i64.shr_u (local.get $kb) (i64.const 3)))))
                          (br $next)))
                        (br_if $legacy (i64.eq (local.get $val) (i64.const -1)))
                        (local.set $ep (i32.add (local.get $ep) (i32.const 16)))
                        (br $probe)))
                    (else
                      ;; No `;` in 8 bytes: a name of 8 to 31 bytes needs its `;` in the next 24.
                      (local.set $s0 (v128.load offset=412545 (local.get $i)))
                      (local.set $s1 (v128.load offset=412561 (local.get $i)))
                      (if (local.tee $h (i32.or
                            (i8x16.bitmask (i8x16.eq (local.get $s0) (local.tee $m0 (i8x16.splat (i32.const 59)))))
                            (i32.shl (i8x16.bitmask (i8x16.eq (local.get $s1) (local.get $m0))) (i32.const 16))))
                        (then
                          (local.set $len (i32.ctz (local.get $h)))
                          (local.set $h (i32.wrap_i64 (i64.shr_u (i64.mul
                            (i64.add
                              (i64.xor (local.get $w)
                                (i64.rotl (i64.load offset=412537 (i32.add (local.get $i) (local.get $len))) (i64.const 29)))
                              (i64.extend_i32_u (local.get $len)))
                            (i64.const 0xA0C2995F40498CB3)) (i64.const 53))))
                          ;; Byte j of the 32 loaded at len_mask + 32 - len is 0xFF when j < len.
                          (local.set $m0 (v128.load offset=150336 (local.tee $k (i32.sub (i32.const 32) (local.get $len)))))
                          (local.set $m1 (v128.load offset=150352 (local.get $k)))
                          (block $miss
                            (loop $slot
                              (br_if $miss (i32.eqz (local.tee $ep
                                (i32.load16_u offset=10768 (i32.shl (local.get $h) (i32.const 1))))))
                              (if (i32.and
                                    (i32.eq
                                      (i32.load offset=55640 (local.tee $ep (i32.mul (local.get $ep) (i32.const 48))))
                                      (local.get $len))
                                    (i32.eqz (v128.any_true (v128.or
                                      (v128.xor (v128.and (local.get $s0) (local.get $m0)) (v128.load offset=55600 (local.get $ep)))
                                      (v128.xor (v128.and (local.get $s1) (local.get $m1)) (v128.load offset=55616 (local.get $ep)))))))
                                (then
                                  (i64.store offset=150400 (local.get $o) (local.tee $val (i64.load offset=55632 (local.get $ep))))
                                  (local.set $o (i32.add (local.get $o) (i32.wrap_i64 (i64.shr_u (local.get $val) (i64.const 56)))))
                                  (local.set $i (i32.add (i32.add (local.get $i) (i32.const 2)) (local.get $len)))
                                  (br $next)))
                              (local.set $h (i32.and (i32.add (local.get $h) (i32.const 1)) (i32.const 2047)))
                              (br $slot))))))))

                ;; Without `;`: the longest legacy name that is a prefix, longest first.
                (local.set $k (i32.load8_u offset=6672 (i32.shr_u
                  (i32.mul (i32.and (i32.wrap_i64 (local.get $w)) (i32.const 0xFFFF)) (i32.const 0x9E3779B1))
                  (i32.const 20))))
                (loop $try
                  (br_if $bare (i32.lt_u (local.get $k) (i32.const 2)))
                  (local.set $key (i64.or
                    (i64.and (local.get $w)
                      (i64.sub (i64.shl (i64.const 1) (i64.extend_i32_u (i32.shl (local.get $k) (i32.const 3)))) (i64.const 1)))
                    (i64.shl (i64.extend_i32_u (local.get $k)) (i64.const 56))))
                  (local.set $ep (i32.shl (i32.wrap_i64 (i64.shr_u
                    (i64.mul (local.get $key) (i64.const 0xF880D97D1854E551)) (i64.const 56))) (i32.const 4)))
                  (loop $probe
                    (if (i64.eq (local.tee $val (i64.load offset=145984 (local.get $ep))) (local.get $key)) (then
                      (i64.store offset=150400 (local.get $o) (local.tee $val (i64.load offset=145992 (local.get $ep))))
                      (local.set $o (i32.add (local.get $o) (i32.wrap_i64 (i64.shr_u (local.get $val) (i64.const 56)))))
                      (local.set $i (i32.add (i32.add (local.get $i) (i32.const 1)) (local.get $k)))
                      (br $next)))
                    (if (i64.ne (local.get $val) (i64.const -1)) (then
                      (local.set $ep (i32.add (local.get $ep) (i32.const 16)))
                      (br $probe))))
                  (local.set $k (i32.sub (local.get $k) (i32.const 1)))
                  (br $try)))

              ;; Not a reference: the `&` is copied.
              (i32.store8 offset=150400 (local.get $o) (i32.const 38))
              (local.set $o (i32.add (local.get $o) (i32.const 1)))
              (local.set $i (i32.add (local.get $i) (i32.const 1))))
            (local.set $e (local.get $i))
            (br_if $ref (i32.lt_u (local.get $q) (local.get $out))))))
        (br $window)))

    ;; Without a reference the input is the output.
    (if (i32.eqz (local.get $e)) (then
      (return (i64.or (i64.const 1771862988161024) (i64.extend_i32_u (local.get $size))))))
    ;; Copy the text after the last reference.
    (local.set $o (i32.sub (local.get $o) (local.get $e)))
    (loop $tail
      (if (i32.lt_u (local.get $e) (local.get $size)) (then
        (v128.store offset=150400 (i32.add (local.get $o) (local.get $e)) (v128.load offset=412544 (local.get $e)))
        (local.set $e (i32.add (local.get $e) (i32.const 16)))
        (br $tail))))
    (i64.or (i64.const 645963081318400) (i64.extend_i32_u (i32.add (local.get $o) (local.get $size))))))

(; pack.py, which produced the packed entity list:

"""Packs the entity list for unescape-small: entities ordered by code point, coded with an
adaptive binary range coder whose contexts are the previous one, two and three name
characters and a match model. The C unpacker in unescape-small.in.c mirrors Model and Decoder 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, 11915, 10082]

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 tdiv(a, b):
    """Division truncating toward zero, as in C."""
    q = abs(a) // abs(b)
    return q if (a < 0) == (b < 0) else -q


class Model:
    def __init__(self, bits=16, limit=6, narrow=False):
        # narrow: 16-bit entries holding a 12-bit probability and a 4-bit count,
        # instead of 32-bit entries holding 16 bits of each.
        self.t = [0] * (1 << bits)
        self.bits, self.limit, self.narrow = bits, limit, narrow

    def load(self, s):
        e = self.t[s]
        return ((e >> 4) ^ 0x800, e & 15) if self.narrow else ((e >> 16) ^ 0x8000, e & 0xFFFF)

    def slots(self, ctxs, node):
        return [(((c + node * HASH_A) * HASH_B) & M32) >> (32 - self.bits) for c in ctxs]

    def predict(self, slots):
        """12-bit probability that the bit is 1."""
        num = den = 0
        for k, s in enumerate(slots):
            p, n = self.load(s)
            half, shift = (0x800, 8) if self.narrow else (0x8000, 12)
            w = (n * (1 + (abs(p - half) >> shift))) << k
            num += p * w
            den += w
        if self.narrow:
            p = num // den if den else 0x800
        else:
            p = (num // den if den else 0x8000) >> 4
        return min(max(p, 1), 4095)

    def update(self, slots, bit):
        for s in slots:
            p, n = self.load(s)
            one = 0x1000 if self.narrow else 0x10000
            p += (((one if bit else 0) - p) * RATE[n]) >> 16
            if n < self.limit:
                n += 1
            self.t[s] = (p ^ 0x800) << 4 | n if self.narrow else (p ^ 0x8000) << 16 | n


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 = [0], [0] * 4096

    def sym(self, ctxs, nbits, value=None):
        node = 1
        for i in range(nbits - 1, -1, -1):
            slots = self.m.slots(ctxs, node)
            p = self.m.predict(slots)
            if self.enc:
                b = value >> i & 1
                self.enc.bit(p, b)
            else:
                b = self.dec.bit(p)
            self.m.update(slots, 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, d.bit_length() if rec else None)
        if nb > 1:
            d = (1 << (nb - 1)) + self.sym([2 * K + nb], nb - 1, d - (1 << (nb - 1)) if rec else None)
        else:
            d = nb
        cp += d
        second = 0
        if self.sym([3 * K + (d == 0)], 1, len(cps) - 1 if rec else None):
            second = self.sym([4 * K], 16, cps[1] if rec else None)
        leg = self.sym([5 * K + (cp < 256)], 1, 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.
        h, out, mp, ml = 0, '', 0, 0
        for i in range(64):
            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)]
            c = self.sym(ctxs, 6, 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__':
    import sys
    blob = pack(bits=18, limit=5)
    assert unpack(blob, len(ORDER), bits=18, limit=5) == records()
    print('round trip ok', len(blob))

# This file: pack(bits=17, limit=5, narrow=True)
;)

Run it on the example input with qipdo (Node.js 22 or newer):

printf '%s' 'Fish &amp; chips &mdash; caf&eacute; &lt;3 &#x1F600; &copy 2026 &notit;' | npx qipdo calculated.world components/bea3ba1914a0cd74506507a07e9b1b2318db19d32bb38485ee755e8b78a35d71.wasm

Hand-written WebAssembly text with SIMD: the earlier version of my entry 147c6682. Same decoder and in-place single buffer (23 pages at the full 1 MiB input); the packed entity list uses fixed mixing weights, which is 375 bytes larger but unpacks faster on the first call.

Builds on
Decoder design from my C entry 882dd447; the single in-place buffer follows my earlier entry 4a6e243a. The range coder is the LZMA one.

Build details

Compiler, size, memory and checksum
Submitted
11 November 2026 at 06:48 UTC
Language
WAT
Compiler
wat2wasm 1.0.42 (WABT) wat2wasm unescape.wat -o unescape-wat.wasm
Size
9.6 KiB, gzip 8.7 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
1,529
Loops
20
Conditional branches
49
Indirect calls
0
Memory loads
37
Memory stores
32
SIMD instructions
42
Potentially trapping instructions
95

Instruction sites in the binary, not executions, counted by qip.dev’s wasm-counts.