leptris 1.9.391.0-arm-linux → 1.9.396.0-arm-linux
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/Rakefile +9 -3
- data/ext/leptris/native/native.c +30 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/ffi.rb +5 -0
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/leptris/xml.rb +25 -0
- data/lib/libleptris.so +0 -0
- data/vendor-src/README.md +1 -1
- data/vendor-src/libleptris/CHANGELOG.md +146 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/README.adoc +37 -0
- data/vendor-src/libleptris/src/CMakeLists.txt +12 -5
- data/vendor-src/libleptris/src/include/leptris/types.h +52 -0
- data/vendor-src/libleptris/src/include/leptris.h +6 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.c +61 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text.h +13 -0
- data/vendor-src/libleptris/src/leptris/common/simd_text_avx2.c +156 -2
- data/vendor-src/libleptris/src/leptris/common/simd_text_neon.c +113 -0
- data/vendor-src/libleptris/src/leptris/core.c +31 -0
- data/vendor-src/libleptris/src/leptris/flat/direct_parse.c +19 -43
- data/vendor-src/libleptris/src/leptris/xpath/parser.c +11 -29
- data/vendor-src/libleptris/src/leptris/xsd/compile.c +82 -8
- data/vendor-src/libleptris/src/leptris/xsd/validate.c +36 -4
- data/vendor-src/libleptris/src/leptris/xsd/xsd_internal.h +8 -0
- data/vendor-src/libleptris/test/CMakeLists.txt +18 -0
- data/vendor-src/libleptris/test/abi/test_capabilities.cpp +112 -0
- data/vendor-src/libleptris/test/dom/test_entity_refs.cpp +111 -0
- data/vendor-src/libleptris/test/parser/test_encoding.cpp +115 -0
- data/vendor-src/libleptris/test/parser/test_utf8_simd.cpp +130 -0
- data/vendor-src/libleptris/test/xsd/test_xsd_compile.cpp +299 -3
- 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: d9946e2b50f4e9284001bb923683803b7cca891c28154e28129c4a079a6ace83
|
|
4
|
+
data.tar.gz: 1da14aa052d16d328f2c49ac9a59f023b8ee192324b0d8e510402230e1e8866e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 89551eed859940f012e3e49b276c0f4b998597a803bb2cfab1eb73d2c7787ccd753a3040946a410bd1fc2b8fe00b436fecaa5a37012defe57994a783861dba9e
|
|
7
|
+
data.tar.gz: a775d3e6462e2b1dea2eaabcc21239b7a6d0b6df02e9a10020033d5b03d0f34141ee68ef49bdb4355847ea65891cbd13bb93896d0ca6d948f85f677203ac8fac
|
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.396"
|
|
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
|
|
@@ -401,8 +401,14 @@ namespace :audit do
|
|
|
401
401
|
.map { |n| n.sub(/\A_/, "") }
|
|
402
402
|
.select { |n| n.start_with?("leptris_") }
|
|
403
403
|
.map { |n| n.sub(/\Aleptris_/, "") }.sort
|
|
404
|
-
attached =
|
|
405
|
-
.
|
|
404
|
+
attached = (
|
|
405
|
+
File.read("lib/leptris/xml/ffi.rb")
|
|
406
|
+
.scan(/attach_function :leptris_([a-z_0-9]+)/).flatten +
|
|
407
|
+
# The native shim's C->C dlsym dispatches (#572: struct-value
|
|
408
|
+
# returns libffi can't carry) are bound surface too.
|
|
409
|
+
File.read("ext/leptris/native/native.c")
|
|
410
|
+
.scan(/lib_sym\((?:h|handle), "leptris_([a-z_0-9]+)"\)/).flatten
|
|
411
|
+
).sort
|
|
406
412
|
# Version-gated faces: soft-attached because engines BEFORE the
|
|
407
413
|
# named release lack the symbol (the load must survive them).
|
|
408
414
|
# They join the audit only when the engine pin reaches that
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -278,6 +278,14 @@ static void *lib_open(const char *path) { return dlopen(path, RTLD_NOW); }
|
|
|
278
278
|
static void *lib_sym(void *h, const char *name) { return dlsym(h, name); }
|
|
279
279
|
#endif
|
|
280
280
|
|
|
281
|
+
/* leptris-ruby#572: the engine's capability descriptor (1.9.392+). */
|
|
282
|
+
typedef struct {
|
|
283
|
+
unsigned long long caps;
|
|
284
|
+
unsigned descriptor_version;
|
|
285
|
+
} rb_caps_t;
|
|
286
|
+
typedef rb_caps_t (*capabilities_fn)(void);
|
|
287
|
+
static capabilities_fn f_capabilities;
|
|
288
|
+
|
|
281
289
|
static void resolve_symbols(const char *lib_path)
|
|
282
290
|
{
|
|
283
291
|
void *h = lib_open(lib_path);
|
|
@@ -314,6 +322,7 @@ static void resolve_symbols(const char *lib_path)
|
|
|
314
322
|
/* Single-crossing element construction (libleptris 1.9.237,
|
|
315
323
|
* #1344): NULL in older engine pins; the create path falls
|
|
316
324
|
* back to create + per-pair sets. */
|
|
325
|
+
f_capabilities = (capabilities_fn)lib_sym(h, "leptris_capabilities");
|
|
317
326
|
f_elem_new_with_attrs =
|
|
318
327
|
(elem_new_attrs_fn)lib_sym(h, "leptris_element_new_with_attributes");
|
|
319
328
|
f_text_create = (text_create_fn)lib_sym(h, "leptris_text_node_create");
|
|
@@ -3965,6 +3974,25 @@ static VALUE nf_sax_parser_free(VALUE self, VALUE obj)
|
|
|
3965
3974
|
return Qnil;
|
|
3966
3975
|
}
|
|
3967
3976
|
|
|
3977
|
+
/* leptris-ruby#572: the engine's capability descriptor as a plain
|
|
3978
|
+
* hash — libffi's small-struct value return proved unreliable across
|
|
3979
|
+
* ABIs, so the shim reads the C struct and returns the two fields
|
|
3980
|
+
* (raw bitset + descriptor version; bits append per #1905). */
|
|
3981
|
+
static VALUE nf_capabilities(VALUE module) {
|
|
3982
|
+
VALUE hash = rb_hash_new();
|
|
3983
|
+
if (!f_capabilities) {
|
|
3984
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("caps")), Qnil);
|
|
3985
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("descriptor_version")), Qnil);
|
|
3986
|
+
return hash;
|
|
3987
|
+
}
|
|
3988
|
+
rb_caps_t c = f_capabilities();
|
|
3989
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("caps")),
|
|
3990
|
+
ULL2NUM(c.caps));
|
|
3991
|
+
rb_hash_aset(hash, ID2SYM(rb_intern("descriptor_version")),
|
|
3992
|
+
UINT2NUM(c.descriptor_version));
|
|
3993
|
+
return hash;
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3968
3996
|
LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
3969
3997
|
{
|
|
3970
3998
|
VALUE m_leptris, m_xml, m_native;
|
|
@@ -4084,6 +4112,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
4084
4112
|
c_sax_parser = rb_define_class_under(m_native, "SaxParser",
|
|
4085
4113
|
c_object);
|
|
4086
4114
|
rb_define_module_function(m_native, "sax_parse", nf_sax_parse, 3);
|
|
4115
|
+
rb_define_module_function(m_native, "capabilities",
|
|
4116
|
+
nf_capabilities, 0);
|
|
4087
4117
|
rb_define_module_function(m_native, "sax_parser_create",
|
|
4088
4118
|
nf_sax_parser_create, 2);
|
|
4089
4119
|
rb_define_module_function(m_native, "sax_parser_feed",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/ffi.rb
CHANGED
|
@@ -566,6 +566,11 @@ module Leptris
|
|
|
566
566
|
[:string, :size_t, :leptris_plan, :pointer],
|
|
567
567
|
:leptris_plan_result
|
|
568
568
|
|
|
569
|
+
# leptris-ruby#572: the engine's capability descriptor is read
|
|
570
|
+
# through the native shim's Native.capabilities (libffi's
|
|
571
|
+
# small-struct value return proved ABI-unreliable); the shim
|
|
572
|
+
# exposes the raw bitset + descriptor version and re-encodes
|
|
573
|
+
# nothing (bits append per contract).
|
|
569
574
|
attach_function :leptris_version, [], UTF8_STRING
|
|
570
575
|
attach_function :leptris_version_components, [:pointer, :pointer, :pointer], :void
|
|
571
576
|
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
|
Binary file
|
data/lib/leptris/xml.rb
CHANGED
|
@@ -43,6 +43,31 @@ module Leptris
|
|
|
43
43
|
|
|
44
44
|
# Nokogiri-style top-level entry points (Nokogiri::XML(...) /
|
|
45
45
|
# Nokogiri::XML.parse). Delegates to Document.parse.
|
|
46
|
+
# leptris-ruby#572: the engine's capability descriptor — the
|
|
47
|
+
# raw bitset plus descriptor version (leptris/leptris#1905).
|
|
48
|
+
# Frozen and memoized: the engine's answers are fixed per binary.
|
|
49
|
+
# Consumers (moxml#504) OR this with wrapper knowledge; bits
|
|
50
|
+
# append per the engine's contract — never reorder.
|
|
51
|
+
def self.capabilities
|
|
52
|
+
@capabilities ||=
|
|
53
|
+
begin
|
|
54
|
+
raw =
|
|
55
|
+
begin
|
|
56
|
+
Native.capabilities
|
|
57
|
+
rescue NameError
|
|
58
|
+
# the native layer loads with `require "leptris"`;
|
|
59
|
+
# a direct `require "leptris/xml"` has not loaded it
|
|
60
|
+
# yet — the explicit entry is the parallel-API door.
|
|
61
|
+
require "leptris"
|
|
62
|
+
Native.capabilities
|
|
63
|
+
end
|
|
64
|
+
{
|
|
65
|
+
caps: raw[:caps],
|
|
66
|
+
descriptor_version: raw[:descriptor_version]
|
|
67
|
+
}.freeze
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
46
71
|
def self.parse(xml_or_io, options: nil, readonly: false,
|
|
47
72
|
recover: true, strict: false)
|
|
48
73
|
recover = false if strict
|
data/lib/libleptris.so
CHANGED
|
Binary file
|
data/vendor-src/README.md
CHANGED
|
@@ -1,5 +1,151 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.396] - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The AVX2 UTF-8 validator rejected valid multi-byte input
|
|
8
|
+
(leptris#1842 A5 follow-up).** Three defects shipped with the
|
|
9
|
+
1.9.395 SIMD gate, all invisible on arm64: the ascii lane's
|
|
10
|
+
polarity was inverted (`cmplt(cur, zero)` is the NOT-ascii half,
|
|
11
|
+
so every ASCII byte tripped the error term), both
|
|
12
|
+
continuation-range bounds compared in the wrong direction
|
|
13
|
+
(`cont_ok` was unsatisfiable — NEON's direct `vcge/vcle` pair
|
|
14
|
+
became a swapped saturating-subtraction idiom), and the tail
|
|
15
|
+
anchor walked back only over continuation runs, letting an
|
|
16
|
+
opener in the final block lane escape validation (both cores).
|
|
17
|
+
The leptris-py UTF-16 BOM lane caught it (`malformed input` on
|
|
18
|
+
`café`); x86 CI failed 25+ tests. Now verified by a 400k-case
|
|
19
|
+
lane-math simulation against the scalar oracle plus boundary
|
|
20
|
+
shapes seeded into the randomized cross-check.
|
|
21
|
+
|
|
22
|
+
- **`leptris_parse_string_inplace` no longer writes past the
|
|
23
|
+
buffer.** The NUL sentinel at `buf[len]` exceeded the documented
|
|
24
|
+
contract (terminators INSIDE the buffer); an exactly-sized
|
|
25
|
+
caller tripped ASAN. The scanner takes `len` and never read it.
|
|
26
|
+
|
|
27
|
+
- **`xs:redefine` bodies were dropped (leptris#1926).** A
|
|
28
|
+
`redefine`d complexType whose body declared new particles (or
|
|
29
|
+
attributes) lost them: the extension derived from the base type
|
|
30
|
+
captured its model against the BASE body, and the redefinition was
|
|
31
|
+
never folded in. Bodies are now captured after the base resolves —
|
|
32
|
+
the same mechanism `xs:override` already uses (PR #1913) — so the
|
|
33
|
+
ladder resolves head-first through the redefinition and the
|
|
34
|
+
extension folds against the redefined content. `xs:group`/attr
|
|
35
|
+
refs inside redefine bodies are collected like override bodies.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## [1.9.395] - 2026-10-31
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
|
|
43
|
+
- **Boundary-vector UTF-8 validation (leptris#1842 A5, PRs #1922/#1924).**
|
|
44
|
+
`leptris_utf8_valid` — AVX2/NEON cores over the existing dispatch
|
|
45
|
+
layer, the scalar DFA as the fallback and oracle — pinned by an
|
|
46
|
+
adversarial matrix plus a 4000-case randomized cross-check. The
|
|
47
|
+
parser's UTF-8 gate now routes through it: **parse of UTF-8-dense
|
|
48
|
+
documents is +32%** (3.70ms -> 2.81ms on a 2.2MB catalog; the
|
|
49
|
+
validator alone ~3.7 GB/s on NEON). Unblocked line-level profiling
|
|
50
|
+
first: the no-LTO `-g` build resolves file:line through
|
|
51
|
+
dsymutil/atos (ThinLTO bitcode was the blocker).
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **PI data-scan bounds (fuzzer class).** Consuming the PI target's
|
|
56
|
+
separator could leave `p.pos` one past `p.end`; `p.end - p.pos`
|
|
57
|
+
underflowed to SIZE_MAX and the data `memchr` walked off the
|
|
58
|
+
allocation (the nightly-fuzz witness segfaulted under the no-LTO
|
|
59
|
+
build — allocator luck hid it on LTO).
|
|
60
|
+
- **ctE019 regression (leptris#1923, PR #1927).** A chameleon-included
|
|
61
|
+
attribute referenced through a restriction base was false-rejected
|
|
62
|
+
as "not declared (strict)" — the 1.9.391 pending-ref fix made the
|
|
63
|
+
forward-referenced rows visible, surfacing that only the derived
|
|
64
|
+
type's rows were consulted. The oracle (xmllint) validates ctE019:
|
|
65
|
+
both derivation spellings now record the restriction base and the
|
|
66
|
+
strict-attribute admission walks the chain (admission only;
|
|
67
|
+
required/fixed/type checks stay derived-only).
|
|
68
|
+
- drop the const-path pristine carve (leptris#1842 A3) (parse)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
## [1.9.394] - 2026-10-30
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
|
|
76
|
+
- **xs:override loads its base and replaces the overridden components
|
|
77
|
+
(leptris#1913, PR #1917).** `override` was absent from the
|
|
78
|
+
declaration gate and the include-family chase, so the base document
|
|
79
|
+
never loaded and the override body's nested declarations were never
|
|
80
|
+
captured — the compiled schema had an empty symbol space (decls=0,
|
|
81
|
+
every instance element "not declared"). Override now chases its
|
|
82
|
+
schemaLocation like include (same targetNamespace), then captures
|
|
83
|
+
its own declaration children as a pseudo-root AFTER the base so the
|
|
84
|
+
head-first lookup ladder sees the replacements; the attributeGroup
|
|
85
|
+
pre-pass honors a capture-force flag in that body, and the ref-check
|
|
86
|
+
declaration collector walks the override body too. Witness: base
|
|
87
|
+
`Doc` replaced by the override's `Doc` — `<doc><b>1</b></doc>`
|
|
88
|
+
validates, `<doc><a>x</a></doc>` is rejected.
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## [1.9.393] - 2026-10-30
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
|
|
96
|
+
- **Capability bit for entity-reference nodes (leptris#1904, PR #1915).**
|
|
97
|
+
`LEPTRIS_PARSE_KEEP_ENTITY_REFS` already parses references as
|
|
98
|
+
first-class `LEPTRIS_NODE_TYPE_ENTITY_REF` nodes — text segments
|
|
99
|
+
split around the five built-ins and custom named references
|
|
100
|
+
(numeric character references stay text, XML 1.0 §4.1) — but
|
|
101
|
+
nothing advertised it. `LEPTRIS_CAP_ENTITY_REF_NODES` lets a
|
|
102
|
+
binding probe once and parse with the flag instead of re-scanning
|
|
103
|
+
expanded text; a spec pins the DOM shape and the
|
|
104
|
+
string-value / serialization / XPath agreement.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- **XSD 1.1 was inert for every `vc:minVersion`-marked schema
|
|
109
|
+
(leptris#1910, leptris#1911, PR #1914).** The version detector
|
|
110
|
+
read only the root's plain `version` attribute; the W3C suite and
|
|
111
|
+
the binding ecosystem mark 1.1 with `vc:minVersion` in the
|
|
112
|
+
versioning namespace. With the capture gates off,
|
|
113
|
+
`xs:alternative` and `xs:assert` compiled to nothing —
|
|
114
|
+
conditional type assignment applied the declared base type's
|
|
115
|
+
model in both directions (false rejects and false accepts), and
|
|
116
|
+
asserts never attached to their types. The detector now honors
|
|
117
|
+
`version`, `vc:minVersion`, and a `vc:maxVersion="1.0"` pin, and
|
|
118
|
+
`xs:openContent` interleave/suffix semantics engage with it
|
|
119
|
+
(leptris#1912's repro validates clean).
|
|
120
|
+
- **Value-comparison keywords accept name-test operands
|
|
121
|
+
(`w le h`).** The XPath `eq/ne/lt/le/gt/ge` operators only read
|
|
122
|
+
as operators when the right operand opened with a scalar, paren,
|
|
123
|
+
variable, or function call — an `xs:assert test="w le h"` failed
|
|
124
|
+
to compile. Adjacent name tokens are never a valid path
|
|
125
|
+
continuation, so a bare name-test follower is unambiguously an
|
|
126
|
+
operand; every path start is now admitted.
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
## [1.9.392] - 2026-10-30
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
|
|
134
|
+
- **Capability descriptor face (leptris#1905, PR #1908).**
|
|
135
|
+
`leptris_capabilities()` returns a versioned bitset of every face
|
|
136
|
+
this installed engine serves — the engine-side half of the
|
|
137
|
+
moxml#504 capability report. Bindings OR it with their own wrapper
|
|
138
|
+
knowledge instead of maintaining scattered version-gate
|
|
139
|
+
constants; build-conditional faces (DUCET collation, utf8proc,
|
|
140
|
+
iconv, AVX2) follow the same `LEPTRIS_HAS_*` macros that gate the
|
|
141
|
+
sources, so the descriptor cannot drift from the binary it ships
|
|
142
|
+
in. Contract: bits append, never reorder; the descriptor version
|
|
143
|
+
gates shape changes. The spec anchors every unconditional bit to
|
|
144
|
+
its real face symbol — a bit without a linked face fails the
|
|
145
|
+
build, not the assertion.
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
3
149
|
## [1.9.391] - 2026-10-30
|
|
4
150
|
|
|
5
151
|
### Fixed
|
|
@@ -744,6 +744,43 @@ To pin enum values (bindings hard-code these):
|
|
|
744
744
|
ctest --test-dir build -R HeaderHygiene
|
|
745
745
|
----
|
|
746
746
|
|
|
747
|
+
=== Capability descriptor (one call: what does this engine serve?)
|
|
748
|
+
|
|
749
|
+
Bindings and embedders stop maintaining scattered version-gate
|
|
750
|
+
constants: one call returns a versioned bitset of every face this
|
|
751
|
+
installed engine serves. Build-conditional faces (Unicode
|
|
752
|
+
normalization, iconv, DUCET collation, AVX2) answer honestly for the
|
|
753
|
+
exact library you linked. New capabilities APPEND bits — the
|
|
754
|
+
descriptor version gates shape changes.
|
|
755
|
+
|
|
756
|
+
[source,c]
|
|
757
|
+
----
|
|
758
|
+
LeptrisCapabilities c = leptris_capabilities();
|
|
759
|
+
if (c.descriptor_version >= 1 && (c.caps & LEPTRIS_CAP_XSD)) {
|
|
760
|
+
/* xsd compile/validate faces are present */
|
|
761
|
+
}
|
|
762
|
+
----
|
|
763
|
+
|
|
764
|
+
Entity references: parsing with `LEPTRIS_PARSE_KEEP_ENTITY_REFS`
|
|
765
|
+
yields `LEPTRIS_NODE_TYPE_ENTITY_REF` nodes instead of pre-expanded
|
|
766
|
+
text — segments split around the five built-ins and custom named
|
|
767
|
+
references (numeric character references stay text, per XML 1.0
|
|
768
|
+
§4.1). String-value, serialization, and XPath all see the expanded
|
|
769
|
+
value. Probe `LEPTRIS_CAP_ENTITY_REF_NODES` before relying on the
|
|
770
|
+
mode.
|
|
771
|
+
|
|
772
|
+
The bits: `LEPTRIS_CAP_PARSE`, `LEPTRIS_CAP_DOM`, `LEPTRIS_CAP_XPATH`,
|
|
773
|
+
`LEPTRIS_CAP_XQUERY`, `LEPTRIS_CAP_DTD`, `LEPTRIS_CAP_SAX`,
|
|
774
|
+
`LEPTRIS_CAP_HTML`, `LEPTRIS_CAP_XINCLUDE`, `LEPTRIS_CAP_DIFF`,
|
|
775
|
+
`LEPTRIS_CAP_DIGEST`, `LEPTRIS_CAP_XSD`, `LEPTRIS_CAP_XSD_SET`,
|
|
776
|
+
`LEPTRIS_CAP_RELAXNG`, `LEPTRIS_CAP_SCHEMATRON`, `LEPTRIS_CAP_XSLT`,
|
|
777
|
+
`LEPTRIS_CAP_DESCRIPTOR`, `LEPTRIS_CAP_NS_SNAPSHOT_BULK`,
|
|
778
|
+
`LEPTRIS_CAP_CHILDREN_BATCH`, `LEPTRIS_CAP_NAME_COMPARE`,
|
|
779
|
+
`LEPTRIS_CAP_VERSION`, and the build-conditional
|
|
780
|
+
`LEPTRIS_CAP_COLLATION_DUCET`, `LEPTRIS_CAP_UNICODE_UTF8PROC`,
|
|
781
|
+
`LEPTRIS_CAP_ENCODING_ICONV`, `LEPTRIS_CAP_SIMD_AVX2`,
|
|
782
|
+
`LEPTRIS_CAP_ENTITY_REF_NODES`.
|
|
783
|
+
|
|
747
784
|
[#FFI]
|
|
748
785
|
== FFI design
|
|
749
786
|
|
|
@@ -300,16 +300,23 @@ target_compile_definitions(leptris_objects PUBLIC _POSIX_C_SOURCE=200809L)
|
|
|
300
300
|
# them all into the same library, dispatch at runtime via cpu.c. The
|
|
301
301
|
# scalar TU (simd_text.c) is always built; these add accelerated paths.
|
|
302
302
|
if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|AMD64|i[3-6]86)")
|
|
303
|
-
|
|
303
|
+
# MSVC has no -mavx2; /arch:AVX2 enables the whole AVX set for the
|
|
304
|
+
# TU (the windows lane shipped scalar-only and the capability bit
|
|
305
|
+
# disagreed with test_capabilities — 2026-10-31).
|
|
306
|
+
if(MSVC)
|
|
307
|
+
set(LEPTRIS_AVX2_FLAG "/arch:AVX2")
|
|
308
|
+
else()
|
|
309
|
+
set(LEPTRIS_AVX2_FLAG "-mavx2")
|
|
310
|
+
endif()
|
|
311
|
+
check_c_compiler_flag("${LEPTRIS_AVX2_FLAG}" LEPTRIS_HAVE_AVX2_FLAG)
|
|
304
312
|
if(LEPTRIS_HAVE_AVX2_FLAG)
|
|
305
313
|
target_sources(leptris_objects PRIVATE leptris/common/simd_text_avx2.c)
|
|
306
314
|
set_source_files_properties(leptris/common/simd_text_avx2.c
|
|
307
|
-
PROPERTIES COMPILE_FLAGS "
|
|
315
|
+
PROPERTIES COMPILE_FLAGS "${LEPTRIS_AVX2_FLAG}")
|
|
308
316
|
# Consumers (simd_text.c dispatch) reference the AVX2 symbol only
|
|
309
|
-
# when this define is present
|
|
310
|
-
# so the TU is skipped there and the dispatch falls back to scalar.
|
|
317
|
+
# when this define is present.
|
|
311
318
|
target_compile_definitions(leptris_objects PRIVATE LEPTRIS_HAS_AVX2_BUILD=1)
|
|
312
|
-
message(STATUS "AOT SIMD: AVX2 text scan ENABLED")
|
|
319
|
+
message(STATUS "AOT SIMD: AVX2 text scan ENABLED (${LEPTRIS_AVX2_FLAG})")
|
|
313
320
|
else()
|
|
314
321
|
message(STATUS "AOT SIMD: AVX2 unavailable (flag rejected)")
|
|
315
322
|
endif()
|
|
@@ -447,6 +447,58 @@ typedef struct leptris_xpath_compiled* LeptrisXPathCompiled;
|
|
|
447
447
|
* compiled — apply it to any number of documents, from any threads. */
|
|
448
448
|
typedef struct leptris_xslt* LeptrisXslt;
|
|
449
449
|
|
|
450
|
+
/* ============================================================================
|
|
451
|
+
* Capability Descriptor (leptris#1905)
|
|
452
|
+
* ============================================================================ */
|
|
453
|
+
|
|
454
|
+
/* What this INSTALLED engine serves — one call, one versioned
|
|
455
|
+
* bitset. Contract: new capabilities APPEND bits; a bit is never
|
|
456
|
+
* removed, repurposed, or reordered — that is a descriptor-version
|
|
457
|
+
* bump. Build-conditional faces (utf8proc/iconv/DUCET/AVX2) answer
|
|
458
|
+
* honestly for the exact library a consumer linked. Bindings OR
|
|
459
|
+
* this with their own wrapper knowledge; consumers stop maintaining
|
|
460
|
+
* scattered version-gate constants. */
|
|
461
|
+
enum {
|
|
462
|
+
LEPTRIS_CAP_PARSE = 1ULL << 0,
|
|
463
|
+
LEPTRIS_CAP_DOM = 1ULL << 1,
|
|
464
|
+
LEPTRIS_CAP_XPATH = 1ULL << 2,
|
|
465
|
+
LEPTRIS_CAP_XQUERY = 1ULL << 3,
|
|
466
|
+
LEPTRIS_CAP_DTD = 1ULL << 4,
|
|
467
|
+
LEPTRIS_CAP_SAX = 1ULL << 5,
|
|
468
|
+
LEPTRIS_CAP_HTML = 1ULL << 6,
|
|
469
|
+
LEPTRIS_CAP_XINCLUDE = 1ULL << 7,
|
|
470
|
+
LEPTRIS_CAP_DIFF = 1ULL << 8,
|
|
471
|
+
LEPTRIS_CAP_DIGEST = 1ULL << 9,
|
|
472
|
+
LEPTRIS_CAP_XSD = 1ULL << 10,
|
|
473
|
+
LEPTRIS_CAP_XSD_SET = 1ULL << 11,
|
|
474
|
+
LEPTRIS_CAP_RELAXNG = 1ULL << 12,
|
|
475
|
+
LEPTRIS_CAP_SCHEMATRON = 1ULL << 13,
|
|
476
|
+
LEPTRIS_CAP_XSLT = 1ULL << 14,
|
|
477
|
+
LEPTRIS_CAP_DESCRIPTOR = 1ULL << 15,
|
|
478
|
+
LEPTRIS_CAP_NS_SNAPSHOT_BULK = 1ULL << 16,
|
|
479
|
+
LEPTRIS_CAP_CHILDREN_BATCH = 1ULL << 17,
|
|
480
|
+
LEPTRIS_CAP_NAME_COMPARE = 1ULL << 18,
|
|
481
|
+
LEPTRIS_CAP_VERSION = 1ULL << 19,
|
|
482
|
+
/* build-conditional */
|
|
483
|
+
LEPTRIS_CAP_COLLATION_DUCET = 1ULL << 20,
|
|
484
|
+
LEPTRIS_CAP_UNICODE_UTF8PROC = 1ULL << 21,
|
|
485
|
+
LEPTRIS_CAP_ENCODING_ICONV = 1ULL << 22,
|
|
486
|
+
LEPTRIS_CAP_SIMD_AVX2 = 1ULL << 23,
|
|
487
|
+
/* LEPTRIS_PARSE_KEEP_ENTITY_REFS yields entity references as
|
|
488
|
+
* first-class ENTITY_REF nodes (segments split around the five
|
|
489
|
+
* built-ins and custom named references; leptris#1904). */
|
|
490
|
+
LEPTRIS_CAP_ENTITY_REF_NODES = 1ULL << 24
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
/* Descriptor shape version: 1. Bump when the STRUCT changes or a
|
|
494
|
+
* bit is retired (never expected — bits append). */
|
|
495
|
+
#define LEPTRIS_CAPS_DESCRIPTOR_VERSION 1u
|
|
496
|
+
|
|
497
|
+
typedef struct leptris_capabilities {
|
|
498
|
+
unsigned long long caps;
|
|
499
|
+
unsigned descriptor_version;
|
|
500
|
+
} LeptrisCapabilities;
|
|
501
|
+
|
|
450
502
|
/* ============================================================================
|
|
451
503
|
* Memory Allocation Function Types
|
|
452
504
|
* ============================================================================ */
|
|
@@ -4230,6 +4230,12 @@ LEPTRIS_API const char* leptris_version(void);
|
|
|
4230
4230
|
*/
|
|
4231
4231
|
LEPTRIS_API void leptris_version_components(int* major, int* minor, int* patch);
|
|
4232
4232
|
|
|
4233
|
+
/* leptris#1905: the capability descriptor — what this installed
|
|
4234
|
+
* engine serves, as a versioned bitset (LEPTRIS_CAP_*). Query once
|
|
4235
|
+
* at attach; new capabilities append bits (the descriptor version
|
|
4236
|
+
* gates shape changes). Thread-safe: pure, allocation-free. */
|
|
4237
|
+
LEPTRIS_API LeptrisCapabilities leptris_capabilities(void);
|
|
4238
|
+
|
|
4233
4239
|
/**
|
|
4234
4240
|
* Release this thread's libleptris caches
|
|
4235
4241
|
*
|
|
@@ -49,6 +49,54 @@ static ptrdiff_t find3_scalar(const char* s, size_t len,
|
|
|
49
49
|
return -1;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/* Scalar UTF-8 DFA — the always-available fallback (and the oracle
|
|
53
|
+
* the SIMD cores are pinned against). Moved from the parser's
|
|
54
|
+
* dp_utf8_valid (#1842 A5). */
|
|
55
|
+
int leptris_utf8_valid_scalar(const char* s_, size_t len) {
|
|
56
|
+
const unsigned char* p = (const unsigned char*)s_;
|
|
57
|
+
size_t i = 0;
|
|
58
|
+
/* ASCII fast path: 8 bytes per iteration. The DFA below runs
|
|
59
|
+
* only when a high byte is present; ordinary documents pay one
|
|
60
|
+
* memory-bandwidth pass. */
|
|
61
|
+
while (i + 8 <= len) {
|
|
62
|
+
uint64_t w;
|
|
63
|
+
memcpy(&w, p + i, 8);
|
|
64
|
+
if (w & UINT64_C(0x8080808080808080)) break;
|
|
65
|
+
i += 8;
|
|
66
|
+
}
|
|
67
|
+
while (i < len && p[i] < 0x80) i++;
|
|
68
|
+
while (i < len) {
|
|
69
|
+
unsigned c = p[i];
|
|
70
|
+
if (c < 0x80) {
|
|
71
|
+
i++;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
size_t n;
|
|
75
|
+
unsigned lo = 0x80;
|
|
76
|
+
unsigned hi = 0xBF;
|
|
77
|
+
if (c >= 0xC2 && c <= 0xDF) {
|
|
78
|
+
n = 1;
|
|
79
|
+
} else if (c >= 0xE0 && c <= 0xEF) {
|
|
80
|
+
n = 2;
|
|
81
|
+
if (c == 0xE0) lo = 0xA0;
|
|
82
|
+
else if (c == 0xED) hi = 0x9F;
|
|
83
|
+
} else if (c >= 0xF0 && c <= 0xF4) {
|
|
84
|
+
n = 3;
|
|
85
|
+
if (c == 0xF0) lo = 0x90;
|
|
86
|
+
else if (c == 0xF4) hi = 0x8F;
|
|
87
|
+
} else {
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
if (len - i <= n) return 0;
|
|
91
|
+
if (p[i + 1] < lo || p[i + 1] > hi) return 0;
|
|
92
|
+
for (size_t k = 2; k <= n; k++) {
|
|
93
|
+
if (p[i + k] < 0x80 || p[i + k] > 0xBF) return 0;
|
|
94
|
+
}
|
|
95
|
+
i += n + 1;
|
|
96
|
+
}
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
52
100
|
/* ---- Dispatch ---------------------------------------------------------- */
|
|
53
101
|
|
|
54
102
|
/* Resolved pointer — initialized on first use (thread-safe enough:
|
|
@@ -63,6 +111,7 @@ static void (*g_count3_high_fn)(const char*, size_t, char, char, char,
|
|
|
63
111
|
int*) = NULL;
|
|
64
112
|
static void (*g_copy3_fn)(char*, const char*, size_t, char, char, char,
|
|
65
113
|
size_t*, size_t*, size_t*) = NULL;
|
|
114
|
+
static int (*g_utf8_fn)(const char*, size_t) = NULL;
|
|
66
115
|
static ptrdiff_t (*g_scan_run_fn)(const char*, size_t, char, char, char,
|
|
67
116
|
int*, int*) = NULL;
|
|
68
117
|
|
|
@@ -105,6 +154,9 @@ static size_t count_scalar(const char* s, size_t len, char c) {
|
|
|
105
154
|
* dispatch_init()" pattern raced when two threads hit the same
|
|
106
155
|
* entry point on first call. */
|
|
107
156
|
static void dispatch_init(void);
|
|
157
|
+
int leptris_utf8_valid_scalar(const char* s, size_t len);
|
|
158
|
+
int leptris_utf8_valid_avx2(const char* s, size_t len);
|
|
159
|
+
int leptris_utf8_valid_neon(const char* s, size_t len);
|
|
108
160
|
static void count3_high_scalar(const char* s, size_t len,
|
|
109
161
|
char c0, char c1, char c2,
|
|
110
162
|
size_t* n0, size_t* n1, size_t* n2,
|
|
@@ -132,6 +184,7 @@ static void dispatch_init(void) {
|
|
|
132
184
|
g_count_fn = leptris_text_count_char_avx2;
|
|
133
185
|
g_count3_high_fn = count3_high_scalar;
|
|
134
186
|
g_scan_run_fn = leptris_text_scan_run_avx2;
|
|
187
|
+
g_utf8_fn = leptris_utf8_valid_avx2;
|
|
135
188
|
return;
|
|
136
189
|
}
|
|
137
190
|
#endif
|
|
@@ -144,6 +197,7 @@ static void dispatch_init(void) {
|
|
|
144
197
|
g_count3_high_fn = leptris_text_count3_high_neon;
|
|
145
198
|
g_copy3_fn = leptris_copy_count3_neon;
|
|
146
199
|
g_scan_run_fn = leptris_text_scan_run_neon;
|
|
200
|
+
g_utf8_fn = leptris_utf8_valid_neon;
|
|
147
201
|
return;
|
|
148
202
|
}
|
|
149
203
|
#endif
|
|
@@ -153,6 +207,7 @@ static void dispatch_init(void) {
|
|
|
153
207
|
g_count_fn = count_scalar;
|
|
154
208
|
g_count3_high_fn = count3_high_scalar;
|
|
155
209
|
g_scan_run_fn = scan_run_scalar;
|
|
210
|
+
g_utf8_fn = leptris_utf8_valid_scalar;
|
|
156
211
|
}
|
|
157
212
|
|
|
158
213
|
/* Scalar kernel: one byte loop counts all three chars and detects
|
|
@@ -239,6 +294,12 @@ void leptris_copy_count3(char* dst, const char* src, size_t len,
|
|
|
239
294
|
leptris_text_count3(src, len, c0, c1, c2, n0, n1, n2);
|
|
240
295
|
}
|
|
241
296
|
|
|
297
|
+
int leptris_utf8_valid(const char* s, size_t len) {
|
|
298
|
+
if (!g_utf8_fn) dispatch_init();
|
|
299
|
+
if (!g_utf8_fn) g_utf8_fn = leptris_utf8_valid_scalar;
|
|
300
|
+
return g_utf8_fn(s, len);
|
|
301
|
+
}
|
|
302
|
+
|
|
242
303
|
ptrdiff_t leptris_text_scan_run(const char* s, size_t len, char term,
|
|
243
304
|
char f1, char f2,
|
|
244
305
|
int* saw_f1, int* saw_f2) {
|
|
@@ -57,6 +57,19 @@ void leptris_text_count3_high(const char* s, size_t len,
|
|
|
57
57
|
size_t* n0, size_t* n1, size_t* n2,
|
|
58
58
|
int* saw_high);
|
|
59
59
|
|
|
60
|
+
/* Returns 1 when [s, s+len) is valid UTF-8, 0 otherwise. SIMD
|
|
61
|
+
* boundary-vector validation (the Lemire/Steagall construction) with
|
|
62
|
+
* the scalar DFA as the fallback; identical verdicts on every input
|
|
63
|
+
* (pinned by the encoding suite's adversarial matrix + a randomized
|
|
64
|
+
* cross-check). #1842 A5: the parser's UTF-8 gate ran a per-byte DFA
|
|
65
|
+
* over the whole document — the hottest line on UTF-8-dense
|
|
66
|
+
* fixtures (line-level profile, 2026-10-30). */
|
|
67
|
+
int leptris_utf8_valid(const char* s, size_t len);
|
|
68
|
+
|
|
69
|
+
/* The scalar DFA fallback (and the SIMD verdicts' oracle) — also
|
|
70
|
+
* the shared tail anchor for the ISA cores. */
|
|
71
|
+
int leptris_utf8_valid_scalar(const char* s, size_t len);
|
|
72
|
+
|
|
60
73
|
/* Copy [src, src+len) to dst while counting c0/c1/c2 occurrences —
|
|
61
74
|
* one fused memory pass (falls back to memcpy + count3 where no
|
|
62
75
|
* SIMD path is compiled). The parser's owns-copy path gets its
|