leptris 1.9.209.1 → 1.9.210.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 +21 -0
- data/Rakefile +70 -1
- 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/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.rb +7 -4
- data/vendor-src/README.md +14 -0
- data/vendor-src/libleptris/CHANGELOG.md +23 -0
- data/vendor-src/libleptris/CMakeLists.txt +1 -1
- data/vendor-src/libleptris/src/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/src/leptris/core.c +2 -0
- data/vendor-src/libleptris/src/leptris/memory/arena.c +34 -1
- data/vendor-src/libleptris/src/leptris/memory/arena.h +3 -0
- data/vendor-src/libleptris/src/leptris/memory/doc_block.c +108 -0
- data/vendor-src/libleptris/src/leptris/memory/doc_block.h +61 -0
- data/vendor-src/libleptris/test/CMakeLists.txt +1 -0
- data/vendor-src/libleptris/test/memory/test_arena.cpp +21 -0
- data/vendor-src/libleptris/test/memory/test_doc_block.cpp +94 -0
- data/vendor-src/libleptris/vcpkg.json +1 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6a8b73b57e537bd42a2f62759cd8b36fb842ed36c5046894453ae4b6c1540ca0
|
|
4
|
+
data.tar.gz: b089afc26fe2c7d8e8396a25a88c00baaa947c732896cf3aa70097c3b88b4302
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1661a5db0ca010813f02bb90a24537fd92c5849cb797775ec7bb98c5217144bbdf95b8eca4ae3ec0a42a18f7cbebc57fd13b33500b1e402c69f903de4be85bff
|
|
7
|
+
data.tar.gz: 6724e22c525398f6ecead93260b6716188242a5f3722bc29fc6810bb00d89f66e8fc2a512729a7915b3be001b0a7c9b3dbbc2250623a22b528a819cc3f7faa49
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.210.0] - 2026-09-20
|
|
9
|
+
|
|
10
|
+
### Performance — engine sync (1.9.210)
|
|
11
|
+
|
|
12
|
+
- Vendored libleptris 1.9.210: the node-layout fork's document
|
|
13
|
+
block (slice 1, #1253) — two contiguous geometrically-grown
|
|
14
|
+
regions per document (dense node array + string arena) with
|
|
15
|
+
region-relative byte-offset edges, so growth never invalidates
|
|
16
|
+
an offset; and the parse-arena retain ceiling raised 32 → 96MB
|
|
17
|
+
with a thread-cleanup drain (#1222/#1238 lane): repeated large
|
|
18
|
+
parses stop freeing/re-mallocing tens of MB per cycle. Measured
|
|
19
|
+
engine-side: 13MB attr shape 341 → 472 MB/s (+38%); the text
|
|
20
|
+
shape's size-decline eliminated (~605-627 flat across
|
|
21
|
+
1.5-13MB). No binding-facing surface changes; audit holds.
|
|
22
|
+
- Note: #1242 was closed upstream before our strikes 7-10
|
|
23
|
+
forensics landed; reopened with the post-fix evidence (the
|
|
24
|
+
1.9.208 memo clear is incomplete — direct-call NULL + empty
|
|
25
|
+
chain on a live element; set_root cross-document false
|
|
26
|
+
positive on windows-4.0). The ubuntu-3.4 gate and armed
|
|
27
|
+
forensics stay until the engine ships the residual fix.
|
|
28
|
+
|
|
8
29
|
## [1.9.209.0] - 2026-09-20
|
|
9
30
|
|
|
10
31
|
### Performance — engine sync (1.9.209)
|
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.210"
|
|
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
|
|
@@ -117,6 +117,35 @@ task :compile do
|
|
|
117
117
|
rm_rf([File.join(dest, "build"), File.join(dest, "prefix")])
|
|
118
118
|
end
|
|
119
119
|
|
|
120
|
+
# Rebuild notes ride every gem beside the sources (packaging
|
|
121
|
+
# doctrine): the exact pins and the commands to reproduce the
|
|
122
|
+
# vendored libraries. The ruby variant's extconf runs these
|
|
123
|
+
# trees automatically at install time.
|
|
124
|
+
File.write(File.join(vsrc, "README.md"), <<~README)
|
|
125
|
+
Sources vendored in this gem (recompile rights; the ruby
|
|
126
|
+
variant builds them at install via extconf.rb):
|
|
127
|
+
|
|
128
|
+
- libleptris v#{LIBLEPTRIS_VERSION}
|
|
129
|
+
- utf8proc v#{UTF8PROC_VERSION}
|
|
130
|
+
|
|
131
|
+
Rebuild by hand:
|
|
132
|
+
|
|
133
|
+
cmake -B vendor-src/utf8proc/build -S vendor-src/utf8proc \
|
|
134
|
+
-DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON \
|
|
135
|
+
-DUTF8PROC_ENABLE_TESTING=OFF
|
|
136
|
+
cmake --build vendor-src/utf8proc/build --config Release
|
|
137
|
+
cmake --install vendor-src/utf8proc/build --prefix <prefix>
|
|
138
|
+
|
|
139
|
+
cmake -B vendor-src/libleptris/build -S vendor-src/libleptris \
|
|
140
|
+
-DCMAKE_BUILD_TYPE=Release \
|
|
141
|
+
-DLEPTRIS_BUILD_SHARED=ON -DLEPTRIS_BUILD_STATIC=OFF \
|
|
142
|
+
-DBUILD_TESTING=OFF -DLEPTRIS_BUILD_CLI=OFF \
|
|
143
|
+
-DLEPTRIS_BUILD_BENCHMARKS=OFF -DLEPTRIS_BUILD_MAN_PAGES=OFF \
|
|
144
|
+
-DLEPTRIS_ENABLE_UTF8PROC=ON \
|
|
145
|
+
-DCMAKE_PREFIX_PATH=<utf8proc-prefix>
|
|
146
|
+
cmake --build vendor-src/libleptris/build --config Release
|
|
147
|
+
README
|
|
148
|
+
|
|
120
149
|
# libleptris 1.9.18's xslt_functions.c:270 assigns LeptrisElement
|
|
121
150
|
# to LeptrisNodeRef — GCC 14 (Alpine/musl) makes incompatible
|
|
122
151
|
# pointer types an error by default and the musl platform gems
|
|
@@ -357,6 +386,46 @@ namespace :audit do
|
|
|
357
386
|
end
|
|
358
387
|
end
|
|
359
388
|
|
|
389
|
+
# Packaging doctrine (#271): every BUILT gem carries the engine
|
|
390
|
+
# source (vendor-src/libleptris build inputs) and the ext sources;
|
|
391
|
+
# platform gems additionally carry the prebuilt libraries and must
|
|
392
|
+
# NOT carry extensions; the pure ruby gem compiles at install
|
|
393
|
+
# (extensions present). Run over pkg/*.gem before publishing —
|
|
394
|
+
# the gate is what keeps the doctrine from regressing silently.
|
|
395
|
+
desc "Audit built gems for the packaging doctrine (source + binaries + extension policy)"
|
|
396
|
+
task :doctrine do
|
|
397
|
+
gems = Dir.glob("pkg/*.gem")
|
|
398
|
+
abort "audit:doctrine: no gems in pkg/ — build first (rake gem:native:...)" if gems.empty?
|
|
399
|
+
|
|
400
|
+
gems.each do |path|
|
|
401
|
+
spec = Gem::Package.new(path).spec
|
|
402
|
+
files = spec.files.map { |f| f.gsub("\\", "/") }
|
|
403
|
+
|
|
404
|
+
missing = []
|
|
405
|
+
missing << "vendor-src/libleptris/CMakeLists.txt" unless
|
|
406
|
+
files.include?("vendor-src/libleptris/CMakeLists.txt")
|
|
407
|
+
missing << "vendor-src/README.md" unless files.include?("vendor-src/README.md")
|
|
408
|
+
missing << "ext/leptris/native/extconf.rb" unless
|
|
409
|
+
files.include?("ext/leptris/native/extconf.rb")
|
|
410
|
+
|
|
411
|
+
if spec.platform.to_s == "ruby"
|
|
412
|
+
missing << "extensions (compile-at-install)" if spec.extensions.empty?
|
|
413
|
+
else
|
|
414
|
+
binary = files.any? { |f| f =~ %r{\Alib/libleptris\.(so|dylib|dll)\z} } ||
|
|
415
|
+
files.any? { |f| f.start_with?("lib/leptris/vendor/") }
|
|
416
|
+
missing << "prebuilt engine binary" unless binary
|
|
417
|
+
missing << "extensions (platform gems never rebuild)" unless spec.extensions.empty?
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
if missing.empty?
|
|
421
|
+
puts "audit:doctrine: #{File.basename(path)} OK " \
|
|
422
|
+
"(#{spec.platform}, #{files.length} files)"
|
|
423
|
+
else
|
|
424
|
+
abort "audit:doctrine: #{File.basename(path)} MISSING: #{missing.join(', ')}"
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
|
|
360
429
|
# @api private
|
|
361
430
|
def nm_available?
|
|
362
431
|
system("nm", "--version", out: File::NULL, err: File::NULL)
|
|
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.rb
CHANGED
|
@@ -29,10 +29,13 @@ begin
|
|
|
29
29
|
rescue LoadError => e
|
|
30
30
|
raise LoadError, <<~MSG
|
|
31
31
|
Leptris could not load the native libleptris library.
|
|
32
|
-
The ruby-platform gem
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
The ruby-platform gem carries the engine sources under
|
|
33
|
+
vendor-src/ and builds them at install time — a failed build
|
|
34
|
+
here usually means cmake or a C toolchain is missing (check
|
|
35
|
+
the gem install output). Alternatively install the
|
|
36
|
+
platform-specific variant for your system (e.g. `gem install
|
|
37
|
+
leptris --platform=arm64-darwin`), or set LEPTRIS_LIB_PATH to
|
|
38
|
+
a libleptris.{so,dylib,dll} file.
|
|
36
39
|
(Underlying error: #{e.message})
|
|
37
40
|
MSG
|
|
38
41
|
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Sources vendored in this gem (recompile rights; the ruby
|
|
2
|
+
variant builds them at install via extconf.rb):
|
|
3
|
+
|
|
4
|
+
- libleptris v1.9.210
|
|
5
|
+
- utf8proc v2.11.0
|
|
6
|
+
|
|
7
|
+
Rebuild by hand:
|
|
8
|
+
|
|
9
|
+
cmake -B vendor-src/utf8proc/build -S vendor-src/utf8proc -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON -DUTF8PROC_ENABLE_TESTING=OFF
|
|
10
|
+
cmake --build vendor-src/utf8proc/build --config Release
|
|
11
|
+
cmake --install vendor-src/utf8proc/build --prefix <prefix>
|
|
12
|
+
|
|
13
|
+
cmake -B vendor-src/libleptris/build -S vendor-src/libleptris -DCMAKE_BUILD_TYPE=Release -DLEPTRIS_BUILD_SHARED=ON -DLEPTRIS_BUILD_STATIC=OFF -DBUILD_TESTING=OFF -DLEPTRIS_BUILD_CLI=OFF -DLEPTRIS_BUILD_BENCHMARKS=OFF -DLEPTRIS_BUILD_MAN_PAGES=OFF -DLEPTRIS_ENABLE_UTF8PROC=ON -DCMAKE_PREFIX_PATH=<utf8proc-prefix>
|
|
14
|
+
cmake --build vendor-src/libleptris/build --config Release
|
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [1.9.210] - 2026-09-20
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- The node-layout fork's document block (slice 1, #1253): two
|
|
8
|
+
contiguous geometrically-grown regions per document (dense node
|
|
9
|
+
array + string arena) with region-relative byte-offset edges —
|
|
10
|
+
growth never invalidates an offset. Foundation for the pugixml-
|
|
11
|
+
class parse layout (TODO.max-perf/node-layout-fork.md).
|
|
12
|
+
|
|
13
|
+
### Performance
|
|
14
|
+
|
|
15
|
+
- Large-document parse (#1222/#1238 lane, #1255): the parse arena
|
|
16
|
+
(~4x input) exceeded the arena retain cache's 32MB ceiling, so
|
|
17
|
+
every repeated-parse cycle freed and re-malloc'd tens of MB.
|
|
18
|
+
Ceiling raised to 96MB (one document's worth of parking) with a
|
|
19
|
+
thread-cleanup drain and ASAN free-for-real. Measured: 13MB
|
|
20
|
+
attr shape 341 -> 472 MB/s (+38%); the text shape's size-decline
|
|
21
|
+
eliminated (465 -> 163 MB/s falling across 1.5-13MB, now
|
|
22
|
+
~605-627 flat).
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
3
26
|
## [1.9.209] - 2026-09-20
|
|
4
27
|
|
|
5
28
|
### Performance
|
|
@@ -74,9 +74,11 @@ LEPTRIS_API void leptris_thread_cleanup(void) {
|
|
|
74
74
|
extern void leptris_xpath_drain_thread_caches(void);
|
|
75
75
|
extern void leptris_root_doc_drain_thread_caches(void);
|
|
76
76
|
extern void leptris_pool_drain_recycler(void);
|
|
77
|
+
extern void leptris_arena_retain_drain(void);
|
|
77
78
|
leptris_xpath_drain_thread_caches();
|
|
78
79
|
leptris_root_doc_drain_thread_caches();
|
|
79
80
|
leptris_pool_drain_recycler();
|
|
81
|
+
leptris_arena_retain_drain();
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/* ---- Memory management API ---- */
|
|
@@ -51,7 +51,20 @@ static inline size_t align_up(size_t n) {
|
|
|
51
51
|
* twice per document lifetime, so contention is irrelevant. */
|
|
52
52
|
#define ARENA_RETAIN_MIN (256u * 1024u)
|
|
53
53
|
#define ARENA_RETAIN_MAX_BLOCKS 4u
|
|
54
|
-
|
|
54
|
+
/* 96MB: a large-document parse arena (~4x input with the
|
|
55
|
+
* pre-sized estimate) must survive the free to be reused by the
|
|
56
|
+
* next parse — at the old 32MB ceiling, every big-doc cycle
|
|
57
|
+
* freed and re-malloc'd tens of MB (libc madvise showed ~2% of
|
|
58
|
+
* repeated-parse wall time). One document's worth of parking. */
|
|
59
|
+
#define ARENA_RETAIN_MAX_BYTES (96u * 1024u * 1024u)
|
|
60
|
+
|
|
61
|
+
#if defined(__has_feature)
|
|
62
|
+
#if __has_feature(address_sanitizer)
|
|
63
|
+
#define ARENA_RETAIN_ASAN 1
|
|
64
|
+
#endif
|
|
65
|
+
#elif defined(__SANITIZE_ADDRESS__)
|
|
66
|
+
#define ARENA_RETAIN_ASAN 1
|
|
67
|
+
#endif
|
|
55
68
|
|
|
56
69
|
static struct {
|
|
57
70
|
char* base[ARENA_RETAIN_MAX_BLOCKS];
|
|
@@ -106,6 +119,14 @@ static char* retain_take(size_t request, size_t* capacity) {
|
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
static void retain_give(char* base, size_t size) {
|
|
122
|
+
#ifdef ARENA_RETAIN_ASAN
|
|
123
|
+
/* ASAN: retained blocks have no TLS destructor and read as
|
|
124
|
+
* LSan leaks — free for real (the recycle win is measurable
|
|
125
|
+
* only in repeated-parse loops, which ASAN runs don't gate). */
|
|
126
|
+
(void)size;
|
|
127
|
+
leptris_free_hook(base);
|
|
128
|
+
return;
|
|
129
|
+
#endif
|
|
109
130
|
if (size < ARENA_RETAIN_MIN) {
|
|
110
131
|
leptris_free_hook(base);
|
|
111
132
|
return;
|
|
@@ -124,6 +145,18 @@ static void retain_give(char* base, size_t size) {
|
|
|
124
145
|
retain_unlock();
|
|
125
146
|
}
|
|
126
147
|
|
|
148
|
+
/* Host-invoked drain (leptris_thread_cleanup): the retained
|
|
149
|
+
* blocks are process-lifetime parking by design; release them so
|
|
150
|
+
* embedded hosts and leak checkers see a clean exit. */
|
|
151
|
+
void leptris_arena_retain_drain(void) {
|
|
152
|
+
retain_lock();
|
|
153
|
+
for (size_t i = 0; i < g_retain.count; i++)
|
|
154
|
+
leptris_free_hook(g_retain.base[i]);
|
|
155
|
+
g_retain.count = 0;
|
|
156
|
+
g_retain.bytes = 0;
|
|
157
|
+
retain_unlock();
|
|
158
|
+
}
|
|
159
|
+
|
|
127
160
|
char* leptris_arena_buffer_alloc(size_t size) {
|
|
128
161
|
size_t capacity = size;
|
|
129
162
|
char* base = retain_take(size, &capacity);
|
|
@@ -55,6 +55,9 @@ void leptris_arena_reset(LeptrisArena* arena);
|
|
|
55
55
|
* All pointers handed out by the arena become invalid. */
|
|
56
56
|
void leptris_arena_destroy(LeptrisArena* arena);
|
|
57
57
|
|
|
58
|
+
/* Release retained arena blocks (leptris_thread_cleanup drain). */
|
|
59
|
+
void leptris_arena_retain_drain(void);
|
|
60
|
+
|
|
58
61
|
/* Bump-allocate `size` bytes, 8-byte aligned. NOT zeroed.
|
|
59
62
|
* Returns NULL when the request does not fit in remaining space —
|
|
60
63
|
* no fallback allocation, ever (see the contract above). */
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* doc_block.c — the node-layout fork's document block (slice 1).
|
|
2
|
+
* See doc_block.h for the design contract. */
|
|
3
|
+
|
|
4
|
+
#include "doc_block.h"
|
|
5
|
+
#include <stdlib.h>
|
|
6
|
+
#include <string.h>
|
|
7
|
+
|
|
8
|
+
#define DOC_BLOCK_MIN_CAP ((size_t)4096)
|
|
9
|
+
#define DOC_BLOCK_ALIGN ((size_t)8)
|
|
10
|
+
|
|
11
|
+
struct LeptrisDocBlock {
|
|
12
|
+
char* nodes; /* node region base (8-aligned) */
|
|
13
|
+
size_t nodes_cap;
|
|
14
|
+
size_t nodes_used;
|
|
15
|
+
char* strs; /* string region base */
|
|
16
|
+
size_t strs_cap;
|
|
17
|
+
size_t strs_used;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
static char* region_grow(char* p, size_t* cap, size_t need) {
|
|
21
|
+
size_t want = *cap ? *cap : DOC_BLOCK_MIN_CAP;
|
|
22
|
+
while (want < need) want *= 2;
|
|
23
|
+
char* grown = (char*)realloc(p, want);
|
|
24
|
+
if (grown) *cap = want;
|
|
25
|
+
return grown;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
LeptrisDocBlock* leptris_doc_block_create(size_t initial_capacity) {
|
|
29
|
+
LeptrisDocBlock* b =
|
|
30
|
+
(LeptrisDocBlock*)calloc(1, sizeof(*b));
|
|
31
|
+
if (!b) return NULL;
|
|
32
|
+
if (initial_capacity && initial_capacity < DOC_BLOCK_MIN_CAP)
|
|
33
|
+
initial_capacity = DOC_BLOCK_MIN_CAP;
|
|
34
|
+
if (initial_capacity) {
|
|
35
|
+
b->nodes = (char*)malloc(initial_capacity);
|
|
36
|
+
b->strs = (char*)malloc(initial_capacity);
|
|
37
|
+
if (!b->nodes || !b->strs) {
|
|
38
|
+
free(b->nodes);
|
|
39
|
+
free(b->strs);
|
|
40
|
+
free(b);
|
|
41
|
+
return NULL;
|
|
42
|
+
}
|
|
43
|
+
b->nodes_cap = b->strs_cap = initial_capacity;
|
|
44
|
+
}
|
|
45
|
+
return b;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void leptris_doc_block_free(LeptrisDocBlock* b) {
|
|
49
|
+
if (!b) return;
|
|
50
|
+
free(b->nodes);
|
|
51
|
+
free(b->strs);
|
|
52
|
+
free(b);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
size_t leptris_doc_block_node_alloc(LeptrisDocBlock* b, size_t sz) {
|
|
56
|
+
if (!b || sz == 0 || sz > (size_t)-1 / 2) return (size_t)-1;
|
|
57
|
+
size_t off = (b->nodes_used + DOC_BLOCK_ALIGN - 1) &
|
|
58
|
+
~(DOC_BLOCK_ALIGN - 1);
|
|
59
|
+
size_t need = off + sz;
|
|
60
|
+
if (need > b->nodes_cap) {
|
|
61
|
+
char* grown = region_grow(b->nodes, &b->nodes_cap, need);
|
|
62
|
+
if (!grown) return (size_t)-1;
|
|
63
|
+
b->nodes = grown;
|
|
64
|
+
}
|
|
65
|
+
b->nodes_used = need;
|
|
66
|
+
return off;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
size_t leptris_doc_block_str(LeptrisDocBlock* b, const char* s,
|
|
70
|
+
size_t len) {
|
|
71
|
+
if (!b || (!s && len)) return (size_t)-1;
|
|
72
|
+
size_t need = b->strs_used + len + 1;
|
|
73
|
+
if (need > b->strs_cap) {
|
|
74
|
+
char* grown = region_grow(b->strs, &b->strs_cap, need);
|
|
75
|
+
if (!grown) return (size_t)-1;
|
|
76
|
+
b->strs = grown;
|
|
77
|
+
}
|
|
78
|
+
size_t off = b->strs_used;
|
|
79
|
+
if (len) memcpy(b->strs + off, s, len);
|
|
80
|
+
b->strs[off + len] = '\0';
|
|
81
|
+
b->strs_used = need;
|
|
82
|
+
return off;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
void* leptris_doc_block_node(const LeptrisDocBlock* b, size_t off) {
|
|
86
|
+
return (b && off < b->nodes_used) ? b->nodes + off : NULL;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
void* leptris_doc_block_nodes_base(const LeptrisDocBlock* b) {
|
|
90
|
+
return b ? b->nodes : NULL;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
size_t leptris_doc_block_nodes_used(const LeptrisDocBlock* b) {
|
|
94
|
+
return b ? b->nodes_used : 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const char* leptris_doc_block_str_at(const LeptrisDocBlock* b,
|
|
98
|
+
size_t off) {
|
|
99
|
+
return (b && off < b->strs_used) ? b->strs + off : NULL;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
size_t leptris_doc_block_bytes_used(const LeptrisDocBlock* b) {
|
|
103
|
+
return b ? b->nodes_used + b->strs_used : 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
size_t leptris_doc_block_capacity(const LeptrisDocBlock* b) {
|
|
107
|
+
return b ? b->nodes_cap + b->strs_cap : 0;
|
|
108
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* lib/src/memory/doc_block.h — the node-layout fork's document
|
|
2
|
+
* block (TODO.max-perf/node-layout-fork, slice 1).
|
|
3
|
+
*
|
|
4
|
+
* Two contiguous, geometrically-grown regions per document — a
|
|
5
|
+
* dense node array and a string arena. Every reference to either
|
|
6
|
+
* is a byte OFFSET within its region, so growth (realloc) never
|
|
7
|
+
* invalidates an edge: callers hold offsets, not pointers, and
|
|
8
|
+
* refetch region bases after any allocation.
|
|
9
|
+
*
|
|
10
|
+
* Differences vs memory/pool.c (which stays for mutation paths):
|
|
11
|
+
* the pool chains fixed 32KB pages, scattering one tree across
|
|
12
|
+
* pages; each parse node pays compact-edge encoding and detached
|
|
13
|
+
* nodes pay root-doc registration. Here the tree is contiguous
|
|
14
|
+
* (walk locality), edges are plain offsets (no cp16 overflow
|
|
15
|
+
* table), and the owning document is implied by the region base.
|
|
16
|
+
*/
|
|
17
|
+
#ifndef LEPTRIS_MEMORY_DOC_BLOCK_H
|
|
18
|
+
#define LEPTRIS_MEMORY_DOC_BLOCK_H
|
|
19
|
+
|
|
20
|
+
#include <stddef.h>
|
|
21
|
+
#include <stdint.h>
|
|
22
|
+
|
|
23
|
+
#ifdef __cplusplus
|
|
24
|
+
extern "C" {
|
|
25
|
+
#endif
|
|
26
|
+
|
|
27
|
+
typedef struct LeptrisDocBlock LeptrisDocBlock;
|
|
28
|
+
|
|
29
|
+
/* Create with an initial byte capacity per region (grown
|
|
30
|
+
* geometrically on demand; 0 = lazy). NULL on failure. */
|
|
31
|
+
LeptrisDocBlock* leptris_doc_block_create(size_t initial_capacity);
|
|
32
|
+
|
|
33
|
+
void leptris_doc_block_free(LeptrisDocBlock* b);
|
|
34
|
+
|
|
35
|
+
/* Node region: bump-allocate sz bytes, 8-byte aligned. Returns
|
|
36
|
+
* the node's offset in the region (stable across growth) — never
|
|
37
|
+
* a pointer; resolve via leptris_doc_block_node. (size_t)-1 on
|
|
38
|
+
* failure. Growth may realloc: refetch any held pointers. */
|
|
39
|
+
size_t leptris_doc_block_node_alloc(LeptrisDocBlock* b, size_t sz);
|
|
40
|
+
|
|
41
|
+
/* String region: append len bytes plus a NUL. Returns the
|
|
42
|
+
* string's offset in ITS region (stable across growth) or
|
|
43
|
+
* (size_t)-1 on failure. */
|
|
44
|
+
size_t leptris_doc_block_str(LeptrisDocBlock* b, const char* s,
|
|
45
|
+
size_t len);
|
|
46
|
+
|
|
47
|
+
/* Resolve an offset (region base + off, one add). */
|
|
48
|
+
void* leptris_doc_block_node(const LeptrisDocBlock* b, size_t off);
|
|
49
|
+
|
|
50
|
+
void* leptris_doc_block_nodes_base(const LeptrisDocBlock* b);
|
|
51
|
+
size_t leptris_doc_block_nodes_used(const LeptrisDocBlock* b);
|
|
52
|
+
const char* leptris_doc_block_str_at(const LeptrisDocBlock* b,
|
|
53
|
+
size_t off);
|
|
54
|
+
size_t leptris_doc_block_bytes_used(const LeptrisDocBlock* b);
|
|
55
|
+
size_t leptris_doc_block_capacity(const LeptrisDocBlock* b);
|
|
56
|
+
|
|
57
|
+
#ifdef __cplusplus
|
|
58
|
+
}
|
|
59
|
+
#endif
|
|
60
|
+
|
|
61
|
+
#endif /* LEPTRIS_MEMORY_DOC_BLOCK_H */
|
|
@@ -116,6 +116,7 @@ leptris_add_test(test_simd_spans common/test_simd_spans.cpp)
|
|
|
116
116
|
leptris_add_test(test_pool memory/test_pool.cpp)
|
|
117
117
|
leptris_add_test(test_oom memory/test_oom_injection.cpp)
|
|
118
118
|
leptris_add_test(test_arena memory/test_arena.cpp)
|
|
119
|
+
leptris_add_test(test_doc_block memory/test_doc_block.cpp)
|
|
119
120
|
leptris_add_test(test_pool_arena memory/test_pool_arena.cpp)
|
|
120
121
|
leptris_add_test(test_xpath xpath/test_xpath.cpp)
|
|
121
122
|
leptris_add_test(test_exslt xpath/test_exslt.cpp)
|
|
@@ -192,11 +192,25 @@ TEST(Arena, AllPointersWithinSpanForCompactEncoding) {
|
|
|
192
192
|
|
|
193
193
|
/* ---- Retained-block free list (parse fault fix) ---------------------- */
|
|
194
194
|
|
|
195
|
+
#if defined(__has_feature)
|
|
196
|
+
#if __has_feature(address_sanitizer)
|
|
197
|
+
#define ARENA_TESTS_ASAN 1
|
|
198
|
+
#endif
|
|
199
|
+
#elif defined(__SANITIZE_ADDRESS__)
|
|
200
|
+
#define ARENA_TESTS_ASAN 1
|
|
201
|
+
#endif
|
|
202
|
+
|
|
195
203
|
TEST(LeptrisArena, RetainsAndReusesLargeBlocks) {
|
|
196
204
|
/* Blocks >= 256 KB must round-trip through the free list: the
|
|
197
205
|
* next same-size create gets the SAME mapping back (no munmap,
|
|
198
206
|
* no page faults). Machine-independent by construction: it
|
|
199
207
|
* asserts pointer identity, not timing. */
|
|
208
|
+
/* Under ASAN the retain cache frees for real (no TLS
|
|
209
|
+
* destructor; parked blocks read as LSan leaks) — the reuse
|
|
210
|
+
* contract is deliberately disabled there. */
|
|
211
|
+
#if defined(ARENA_TESTS_ASAN)
|
|
212
|
+
GTEST_SKIP() << "retain cache disabled under ASAN";
|
|
213
|
+
#endif
|
|
200
214
|
const size_t big = 512u * 1024u;
|
|
201
215
|
LeptrisArena* a1 = leptris_arena_create(big);
|
|
202
216
|
ASSERT_NE(a1, nullptr);
|
|
@@ -239,6 +253,13 @@ TEST(LeptrisArena, SmallBlocksStillMallocSemantics) {
|
|
|
239
253
|
}
|
|
240
254
|
|
|
241
255
|
TEST(LeptrisArena, BufferRoundTripReusesMapping) {
|
|
256
|
+
/* Under ASAN the retain cache frees for real (no TLS
|
|
257
|
+
* destructor; parked blocks read as LSan leaks) — the reuse
|
|
258
|
+
* contract is deliberately disabled there. */
|
|
259
|
+
#if defined(ARENA_TESTS_ASAN)
|
|
260
|
+
GTEST_SKIP() << "retain cache disabled under ASAN";
|
|
261
|
+
#endif
|
|
262
|
+
|
|
242
263
|
char* b1 = leptris_arena_buffer_alloc(512u * 1024u);
|
|
243
264
|
ASSERT_NE(b1, nullptr);
|
|
244
265
|
memset(b1, 1, 512u * 1024u);
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// test/memory/test_doc_block.cpp - the node-layout fork's document
|
|
2
|
+
// block (slice 1). Falsifiable contract: offsets stay valid across
|
|
3
|
+
// geometric growth, regions stay dense/aligned, boundary inputs
|
|
4
|
+
// are refused.
|
|
5
|
+
|
|
6
|
+
#include <gtest/gtest.h>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <cstdio>
|
|
9
|
+
#include <vector>
|
|
10
|
+
#include "../../src/leptris/memory/doc_block.h"
|
|
11
|
+
|
|
12
|
+
typedef struct {
|
|
13
|
+
uint32_t first_child;
|
|
14
|
+
uint32_t next;
|
|
15
|
+
uint32_t name_off;
|
|
16
|
+
} TNode;
|
|
17
|
+
|
|
18
|
+
TEST(DocBlock, OffsetsSurviveGrowth) {
|
|
19
|
+
LeptrisDocBlock* b = leptris_doc_block_create(0);
|
|
20
|
+
ASSERT_NE(b, nullptr);
|
|
21
|
+
std::vector<uint32_t> offs;
|
|
22
|
+
const int N = 20000;
|
|
23
|
+
for (int i = 0; i < N; i++) {
|
|
24
|
+
size_t off = leptris_doc_block_node_alloc(b, sizeof(TNode));
|
|
25
|
+
ASSERT_NE(off, (size_t)-1);
|
|
26
|
+
ASSERT_EQ(off % 8u, 0u);
|
|
27
|
+
TNode* n = (TNode*)leptris_doc_block_node(b, off);
|
|
28
|
+
ASSERT_NE(n, nullptr);
|
|
29
|
+
n->first_child = 0;
|
|
30
|
+
n->next = i > 0 ? offs[i - 1] : 0;
|
|
31
|
+
n->name_off = 0;
|
|
32
|
+
offs.push_back((uint32_t)off);
|
|
33
|
+
if (i % 1000 == 0) {
|
|
34
|
+
for (size_t k = 1; k < offs.size(); k++) {
|
|
35
|
+
const TNode* p =
|
|
36
|
+
(const TNode*)leptris_doc_block_node(b, offs[k]);
|
|
37
|
+
ASSERT_NE(p, nullptr);
|
|
38
|
+
ASSERT_EQ(p->next, offs[k - 1]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
EXPECT_GT(leptris_doc_block_capacity(b), (size_t)0);
|
|
43
|
+
leptris_doc_block_free(b);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
TEST(DocBlock, StringRegionStableAcrossGrowth) {
|
|
47
|
+
LeptrisDocBlock* b = leptris_doc_block_create(64);
|
|
48
|
+
ASSERT_NE(b, nullptr);
|
|
49
|
+
std::vector<size_t> offs;
|
|
50
|
+
char buf[32];
|
|
51
|
+
for (int i = 0; i < 5000; i++) {
|
|
52
|
+
int n = snprintf(buf, sizeof(buf), "name-%05d", i);
|
|
53
|
+
size_t off = leptris_doc_block_str(b, buf, (size_t)n);
|
|
54
|
+
ASSERT_NE(off, (size_t)-1);
|
|
55
|
+
offs.push_back(off);
|
|
56
|
+
}
|
|
57
|
+
for (int i = 0; i < 5000; i += 500) {
|
|
58
|
+
int n = snprintf(buf, sizeof(buf), "name-%05d", i);
|
|
59
|
+
const char* s = leptris_doc_block_str_at(b, offs[i]);
|
|
60
|
+
ASSERT_NE(s, nullptr);
|
|
61
|
+
EXPECT_EQ(std::memcmp(s, buf, (size_t)n), 0);
|
|
62
|
+
EXPECT_EQ(s[n], '\0');
|
|
63
|
+
}
|
|
64
|
+
leptris_doc_block_free(b);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
TEST(DocBlock, BoundaryInputsRefused) {
|
|
68
|
+
LeptrisDocBlock* b = leptris_doc_block_create(0);
|
|
69
|
+
ASSERT_NE(b, nullptr);
|
|
70
|
+
EXPECT_EQ(leptris_doc_block_node_alloc(b, 0), (size_t)-1);
|
|
71
|
+
EXPECT_EQ(leptris_doc_block_node_alloc(nullptr, 8), (size_t)-1);
|
|
72
|
+
EXPECT_EQ(leptris_doc_block_str(b, nullptr, 3), (size_t)-1);
|
|
73
|
+
size_t off = leptris_doc_block_str(b, "", 0);
|
|
74
|
+
EXPECT_NE(off, (size_t)-1);
|
|
75
|
+
EXPECT_STREQ(leptris_doc_block_str_at(b, off), "");
|
|
76
|
+
EXPECT_EQ(leptris_doc_block_node(b, 1 << 20), nullptr);
|
|
77
|
+
leptris_doc_block_free(b);
|
|
78
|
+
leptris_doc_block_free(nullptr);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
TEST(DocBlock, NodesAndStringsIndependent) {
|
|
82
|
+
LeptrisDocBlock* b = leptris_doc_block_create(0);
|
|
83
|
+
ASSERT_NE(b, nullptr);
|
|
84
|
+
size_t n1 = leptris_doc_block_node_alloc(b, 16);
|
|
85
|
+
size_t s1 = leptris_doc_block_str(b, "abc", 3);
|
|
86
|
+
size_t n2 = leptris_doc_block_node_alloc(b, 16);
|
|
87
|
+
size_t s2 = leptris_doc_block_str(b, "defg", 4);
|
|
88
|
+
EXPECT_NE(n1, n2);
|
|
89
|
+
EXPECT_NE(s1, s2);
|
|
90
|
+
EXPECT_STREQ(leptris_doc_block_str_at(b, s1), "abc");
|
|
91
|
+
EXPECT_STREQ(leptris_doc_block_str_at(b, s2), "defg");
|
|
92
|
+
EXPECT_EQ(leptris_doc_block_nodes_used(b), 32u);
|
|
93
|
+
leptris_doc_block_free(b);
|
|
94
|
+
}
|
|
@@ -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.210",
|
|
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,7 +1,7 @@
|
|
|
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.210.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
@@ -205,6 +205,7 @@ files:
|
|
|
205
205
|
- lib/leptris/xml/xquery.rb
|
|
206
206
|
- lib/leptris/xml/xslt.rb
|
|
207
207
|
- scripts/gem_smoke.rb
|
|
208
|
+
- vendor-src/README.md
|
|
208
209
|
- vendor-src/libleptris/CHANGELOG.md
|
|
209
210
|
- vendor-src/libleptris/CLAUDE.md
|
|
210
211
|
- vendor-src/libleptris/CMakeLists.txt
|
|
@@ -662,6 +663,8 @@ files:
|
|
|
662
663
|
- vendor-src/libleptris/src/leptris/leptris_parse.h
|
|
663
664
|
- vendor-src/libleptris/src/leptris/memory/arena.c
|
|
664
665
|
- vendor-src/libleptris/src/leptris/memory/arena.h
|
|
666
|
+
- vendor-src/libleptris/src/leptris/memory/doc_block.c
|
|
667
|
+
- vendor-src/libleptris/src/leptris/memory/doc_block.h
|
|
665
668
|
- vendor-src/libleptris/src/leptris/memory/pool.c
|
|
666
669
|
- vendor-src/libleptris/src/leptris/memory/pool.h
|
|
667
670
|
- vendor-src/libleptris/src/leptris/rng/parse.c
|
|
@@ -816,6 +819,7 @@ files:
|
|
|
816
819
|
- vendor-src/libleptris/test/html/test_html.cpp
|
|
817
820
|
- vendor-src/libleptris/test/html/test_html5lib.cpp
|
|
818
821
|
- vendor-src/libleptris/test/memory/test_arena.cpp
|
|
822
|
+
- vendor-src/libleptris/test/memory/test_doc_block.cpp
|
|
819
823
|
- vendor-src/libleptris/test/memory/test_oom_injection.cpp
|
|
820
824
|
- vendor-src/libleptris/test/memory/test_pool.cpp
|
|
821
825
|
- vendor-src/libleptris/test/memory/test_pool_arena.cpp
|