leptris 1.9.208.0 → 1.9.209.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +92 -21
- data/Rakefile +8 -7
- data/lib/leptris/vendor/aarch64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/aarch64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/arm-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/arm64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/ppc64le-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libleptris.so +0 -0
- data/lib/leptris/vendor/s390x-linux-musl/libutf8proc.so.3 +0 -0
- data/lib/leptris/vendor/x86_64-darwin/libleptris.dylib +0 -0
- data/lib/leptris/vendor/x86_64-linux/libleptris.so +0 -0
- data/lib/leptris/vendor/x86_64-linux-musl/libleptris.so +0 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/diff.rb +14 -0
- data/lib/leptris/xml/ffi.rb +0 -32
- data/vendor-src/libleptris/CHANGELOG.md +13 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/TODO.max-perf/00-INDEX.md +5 -1
- data/vendor-src/libleptris/TODO.max-perf/node-layout-fork.md +70 -0
- data/vendor-src/libleptris/src/leptris/html/html_parse.c +107 -117
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 700c8b243ae83bca888161a3db72b2e3c6304023c2d6876716d63cbc77046469
|
|
4
|
+
data.tar.gz: 3f5e149db82b4dd010e7a485650321bc752d74ee198e06b5711736781eca3281
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 87f6bfc69bcabf29bef4721bddc2947311ba021146912be96f34d043123611f0bc5230fc027855d897d42b7979ff7bed97d0b6df749bcaaf9f7b62cc642c6d26
|
|
7
|
+
data.tar.gz: 022fa14f46227d54e5966be7f0b90da028a2ff4bed14e4c2f2f4e26343a4a684c43770637077ab31657efb6c7a63af357285558cc4d25550156c55742c39f83a
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,87 @@ All notable changes to Leptris will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.209.0] - 2026-09-20
|
|
9
|
+
|
|
10
|
+
### Performance — engine sync (1.9.209)
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.209: WHATWG HTML parse round 2
|
|
13
|
+
(#1218) — the per-close-tag implied-end/open-side name-list
|
|
14
|
+
scans (40 + 33 names, ~7% of parse self-time on table-heavy
|
|
15
|
+
pages) became membership bits on the first-char-bucketed tag
|
|
16
|
+
classifier; bench_html_parse 24.1 → 25.1-26.7 MB/s. No
|
|
17
|
+
binding-facing surface changes; audit holds.
|
|
18
|
+
|
|
19
|
+
### Added — `Diff#summary` / `Diff#to_json` (the 1.9.200 output modes, #1184)
|
|
20
|
+
|
|
21
|
+
- The `leptris diff --summary` / `--json` CLI modes' library face:
|
|
22
|
+
per-kind op counts (`{update_attr: 1, insert: 1}`, `{}` when
|
|
23
|
+
identical) and the op list as JSON — pure Ruby over the existing
|
|
24
|
+
`#ops` face, no new engine symbols. Salvaged from #269's unique
|
|
25
|
+
half (its DTD half was superseded by the 1.9.204 wiring).
|
|
26
|
+
|
|
27
|
+
## [1.9.208.0] - 2026-09-20
|
|
28
|
+
|
|
29
|
+
### Fixed — engine sync: the #1242 TLS-memo corruption (1.9.208)
|
|
30
|
+
|
|
31
|
+
- Vendored libleptris 1.9.208: the TLS `(root, doc)` last-hit memo
|
|
32
|
+
in `leptris_element_get_document` trusted the root address and
|
|
33
|
+
was cleared only by document identity — an element-level
|
|
34
|
+
unregistration left `(freed_address, doc)` behind, and a
|
|
35
|
+
recycled address resolved the WRONG document, poisoning
|
|
36
|
+
attribute ownership (whole-run all-NULL
|
|
37
|
+
`leptris_element_attribute` reads; set_root's cross-document
|
|
38
|
+
check tripping EINVAL). The memo is now cleared whenever the
|
|
39
|
+
outgoing root address matches. `leptris_document_set_root` also
|
|
40
|
+
sets a fresh thread-local error on every rejection (no more
|
|
41
|
+
stale XPath messages), and `LEPTRIS_DEBUG_ATTR_MISS=1` dumps
|
|
42
|
+
attribute-chain state on lookup misses.
|
|
43
|
+
- **First IBM Z gem: `s390x-linux-musl`** ships from this release —
|
|
44
|
+
the 14th variant (qemu-built, in-container smoked). The release
|
|
45
|
+
build-musl containers needed `linux-headers` (the ffi gem's
|
|
46
|
+
bundled libffi includes `linux/limits.h` under musl; #283 fixed
|
|
47
|
+
the wrong apk line, #284 the one s390x runs). With #1197 fixed
|
|
48
|
+
(1.9.206) and strict s390x CI legs green since 1.9.207, the
|
|
49
|
+
big-endian platform is fully shippable.
|
|
50
|
+
- **CI un-quarantine where the evidence holds**: the s390x/ppc64le
|
|
51
|
+
qemu test legs are strict again, and so are the armv7/ppc64le/
|
|
52
|
+
s390x-musl release-build cells (#1174's ILP32 port shipped; arm
|
|
53
|
+
gems publish from every release since). The ubuntu-latest Ruby
|
|
54
|
+
3.4 leg stays non-blocking: strikes 7-10 on 1.9.208 show the
|
|
55
|
+
engine's memo clear was incomplete — in-spec forensics captured
|
|
56
|
+
a direct `leptris_element_attribute` call returning NULL with an
|
|
57
|
+
EMPTY chain on a live yielded element, and windows-4.0 produced
|
|
58
|
+
the fresh `set_root: element belongs to a different document`
|
|
59
|
+
for a same-document element. One mechanism: the TLS memo
|
|
60
|
+
resolving elements to a wrong document under address-recycling
|
|
61
|
+
interleavings. Tracked as leptris-ruby#279 / leptris/leptris#1242
|
|
62
|
+
with the dump env + forensics armed on the leg until the
|
|
63
|
+
residual engine fix lands.
|
|
64
|
+
- ffi.rb dedupe: the 1.9.206-surface block re-attached ten
|
|
65
|
+
symbols the 1.9.204 wiring had already attached (audit counted
|
|
66
|
+
345 attachments against 335 exports). The typed 1.9.206 block
|
|
67
|
+
is kept; audit 335/335.
|
|
68
|
+
|
|
69
|
+
## [1.9.207.0] - 2026-09-19
|
|
70
|
+
|
|
71
|
+
### Fixed — engine sync: big-endian close-tag parse (#1197 upstream)
|
|
72
|
+
|
|
73
|
+
- Vendored libleptris 1.9.207: the close-tag fast-path compare
|
|
74
|
+
masked the wrong end of its 8-byte name load on big-endian hosts,
|
|
75
|
+
rejecting ordinary paired-tag documents with "malformed input"
|
|
76
|
+
(434/727 binding-suite failures under qemu s390x). The mask now
|
|
77
|
+
follows memory order per byte dialect; the s390x-linux-musl
|
|
78
|
+
platform gem becomes viable with this release.
|
|
79
|
+
|
|
80
|
+
### Added — FFI mirror adopts the 1.9.202–1.9.207 engine surface
|
|
81
|
+
|
|
82
|
+
- audit:symbols lockstep attachments: recover diagnostics
|
|
83
|
+
(leptris_document_parse_diag[_count]), the standalone DTD family
|
|
84
|
+
(leptris_dtd_*), declaration/doctype removal, engine-heap buffer
|
|
85
|
+
allocation, and one-shot SAX. leptris_set_error stays internal —
|
|
86
|
+
the engine now hides it on ELF too (leptris#1243), so the export
|
|
87
|
+
surface agrees across Linux/macOS/Windows.
|
|
88
|
+
|
|
8
89
|
## [1.9.204.0] - 2026-09-19
|
|
9
90
|
|
|
10
91
|
### Added — document-level mutation completed: #1229 (the 1.9.204 surface)
|
|
@@ -51,29 +132,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
51
132
|
ppc64le qemu leg (ffi_spec:223) once the audit fix let the suite
|
|
52
133
|
run; the spec now asserts JVM ≡ host tier.
|
|
53
134
|
|
|
54
|
-
###
|
|
135
|
+
### Resolved upstream threads (final state)
|
|
55
136
|
|
|
137
|
+
- **#1220 (HTML mutations lost on serialization) — fixed in
|
|
138
|
+
1.9.204** (engine commit 737fe977 / #1228). Verified from the
|
|
139
|
+
binding on 1.9.208: the original reopen repro serializes the
|
|
140
|
+
mutation on both the whatwg and html4 lanes.
|
|
56
141
|
- **Iterparse attribute reads: all-nil runs on ubuntu-latest Ruby
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
rejecting ordinary paired-tag documents with "malformed input"
|
|
64
|
-
(434/727 binding-suite failures under qemu s390x). The mask now
|
|
65
|
-
follows memory order per byte dialect; the s390x-linux-musl
|
|
66
|
-
platform gem becomes viable with this release.
|
|
67
|
-
|
|
68
|
-
### Added — FFI mirror adopts the 1.9.202–1.9.207 engine surface
|
|
69
|
-
|
|
70
|
-
- audit:symbols lockstep attachments: recover diagnostics
|
|
71
|
-
(leptris_document_parse_diag[_count]), the standalone DTD family
|
|
72
|
-
(leptris_dtd_*), declaration/doctype removal, engine-heap buffer
|
|
73
|
-
allocation, and one-shot SAX. leptris_set_error stays internal —
|
|
74
|
-
the engine now hides it on ELF too (leptris#1243), so the export
|
|
75
|
-
surface agrees across Linux/macOS/Windows.
|
|
76
|
-
|
|
142
|
+
3.4 legs** — filed upstream as leptris/leptris#1242
|
|
143
|
+
(leptris-ruby#279 tracks the investigation). Originally
|
|
144
|
+
suspected binding-side, finally root-caused engine-side by
|
|
145
|
+
in-spec forensics (see the 1.9.208.0 entry); the engine's
|
|
146
|
+
1.9.208 fix is incomplete and the flake persists. The leg is
|
|
147
|
+
gated with diagnostics armed until the residual engine fix.
|
|
77
148
|
## [1.9.201.3] - 2026-09-19
|
|
78
149
|
|
|
79
150
|
### Added — the HTML facade (Nokogiri-shaped entry points)
|
data/Rakefile
CHANGED
|
@@ -8,7 +8,7 @@ RSpec::Core::RakeTask.new(:spec)
|
|
|
8
8
|
# Pin for `rake compile` and the platform-gem builds. Keep in lockstep
|
|
9
9
|
# with .github/workflows/build.yml (which calls `rake compile`) and the
|
|
10
10
|
# CHANGELOG when libleptris releases.
|
|
11
|
-
LIBLEPTRIS_VERSION = "1.9.
|
|
11
|
+
LIBLEPTRIS_VERSION = "1.9.209"
|
|
12
12
|
# Vendored alongside libleptris for fn:normalize-unicode (TODO
|
|
13
13
|
# .restructure/20): built per platform with a RELOCATABLE @rpath
|
|
14
14
|
# install name, loaded by ffi.rb before libleptris so the
|
|
@@ -77,13 +77,14 @@ task :compile do
|
|
|
77
77
|
fetch_tarball.call(
|
|
78
78
|
"https://api.github.com/repos/leptris/leptris/tarball/v#{version}", build)
|
|
79
79
|
|
|
80
|
-
# NOTE: 32-bit ARM (armv7)
|
|
80
|
+
# NOTE: 32-bit ARM (armv7) WAS an engine port task — the round-19
|
|
81
81
|
# ABI asserts pin layouts to 8-byte-pointer sizes and correctly
|
|
82
|
-
#
|
|
83
|
-
# #1174;
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
82
|
+
# aborted the build on 32-bit. Upstream shipped the ILP32 port
|
|
83
|
+
# (leptris/leptris#1174, closed; per-wordsize layout pins +
|
|
84
|
+
# inline-value gate landed in 1.9.199) and arm-linux /
|
|
85
|
+
# arm-linux-musl gems publish from every release since — the
|
|
86
|
+
# emulated release legs are strict again. We patch nothing here;
|
|
87
|
+
# the emulated-leg smoke validates whatever the engine provides.
|
|
87
88
|
|
|
88
89
|
# utf8proc: shared build only, @rpath install name, local prefix.
|
|
89
90
|
u8_dir = File.expand_path("tmp/utf8proc-#{UTF8PROC_VERSION}", __dir__)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/diff.rb
CHANGED
|
@@ -71,4 +71,18 @@ class Leptris::XML::Diff
|
|
|
71
71
|
str_ptr = Leptris::XML::FFI.leptris_diff_serialize(@handle)
|
|
72
72
|
Leptris::XML::FFI.read_owned_string(str_ptr)
|
|
73
73
|
end
|
|
74
|
+
|
|
75
|
+
# Per-kind op counts (#1184): {update_attr: 1, insert: 1, ...} —
|
|
76
|
+
# {} when identical.
|
|
77
|
+
def summary
|
|
78
|
+
ops.each_with_object(Hash.new(0)) { |op, h| h[op[:type]] += 1 }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The op list as JSON (#1184) — the same shape #ops yields, with
|
|
82
|
+
# stringified ops. Requires the json standard library lazily.
|
|
83
|
+
def to_json(*args)
|
|
84
|
+
require "json"
|
|
85
|
+
ops.map { |op| op.transform_values { |v| v.nil? ? nil : v.to_s } }
|
|
86
|
+
.to_json(*args)
|
|
87
|
+
end
|
|
74
88
|
end
|
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -386,12 +386,6 @@ attach_function :leptris_parse_string,
|
|
|
386
386
|
[:leptris_document, :int], :leptris_status
|
|
387
387
|
attach_function :leptris_document_set_doctype,
|
|
388
388
|
[:leptris_document, :string, :string, :string], :leptris_doctype
|
|
389
|
-
# The #1229 remove-half (libleptris 1.9.204): un-set the
|
|
390
|
-
# declaration; un-set the DOCTYPE (NOT_FOUND when none set).
|
|
391
|
-
attach_function :leptris_document_clear_declaration,
|
|
392
|
-
[:leptris_document], :leptris_status
|
|
393
|
-
attach_function :leptris_document_remove_doctype,
|
|
394
|
-
[:leptris_document], :leptris_status
|
|
395
389
|
attach_function :leptris_document_append_pi,
|
|
396
390
|
[:leptris_document, :string, :string], :leptris_node_ref
|
|
397
391
|
attach_function :leptris_document_remove_child,
|
|
@@ -860,14 +854,6 @@ attach_function :leptris_parse_string,
|
|
|
860
854
|
[:leptris_sax_parser], :void
|
|
861
855
|
attach_function :leptris_sax_parser_set_streaming,
|
|
862
856
|
[:leptris_sax_parser, :int], :int
|
|
863
|
-
# One-shot input hint (libleptris 1.9.204): whole document in
|
|
864
|
-
# a single feed(), alive for the parser's lifetime — enables
|
|
865
|
-
# in-place scans. The binding's one-shot entry
|
|
866
|
-
# (leptris_sax_parse) is one-shot by construction; the engine
|
|
867
|
-
# sets this itself for pull's memory source. Attach-only
|
|
868
|
-
# until a chunked-feed face wants to opt in.
|
|
869
|
-
attach_function :leptris_sax_parser_set_one_shot,
|
|
870
|
-
[:leptris_sax_parser, :int], :void
|
|
871
857
|
|
|
872
858
|
# Pull parsing (v1.6.0): StAX-style cursor over a document.
|
|
873
859
|
attach_function :leptris_pull_new,
|
|
@@ -1053,19 +1039,6 @@ attach_function :leptris_parse_string,
|
|
|
1053
1039
|
:line, :int,
|
|
1054
1040
|
:column, :int
|
|
1055
1041
|
end
|
|
1056
|
-
attach_function :leptris_dtd_parse,
|
|
1057
|
-
[:string, :size_t], :pointer
|
|
1058
|
-
# leptris_document_get_dtd attached above with the readers
|
|
1059
|
-
attach_function :leptris_dtd_parse_external_subset,
|
|
1060
|
-
[:pointer, :string, :size_t], :int
|
|
1061
|
-
attach_function :leptris_dtd_set_pe_loader,
|
|
1062
|
-
[:pointer, :pointer, :pointer], :void
|
|
1063
|
-
attach_function :leptris_dtd_validate,
|
|
1064
|
-
[:leptris_document, :pointer, :pointer], :int
|
|
1065
|
-
attach_function :leptris_dtd_free,
|
|
1066
|
-
[:pointer], :void
|
|
1067
|
-
attach_function :leptris_dtd_error_free,
|
|
1068
|
-
[:pointer], :void
|
|
1069
1042
|
|
|
1070
1043
|
attach_function :leptris_rng_parse,
|
|
1071
1044
|
[:string, :size_t, :pointer], :leptris_relaxng
|
|
@@ -1143,11 +1116,6 @@ attach_function :leptris_parse_string,
|
|
|
1143
1116
|
[:leptris_doctype], :string
|
|
1144
1117
|
|
|
1145
1118
|
attach_function :leptris_free_string, [:pointer], :void
|
|
1146
|
-
# Engine-heap allocation (libleptris 1.9.204): buffers handed
|
|
1147
|
-
# to engine callbacks (the DTD PE loader) so the engine frees
|
|
1148
|
-
# through its matching deallocation path. Release with
|
|
1149
|
-
# leptris_free_string when NOT consumed by the engine.
|
|
1150
|
-
attach_function :leptris_alloc_buffer, [:size_t], :pointer
|
|
1151
1119
|
attach_function :leptris_explicit_cleanup, [], :void
|
|
1152
1120
|
attach_function :leptris_set_memory_management_functions,
|
|
1153
1121
|
[:pointer, :pointer], :void
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.209] - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### Performance
|
|
6
|
+
|
|
7
|
+
- WHATWG HTML parse, #1218 round 2: the per-close-tag name-list
|
|
8
|
+
scans (a 40-name implied-end set and a 33-name open-side set,
|
|
9
|
+
~7% of parse self-time on table-heavy pages) are now membership
|
|
10
|
+
bits on the first-char-bucketed tag classifier — both lookups
|
|
11
|
+
are one bucket walk, sets preserved bit-for-bit.
|
|
12
|
+
bench_html_parse plain fixture: 24.1 → 25.1-26.7 MB/s.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [1.9.208] - 2026-09-19
|
|
4
17
|
|
|
5
18
|
### Fixed
|
|
@@ -9,7 +9,7 @@ RED bench first where perf claims are made.
|
|
|
9
9
|
| 1 | SIMD/memchr HTML tokenizer scans | perf | memchr ×2 (#1176/#1190) + LUT (#1199, +30%/+51%) | DONE v1.9.199 |
|
|
10
10
|
| 7 | leptris validate CLI + bindings | feature | engine half merged #1198 (--dtd + DTD engine fixes); bindings remain | half done |
|
|
11
11
|
| 8 | native XML diff consumer surface | feature | CLI diff text/JSON/--summary + engine API shipped | DONE (#1184 closed) |
|
|
12
|
-
| 4 | compact-node micro-opts (non-layout) | perf |
|
|
12
|
+
| 4 | compact-node micro-opts (non-layout) | perf | residual = node-creation cost; SUPERSEDED by the node-layout fork (TODO.max-perf/node-layout-fork.md, owner green-lit 2026-09-20) | folded into fork |
|
|
13
13
|
| 2 | compiled-dispatch tokenizer | perf | slices 1+3 landed #1236; slice 2's class LUT + dense switch pre-existed; residual = iterparse consumer (row 3) | mostly done |
|
|
14
14
|
| 5 | XSLT 2x per-bench profile rounds (#682) | perf | medium, ongoing | in progress |
|
|
15
15
|
| 6 | language tails (QT3/dates/fn-items/lane 15) | correctness | medium-large | queued |
|
|
@@ -20,3 +20,7 @@ Portability wave 2026-09-18 (v1.9.199): ILP32 layout port #1193 —
|
|
|
20
20
|
per-wordsize pins, SSE2 gate, xsl:number 64-bit chain, ILP32 +
|
|
21
21
|
native-arm64 + Intel-macOS CI legs, concurrency groups everywhere.
|
|
22
22
|
Next portability gate: s390x big-endian (#1194, PR #1202).
|
|
23
|
+
|
|
24
|
+
The node-layout fork (row 4 successor, owner green-lit 2026-09-20):
|
|
25
|
+
TODO.max-perf/node-layout-fork.md — 5 slices, addresses #1222 +
|
|
26
|
+
#1179 residual + #1238 parse side.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# The node-layout fork — pugixml-class allocation for the DOM
|
|
2
|
+
|
|
3
|
+
Status: DESIGN (2026-09-20). Owner green-light received. This is
|
|
4
|
+
the durable fix for #1222 (2-5.9x vs pugixml large docs), #1179's
|
|
5
|
+
residual node-creation cost, and the parse-side constant factor of
|
|
6
|
+
#1238. Measured basis: scaling is LINEAR on serialbench fixtures
|
|
7
|
+
(352-467 MB/s across 1.5-13MB) — the gap is a per-node constant
|
|
8
|
+
cost, not complexity.
|
|
9
|
+
|
|
10
|
+
## Why the gap exists (measured)
|
|
11
|
+
|
|
12
|
+
Every element today costs: pool_alloc (block chain + freelist
|
|
13
|
+
recycler bookkeeping) + compact-edge encoding (cp16 + overflow
|
|
14
|
+
table on out-of-range pairs) + root-doc registration for detached
|
|
15
|
+
nodes + name materialization through the namebp/inline paths.
|
|
16
|
+
pugixml pays: one bump-pointer increment inside ONE document-sized
|
|
17
|
+
block, with node "pointers" as intra-block offsets and names
|
|
18
|
+
interned into a shared char arena. Constant-factor difference per
|
|
19
|
+
node, multiplied by millions of nodes.
|
|
20
|
+
|
|
21
|
+
## Target layout (pugixml-class, adapted)
|
|
22
|
+
|
|
23
|
+
- ONE primary allocation per document (grown geometrically,
|
|
24
|
+
never per-node): node array with intra-block integer offsets for
|
|
25
|
+
parent/first-child/next-sibling/first-attr edges. No cp16
|
|
26
|
+
overflow table (offsets are block-relative and always
|
|
27
|
+
representable), no root-doc map for parse-created nodes (block
|
|
28
|
+
base implies the document).
|
|
29
|
+
- String arena beside the node array: element/attr names
|
|
30
|
+
interned by (ptr,len) hash into the block; parse buffers stay
|
|
31
|
+
immutable (#1125) and zero-copy views remain the primary
|
|
32
|
+
representation — interning only replaces the per-node copies.
|
|
33
|
+
- MUTATION-CREATED nodes keep today's paths initially (mut blocks
|
|
34
|
+
+ root-doc map) behind the same public API; the internal
|
|
35
|
+
get_document walk resolves by block base first. Migration of
|
|
36
|
+
mutation paths is a later slice, gated by the same benches.
|
|
37
|
+
- ABI unchanged: every public handle is already opaque; the
|
|
38
|
+
compact-pointer exports and element-size check (VALIDATION.md)
|
|
39
|
+
get new expected values.
|
|
40
|
+
|
|
41
|
+
## Slices (one PR each, RED bench first)
|
|
42
|
+
|
|
43
|
+
1. **Document block allocator**: the growable block + offset-edge
|
|
44
|
+
node struct, PARALLEL to the pool (parse path only, behind a
|
|
45
|
+
flag). Gate: no test changes; element-size check documents both.
|
|
46
|
+
2. **Parser wiring**: flat/direct_parse + html_parse build into
|
|
47
|
+
block storage. Gate: full ctest + html5lib corpus 1169 floor;
|
|
48
|
+
RED bench: serialbench shapes to >=350 MB/s at 13MB (close the
|
|
49
|
+
libxml2 gap, halve the pugixml gap).
|
|
50
|
+
3. **Name interning**: shared arena replaces per-node name copies.
|
|
51
|
+
Gate: attr/name read benches (many-attrs, first-access) no
|
|
52
|
+
regression; memory RSS down on name-heavy docs.
|
|
53
|
+
4. **Compact-edge retirement for parse trees**: drop overflow-table
|
|
54
|
+
traffic on the block path; mutation keeps the old encode until
|
|
55
|
+
slice 5.
|
|
56
|
+
5. **Mutation unification** (optional, last): programmatic
|
|
57
|
+
create/append onto the block. Gate: append <= pugixml (the
|
|
58
|
+
lane-18 P1 row).
|
|
59
|
+
|
|
60
|
+
## Non-goals / invariants
|
|
61
|
+
|
|
62
|
+
- No public API or ABI change (opaque handles).
|
|
63
|
+
- Digest (#869), diff, c14n correctness rides the same node
|
|
64
|
+
accessors — all read through the inline edge helpers, which
|
|
65
|
+
become offset arithmetic.
|
|
66
|
+
- The address-keyed TLS caches (root-doc map/memo) survive for
|
|
67
|
+
MUTATION nodes only; their invalidation contracts (#1242/#1038)
|
|
68
|
+
must be re-gated, not assumed.
|
|
69
|
+
|
|
70
|
+
Effort: medium-large. Every slice independently shippable.
|
|
@@ -2189,40 +2189,36 @@ static int h_closes_ww(const char* open, const char* start) {
|
|
|
2189
2189
|
/* WHATWG: starts that close an open p in button scope
|
|
2190
2190
|
* (13.2.6.4.7 "in body" block set + h/pre/form/li/dd/dt/plaintext/
|
|
2191
2191
|
* hr/xmp). */
|
|
2192
|
+
typedef struct {
|
|
2193
|
+
char name[12];
|
|
2194
|
+
uint8_t len;
|
|
2195
|
+
uint8_t special; /* WHATWG 13.2.4 special set */
|
|
2196
|
+
uint8_t no_fmt; /* never reconstructed (formatting) */
|
|
2197
|
+
uint8_t closes; /* bit0: h_p_closes implied-end set; bit1:
|
|
2198
|
+
* k_p_closers — replaced the per-close-tag
|
|
2199
|
+
* strcmp scans (#1218 profile). */
|
|
2200
|
+
} HTagInfo;
|
|
2201
|
+
|
|
2202
|
+
static const HTagInfo* h_tag_lookup(const char* n);
|
|
2203
|
+
|
|
2204
|
+
static const HTagInfo* h_tag_lookup(const char* n);
|
|
2205
|
+
|
|
2192
2206
|
static int h_p_closes(const char* start) {
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
"p", "search", "section", "summary", "ul",
|
|
2199
|
-
"h1", "h2", "h3", "h4", "h5",
|
|
2200
|
-
"h6", "pre", "listing", "form", "li",
|
|
2201
|
-
"dd", "dt", "plaintext", "hr", "xmp",
|
|
2202
|
-
NULL};
|
|
2203
|
-
for (int i = 0; k[i]; i++)
|
|
2204
|
-
if (strcmp(start, k[i]) == 0) return 1;
|
|
2205
|
-
return 0;
|
|
2207
|
+
/* Classifier hit (round: #1218 profile — the 40-name strcmp
|
|
2208
|
+
* scan was the top WHATWG self-time on table-heavy pages).
|
|
2209
|
+
* Sets preserved bit-for-bit from the old k[] list. */
|
|
2210
|
+
const HTagInfo* t = h_tag_lookup(start);
|
|
2211
|
+
return t && (t->closes & 1);
|
|
2206
2212
|
}
|
|
2207
2213
|
|
|
2208
2214
|
/* Implied-end sets: a start tag in `closes` closes any open
|
|
2209
2215
|
* element named `name` (HTML4 §7.5.4 / table model). */
|
|
2210
|
-
static const char* const k_p_closers[] = {
|
|
2211
|
-
"address", "article", "aside", "blockquote", "details", "dialog",
|
|
2212
|
-
"dir", "div", "dl", "fieldset", "figcaption", "figure", "footer",
|
|
2213
|
-
"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup",
|
|
2214
|
-
"hr", "li", "main", "menu", "nav", "ol", "p", "pre", "section",
|
|
2215
|
-
"table", "ul", NULL,
|
|
2216
|
-
};
|
|
2217
|
-
static int h_in_list(const char* const* list, const char* name) {
|
|
2218
|
-
for (int i = 0; list[i]; i++)
|
|
2219
|
-
if (strcmp(list[i], name) == 0) return 1;
|
|
2220
|
-
return 0;
|
|
2221
|
-
}
|
|
2222
2216
|
/* Does starting `start` close an open element named `open`? */
|
|
2223
2217
|
static int h_closes(const char* open, const char* start) {
|
|
2224
|
-
if (strcmp(open, "p") == 0
|
|
2225
|
-
|
|
2218
|
+
if (strcmp(open, "p") == 0) {
|
|
2219
|
+
const HTagInfo* t = h_tag_lookup(start);
|
|
2220
|
+
if (t && (t->closes & 2)) return 1;
|
|
2221
|
+
}
|
|
2226
2222
|
if (strcmp(open, "li") == 0 && strcmp(start, "li") == 0)
|
|
2227
2223
|
return 1;
|
|
2228
2224
|
if ((strcmp(open, "dt") == 0 || strcmp(open, "dd") == 0) &&
|
|
@@ -3066,98 +3062,92 @@ static int h_is_formatting(const char* n) {
|
|
|
3066
3062
|
* sampler pinned at ~21% of WHATWG parse (h_is_special_ww +
|
|
3067
3063
|
* h_reconstructs). Keep in sync with the WHATWG special and
|
|
3068
3064
|
* no-formatting sets. */
|
|
3069
|
-
typedef struct {
|
|
3070
|
-
char name[12];
|
|
3071
|
-
uint8_t len;
|
|
3072
|
-
uint8_t special; /* WHATWG 13.2.4 special set */
|
|
3073
|
-
uint8_t no_fmt; /* never reconstructed (formatting) */
|
|
3074
|
-
} HTagInfo;
|
|
3075
3065
|
|
|
3076
3066
|
static const HTagInfo h_tag_infos[] = {
|
|
3077
|
-
{"address", 7, 1, 1},
|
|
3078
|
-
{"applet", 6, 1, 0},
|
|
3079
|
-
{"area", 4, 1, 0},
|
|
3080
|
-
{"article", 7, 1, 1},
|
|
3081
|
-
{"aside", 5, 1, 1},
|
|
3082
|
-
{"base", 4, 1, 1},
|
|
3083
|
-
{"basefont", 8, 1, 1},
|
|
3084
|
-
{"bgsound", 7, 1, 1},
|
|
3085
|
-
{"blockquote", 10, 1, 1},
|
|
3086
|
-
{"body", 4, 1, 1},
|
|
3087
|
-
{"br", 2, 1, 0},
|
|
3088
|
-
{"button", 6, 1, 0},
|
|
3089
|
-
{"caption", 7, 1, 1},
|
|
3090
|
-
{"center", 6, 1, 1},
|
|
3091
|
-
{"col", 3, 1, 1},
|
|
3092
|
-
{"colgroup", 8, 1, 1},
|
|
3093
|
-
{"dd", 2, 1, 1},
|
|
3094
|
-
{"details", 7, 1, 1},
|
|
3095
|
-
{"dialog", 6, 1, 1},
|
|
3096
|
-
{"dir", 3, 1, 1},
|
|
3097
|
-
{"div", 3, 1, 1},
|
|
3098
|
-
{"dl", 2, 1, 1},
|
|
3099
|
-
{"dt", 2, 1, 1},
|
|
3100
|
-
{"embed", 5, 1, 0},
|
|
3101
|
-
{"fieldset", 8, 1, 1},
|
|
3102
|
-
{"figcaption", 10, 1, 1},
|
|
3103
|
-
{"figure", 6, 1, 1},
|
|
3104
|
-
{"footer", 6, 1, 1},
|
|
3105
|
-
{"form", 4, 1, 1},
|
|
3106
|
-
{"frame", 5, 1, 1},
|
|
3107
|
-
{"frameset", 8, 1, 1},
|
|
3108
|
-
{"h1", 2, 1, 1},
|
|
3109
|
-
{"h2", 2, 1, 1},
|
|
3110
|
-
{"h3", 2, 1, 1},
|
|
3111
|
-
{"h4", 2, 1, 1},
|
|
3112
|
-
{"h5", 2, 1, 1},
|
|
3113
|
-
{"h6", 2, 1, 1},
|
|
3114
|
-
{"head", 4, 1, 1},
|
|
3115
|
-
{"header", 6, 1, 1},
|
|
3116
|
-
{"hgroup", 6, 1, 1},
|
|
3117
|
-
{"hr", 2, 1, 1},
|
|
3118
|
-
{"html", 4, 1, 1},
|
|
3119
|
-
{"iframe", 6, 1, 0},
|
|
3120
|
-
{"img", 3, 1, 0},
|
|
3121
|
-
{"input", 5, 1, 0},
|
|
3122
|
-
{"keygen", 6, 1, 0},
|
|
3123
|
-
{"li", 2, 1, 1},
|
|
3124
|
-
{"link", 4, 1, 1},
|
|
3125
|
-
{"listing", 7, 1, 1},
|
|
3126
|
-
{"main", 4, 1, 1},
|
|
3127
|
-
{"marquee", 7, 1, 0},
|
|
3128
|
-
{"menu", 4, 1, 1},
|
|
3129
|
-
{"meta", 4, 1, 1},
|
|
3130
|
-
{"nav", 3, 1, 1},
|
|
3131
|
-
{"noembed", 7, 1, 0},
|
|
3132
|
-
{"noframes", 8, 1, 1},
|
|
3133
|
-
{"noscript", 8, 1, 0},
|
|
3134
|
-
{"object", 6, 1, 0},
|
|
3135
|
-
{"ol", 2, 1, 1},
|
|
3136
|
-
{"p", 1, 1, 1},
|
|
3137
|
-
{"param", 5, 1, 0},
|
|
3138
|
-
{"plaintext", 9, 1, 1},
|
|
3139
|
-
{"pre", 3, 1, 1},
|
|
3140
|
-
{"script", 6, 1, 1},
|
|
3141
|
-
{"search", 6, 1, 1},
|
|
3142
|
-
{"section", 7, 1, 1},
|
|
3143
|
-
{"select", 6, 1, 0},
|
|
3144
|
-
{"source", 6, 1, 0},
|
|
3145
|
-
{"style", 5, 1, 1},
|
|
3146
|
-
{"summary", 7, 1, 1},
|
|
3147
|
-
{"table", 5, 1, 1},
|
|
3148
|
-
{"tbody", 5, 1, 1},
|
|
3149
|
-
{"td", 2, 1, 1},
|
|
3150
|
-
{"template", 8, 1, 1},
|
|
3151
|
-
{"textarea", 8, 1, 1},
|
|
3152
|
-
{"tfoot", 5, 1, 1},
|
|
3153
|
-
{"th", 2, 1, 1},
|
|
3154
|
-
{"thead", 5, 1, 1},
|
|
3155
|
-
{"title", 5, 1, 1},
|
|
3156
|
-
{"tr", 2, 1, 1},
|
|
3157
|
-
{"track", 5, 1, 0},
|
|
3158
|
-
{"ul", 2, 1, 1},
|
|
3159
|
-
{"wbr", 3, 1, 0},
|
|
3160
|
-
{"xmp", 3, 1, 0},
|
|
3067
|
+
{"address", 7, 1, 1, 3},
|
|
3068
|
+
{"applet", 6, 1, 0, 0},
|
|
3069
|
+
{"area", 4, 1, 0, 0},
|
|
3070
|
+
{"article", 7, 1, 1, 3},
|
|
3071
|
+
{"aside", 5, 1, 1, 3},
|
|
3072
|
+
{"base", 4, 1, 1, 0},
|
|
3073
|
+
{"basefont", 8, 1, 1, 0},
|
|
3074
|
+
{"bgsound", 7, 1, 1, 0},
|
|
3075
|
+
{"blockquote", 10, 1, 1, 3},
|
|
3076
|
+
{"body", 4, 1, 1, 0},
|
|
3077
|
+
{"br", 2, 1, 0, 0},
|
|
3078
|
+
{"button", 6, 1, 0, 0},
|
|
3079
|
+
{"caption", 7, 1, 1, 0},
|
|
3080
|
+
{"center", 6, 1, 1, 1},
|
|
3081
|
+
{"col", 3, 1, 1, 0},
|
|
3082
|
+
{"colgroup", 8, 1, 1, 0},
|
|
3083
|
+
{"dd", 2, 1, 1, 1},
|
|
3084
|
+
{"details", 7, 1, 1, 3},
|
|
3085
|
+
{"dialog", 6, 1, 1, 3},
|
|
3086
|
+
{"dir", 3, 1, 1, 3},
|
|
3087
|
+
{"div", 3, 1, 1, 3},
|
|
3088
|
+
{"dl", 2, 1, 1, 3},
|
|
3089
|
+
{"dt", 2, 1, 1, 1},
|
|
3090
|
+
{"embed", 5, 1, 0, 0},
|
|
3091
|
+
{"fieldset", 8, 1, 1, 3},
|
|
3092
|
+
{"figcaption", 10, 1, 1, 3},
|
|
3093
|
+
{"figure", 6, 1, 1, 3},
|
|
3094
|
+
{"footer", 6, 1, 1, 3},
|
|
3095
|
+
{"form", 4, 1, 1, 3},
|
|
3096
|
+
{"frame", 5, 1, 1, 0},
|
|
3097
|
+
{"frameset", 8, 1, 1, 0},
|
|
3098
|
+
{"h1", 2, 1, 1, 3},
|
|
3099
|
+
{"h2", 2, 1, 1, 3},
|
|
3100
|
+
{"h3", 2, 1, 1, 3},
|
|
3101
|
+
{"h4", 2, 1, 1, 3},
|
|
3102
|
+
{"h5", 2, 1, 1, 3},
|
|
3103
|
+
{"h6", 2, 1, 1, 3},
|
|
3104
|
+
{"head", 4, 1, 1, 0},
|
|
3105
|
+
{"header", 6, 1, 1, 3},
|
|
3106
|
+
{"hgroup", 6, 1, 1, 3},
|
|
3107
|
+
{"hr", 2, 1, 1, 3},
|
|
3108
|
+
{"html", 4, 1, 1, 0},
|
|
3109
|
+
{"iframe", 6, 1, 0, 0},
|
|
3110
|
+
{"img", 3, 1, 0, 0},
|
|
3111
|
+
{"input", 5, 1, 0, 0},
|
|
3112
|
+
{"keygen", 6, 1, 0, 0},
|
|
3113
|
+
{"li", 2, 1, 1, 3},
|
|
3114
|
+
{"link", 4, 1, 1, 0},
|
|
3115
|
+
{"listing", 7, 1, 1, 1},
|
|
3116
|
+
{"main", 4, 1, 1, 3},
|
|
3117
|
+
{"marquee", 7, 1, 0, 0},
|
|
3118
|
+
{"menu", 4, 1, 1, 3},
|
|
3119
|
+
{"meta", 4, 1, 1, 0},
|
|
3120
|
+
{"nav", 3, 1, 1, 3},
|
|
3121
|
+
{"noembed", 7, 1, 0, 0},
|
|
3122
|
+
{"noframes", 8, 1, 1, 0},
|
|
3123
|
+
{"noscript", 8, 1, 0, 0},
|
|
3124
|
+
{"object", 6, 1, 0, 0},
|
|
3125
|
+
{"ol", 2, 1, 1, 3},
|
|
3126
|
+
{"p", 1, 1, 1, 3},
|
|
3127
|
+
{"param", 5, 1, 0, 0},
|
|
3128
|
+
{"plaintext", 9, 1, 1, 1},
|
|
3129
|
+
{"pre", 3, 1, 1, 3},
|
|
3130
|
+
{"script", 6, 1, 1, 0},
|
|
3131
|
+
{"search", 6, 1, 1, 1},
|
|
3132
|
+
{"section", 7, 1, 1, 3},
|
|
3133
|
+
{"select", 6, 1, 0, 0},
|
|
3134
|
+
{"source", 6, 1, 0, 0},
|
|
3135
|
+
{"style", 5, 1, 1, 0},
|
|
3136
|
+
{"summary", 7, 1, 1, 1},
|
|
3137
|
+
{"table", 5, 1, 1, 2},
|
|
3138
|
+
{"tbody", 5, 1, 1, 0},
|
|
3139
|
+
{"td", 2, 1, 1, 0},
|
|
3140
|
+
{"template", 8, 1, 1, 0},
|
|
3141
|
+
{"textarea", 8, 1, 1, 0},
|
|
3142
|
+
{"tfoot", 5, 1, 1, 0},
|
|
3143
|
+
{"th", 2, 1, 1, 0},
|
|
3144
|
+
{"thead", 5, 1, 1, 0},
|
|
3145
|
+
{"title", 5, 1, 1, 0},
|
|
3146
|
+
{"tr", 2, 1, 1, 0},
|
|
3147
|
+
{"track", 5, 1, 0, 0},
|
|
3148
|
+
{"ul", 2, 1, 1, 3},
|
|
3149
|
+
{"wbr", 3, 1, 0, 0},
|
|
3150
|
+
{"xmp", 3, 1, 0, 1},
|
|
3161
3151
|
};
|
|
3162
3152
|
|
|
3163
3153
|
/* bucket[first_char] = start index; bucket[first_char+1] = end */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json",
|
|
3
3
|
"name": "leptris",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.209",
|
|
5
5
|
"description": "High-performance XML parser and XPath 1.0 engine in C (libleptris)",
|
|
6
6
|
"homepage": "https://github.com/leptris/leptris",
|
|
7
7
|
"license": "MIT",
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: leptris
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.9.
|
|
4
|
+
version: 1.9.209.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|
|
@@ -154,6 +154,8 @@ files:
|
|
|
154
154
|
- lib/leptris/vendor/arm64-darwin/libutf8proc.3.dylib
|
|
155
155
|
- lib/leptris/vendor/ppc64le-linux/libleptris.so
|
|
156
156
|
- lib/leptris/vendor/ppc64le-linux/libutf8proc.so.3
|
|
157
|
+
- lib/leptris/vendor/s390x-linux-musl/libleptris.so
|
|
158
|
+
- lib/leptris/vendor/s390x-linux-musl/libutf8proc.so.3
|
|
157
159
|
- lib/leptris/vendor/x86_64-darwin/libleptris.dylib
|
|
158
160
|
- lib/leptris/vendor/x86_64-darwin/libutf8proc.3.dylib
|
|
159
161
|
- lib/leptris/vendor/x86_64-linux-musl/libleptris.so
|
|
@@ -246,6 +248,7 @@ files:
|
|
|
246
248
|
- vendor-src/libleptris/TODO.max-perf/7-validate-bindings.md
|
|
247
249
|
- vendor-src/libleptris/TODO.max-perf/8-native-xml-diff-consumer.md
|
|
248
250
|
- vendor-src/libleptris/TODO.max-perf/9-prebuilt-gem-matrix.md
|
|
251
|
+
- vendor-src/libleptris/TODO.max-perf/node-layout-fork.md
|
|
249
252
|
- vendor-src/libleptris/TODO.md
|
|
250
253
|
- vendor-src/libleptris/TODO.transform/README.md
|
|
251
254
|
- vendor-src/libleptris/TODO.validate/README.md
|