leptris 1.9.379.2-aarch64-linux → 1.9.380.0-aarch64-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/CHANGELOG.md +17 -0
- data/ext/leptris/native/native.c +173 -0
- data/lib/leptris/version.rb +1 -1
- data/lib/leptris/xml/element.rb +16 -1
- data/lib/leptris/xml/native.so +0 -0
- data/lib/leptris/xml/native_3_3.so +0 -0
- data/lib/leptris/xml/native_layer.rb +15 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a91b52e8b927c7e0fd2fcaf919f5bb5a80e2e0322ad3eb156070b575b0d42a3
|
|
4
|
+
data.tar.gz: 5799a4d22d3ff84a6ffcd56e7186cf3e414d8f58a6b63f8c083ff396147aad54
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '049afbaa1b7eb3a8cccc9bd2fa69c74ae5d1fbe6b03011103bbc37279d5813a42fade5ca4a49fd6d5853995f2be0896fcebbba0d3bf393e111c822f32e4017d5'
|
|
7
|
+
data.tar.gz: e2048e575a184dd1291ab0097eb92a04adfc4342021b7f5c61dd94bde554126ef661e6f9ecc6052af9f964573e14be21d9d0eb344123ec1ea9b751b11755e66e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
## [1.9.379.3] - 2026-10-22
|
|
2
|
+
|
|
3
|
+
### Fixed
|
|
4
|
+
|
|
5
|
+
- **attribute_nodes desync after interleaved bare-name writes
|
|
6
|
+
(leptris/leptris-ruby#526, PR #533)**: a cold bare-name `[]` read
|
|
7
|
+
nil'd the attribute_nodes/keys/values memos but restamped only the
|
|
8
|
+
attributes version, so the sibling faces served a stamped nil.
|
|
9
|
+
Dropped faces now drop their stamps; a `key?` partial-fill hole
|
|
10
|
+
found by the same 2000-trial sweep is fixed too.
|
|
11
|
+
|
|
12
|
+
### Added — native layer
|
|
13
|
+
|
|
14
|
+
- **Bulk XPath materialization (PR #531)**:
|
|
15
|
+
`xpath_result_rows_bulk` returns the whole node-set as registered
|
|
16
|
+
NativeNodes in one C crossing.
|
|
17
|
+
|
|
1
18
|
## [1.9.379.2] - 2026-10-21
|
|
2
19
|
|
|
3
20
|
### Added — native layer
|
data/ext/leptris/native/native.c
CHANGED
|
@@ -1925,6 +1925,89 @@ static VALUE nf_at_xpath_first(VALUE self, VALUE document,
|
|
|
1925
1925
|
return node;
|
|
1926
1926
|
}
|
|
1927
1927
|
|
|
1928
|
+
/* ---- Bulk XPath rows as registered NativeNodes (#529) ----------
|
|
1929
|
+
* The consumer's iterate-every-row lane pays ONE FFI crossing and
|
|
1930
|
+
* one Ruby wrapper mint PER result row. This face materializes the
|
|
1931
|
+
* whole nodeset in a single call: every real tree node mints
|
|
1932
|
+
* through the document's registered wrapper klasses (#340) and the
|
|
1933
|
+
* native identity cache — the same mint the walk and create faces
|
|
1934
|
+
* use, cache-checked so one address is one Ruby object — so rows
|
|
1935
|
+
* arrive already carrying the consumer's contract, with no
|
|
1936
|
+
* Ruby-visible per-row call. Entries the consumer-klass path can't
|
|
1937
|
+
* represent (synthetic attribute rows, sequence-carrier text,
|
|
1938
|
+
* namespaces, unregistered documents) keep the shared per-entry
|
|
1939
|
+
* binding materializer, exactly what the eager path minted before.
|
|
1940
|
+
* Nodesets free the handle here (TODO.perf/22 eager discipline);
|
|
1941
|
+
* non-nodeset results return Qnil UNFREED — the caller keeps the
|
|
1942
|
+
* scalar path, whose wrap owns the free. */
|
|
1943
|
+
static VALUE nf_xpath_result_rows_bulk(VALUE self, VALUE document,
|
|
1944
|
+
VALUE result_addr)
|
|
1945
|
+
{
|
|
1946
|
+
void *result = (void *)(uintptr_t)NUM2ULL(result_addr);
|
|
1947
|
+
void **buf;
|
|
1948
|
+
int *kinds;
|
|
1949
|
+
size_t scount;
|
|
1950
|
+
long i, n;
|
|
1951
|
+
VALUE cache, out;
|
|
1952
|
+
|
|
1953
|
+
(void)self;
|
|
1954
|
+
resolve_binding_classes();
|
|
1955
|
+
if (f_xp_type(result) != 0) /* XPATH_NODESET only */
|
|
1956
|
+
return Qnil;
|
|
1957
|
+
|
|
1958
|
+
scount = f_xp_count(result);
|
|
1959
|
+
if (scount == 0) {
|
|
1960
|
+
f_xp_free(result);
|
|
1961
|
+
return rb_ary_new2(0);
|
|
1962
|
+
}
|
|
1963
|
+
buf = ruby_xmalloc(scount * sizeof(void *));
|
|
1964
|
+
kinds = ruby_xmalloc(scount * sizeof(int));
|
|
1965
|
+
/* The batch accessor compacts (invalid pointers skip) — n is
|
|
1966
|
+
* the row count AND the index space the name/value accessors
|
|
1967
|
+
* answer on. */
|
|
1968
|
+
scount = f_xp_nodes_ex(result, buf, kinds, scount);
|
|
1969
|
+
n = (long)scount;
|
|
1970
|
+
out = rb_ary_new_capa(n);
|
|
1971
|
+
cache = binding_cache_of(document);
|
|
1972
|
+
for (i = 0; i < n; i++) {
|
|
1973
|
+
void *ptr = buf[i];
|
|
1974
|
+
int tag;
|
|
1975
|
+
VALUE node, key;
|
|
1976
|
+
|
|
1977
|
+
if (ptr == NULL)
|
|
1978
|
+
continue;
|
|
1979
|
+
/* The unified tag space (#477) classifies every entry from
|
|
1980
|
+
* the node word itself: 0..5 real tree kinds — the same
|
|
1981
|
+
* ints the doc-klass map is indexed by — 6/7/8 synthetic
|
|
1982
|
+
* attribute/namespace/text. */
|
|
1983
|
+
tag = (int)f_node_type(ptr);
|
|
1984
|
+
key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
1985
|
+
if (tag >= WS_NODE_ELEMENT && tag <= WS_NODE_DOCTYPE) {
|
|
1986
|
+
VALUE mk = doc_klass_for_kind(document, tag);
|
|
1987
|
+
if (!NIL_P(mk)) {
|
|
1988
|
+
node = rb_hash_aref(native_cache_of(document), key);
|
|
1989
|
+
if (NIL_P(node))
|
|
1990
|
+
node = mint_consumer_node(mk, document, ptr);
|
|
1991
|
+
rb_ary_push(out, node);
|
|
1992
|
+
continue;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
node = materialize_xp_entry(document, cache, result, ptr,
|
|
1996
|
+
kinds[i], (int)i);
|
|
1997
|
+
if (node == m_native_sentinel) {
|
|
1998
|
+
/* Rare kind the entry materializer defers (namespace
|
|
1999
|
+
* rows): mint the plain native twin instead of leaking
|
|
2000
|
+
* the module sentinel into consumer-visible rows. */
|
|
2001
|
+
node = wrap_new(document, ptr);
|
|
2002
|
+
}
|
|
2003
|
+
rb_ary_push(out, node);
|
|
2004
|
+
}
|
|
2005
|
+
ruby_xfree(buf);
|
|
2006
|
+
ruby_xfree(kinds);
|
|
2007
|
+
f_xp_free(result);
|
|
2008
|
+
return out;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
1928
2011
|
/* ---- Bulk attribute materialization (TODO.perf/10) ---------------
|
|
1929
2012
|
* Element#attributes / #keys / #values walk the attribute list
|
|
1930
2013
|
* one FFI call per row (name + value). One C pass builds the
|
|
@@ -2444,6 +2527,92 @@ static VALUE nf_snapshot_rows(VALUE self, VALUE document, VALUE addr)
|
|
|
2444
2527
|
return out;
|
|
2445
2528
|
}
|
|
2446
2529
|
|
|
2530
|
+
/* ---- Columnar preorder walk (leptris-ruby#527) -------------------
|
|
2531
|
+
* The moxml cold walk crossed the boundary once per element
|
|
2532
|
+
* (children_read + name_read per node, ~23% of cold-walk
|
|
2533
|
+
* samples). walk_columnar returns a WHOLE subtree's walk data in
|
|
2534
|
+
* ONE crossing: [names, handles, depths] — three parallel arrays,
|
|
2535
|
+
* preorder, the addressed node at depth 0.
|
|
2536
|
+
*
|
|
2537
|
+
* names: interned (frozen, shared) fstrings for elements, nil for
|
|
2538
|
+
* the child kinds with no name (text/comment/cdata/pi) — the same
|
|
2539
|
+
* discipline as nn_name, so repeat names allocate zero.
|
|
2540
|
+
* handles: NativeNodes (TypedData path) minted through the
|
|
2541
|
+
* doc-klass map (#340) when the document registered wrapper
|
|
2542
|
+
* klasses, else the kind-resolved NativeNode subclasses — every
|
|
2543
|
+
* mint lands in the document's native-cache identity, so handles
|
|
2544
|
+
* here ARE the children_read/visit wrappers and re-walks return
|
|
2545
|
+
* identical objects.
|
|
2546
|
+
* depths: root-relative ints; document/doctype receivers walk
|
|
2547
|
+
* their children at depth 0 (same entry rule as snapshot_rows). */
|
|
2548
|
+
static VALUE columnar_node_for(VALUE document, VALUE owner_klass,
|
|
2549
|
+
void *ptr, int kind)
|
|
2550
|
+
{
|
|
2551
|
+
VALUE key = ULL2NUM((uint64_t)(uintptr_t)ptr);
|
|
2552
|
+
VALUE cache = native_cache_of(document);
|
|
2553
|
+
VALUE node = rb_hash_aref(cache, key);
|
|
2554
|
+
struct native_node *n;
|
|
2555
|
+
|
|
2556
|
+
if (!NIL_P(node)) return node;
|
|
2557
|
+
VALUE mk = doc_klass_for_kind(document, kind);
|
|
2558
|
+
if (!NIL_P(mk))
|
|
2559
|
+
return mint_consumer_node(mk, document, ptr);
|
|
2560
|
+
node = nn_allocate(klass_for_kind(owner_klass, kind));
|
|
2561
|
+
TypedData_Get_Struct(node, struct native_node, &nn_type, n);
|
|
2562
|
+
n->ptr = ptr;
|
|
2563
|
+
n->document = document;
|
|
2564
|
+
rb_hash_aset(cache, key, node);
|
|
2565
|
+
return node;
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
static void walk_columnar_impl(VALUE document, VALUE owner_klass,
|
|
2569
|
+
void *node, int depth, VALUE names,
|
|
2570
|
+
VALUE handles, VALUE depths)
|
|
2571
|
+
{
|
|
2572
|
+
int kind = f_node_type(node);
|
|
2573
|
+
void *c;
|
|
2574
|
+
|
|
2575
|
+
if (kind == WS_NODE_ELEMENT) {
|
|
2576
|
+
const char *name = f_elem_name(node);
|
|
2577
|
+
rb_ary_push(names, name
|
|
2578
|
+
? rb_enc_interned_str(name, strlen(name),
|
|
2579
|
+
rb_utf8_encoding())
|
|
2580
|
+
: Qnil);
|
|
2581
|
+
rb_ary_push(handles, columnar_node_for(document, owner_klass,
|
|
2582
|
+
node, kind));
|
|
2583
|
+
rb_ary_push(depths, INT2NUM(depth));
|
|
2584
|
+
for (c = f_first_child(node); c; c = f_next_sibling(c)) {
|
|
2585
|
+
walk_columnar_impl(document, owner_klass, c, depth + 1,
|
|
2586
|
+
names, handles, depths);
|
|
2587
|
+
}
|
|
2588
|
+
} else if (kind == WS_NODE_TEXT || kind == WS_NODE_COMMENT ||
|
|
2589
|
+
kind == WS_NODE_CDATA || kind == WS_NODE_PI) {
|
|
2590
|
+
rb_ary_push(names, Qnil);
|
|
2591
|
+
rb_ary_push(handles, columnar_node_for(document, owner_klass,
|
|
2592
|
+
node, kind));
|
|
2593
|
+
rb_ary_push(depths, INT2NUM(depth));
|
|
2594
|
+
} else if (kind == WS_NODE_DOCTYPE || kind == 9 /* DOCUMENT */) {
|
|
2595
|
+
for (c = f_first_child(node); c; c = f_next_sibling(c)) {
|
|
2596
|
+
walk_columnar_impl(document, owner_klass, c, depth,
|
|
2597
|
+
names, handles, depths);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
static VALUE nf_walk_columnar(VALUE self, VALUE root)
|
|
2603
|
+
{
|
|
2604
|
+
struct native_node *n;
|
|
2605
|
+
VALUE names, handles, depths;
|
|
2606
|
+
(void)self;
|
|
2607
|
+
TypedData_Get_Struct(root, struct native_node, &nn_type, n);
|
|
2608
|
+
names = rb_ary_new();
|
|
2609
|
+
handles = rb_ary_new();
|
|
2610
|
+
depths = rb_ary_new();
|
|
2611
|
+
walk_columnar_impl(n->document, rb_obj_class(root), n->ptr, 0,
|
|
2612
|
+
names, handles, depths);
|
|
2613
|
+
return rb_ary_new3(3, names, handles, depths);
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2447
2616
|
/* ---- Struct plan executor (moxml "miles faster" round) ------------
|
|
2448
2617
|
* The consumer's shape compiles to spec: name (interned String) ->
|
|
2449
2618
|
* [Struct class, attrs Hash (name -> slot Symbol), text slot Symbol
|
|
@@ -3653,6 +3822,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3653
3822
|
nf_at_xpath_first, 2);
|
|
3654
3823
|
rb_define_module_function(m_native, "materialize_xpath",
|
|
3655
3824
|
nf_materialize_xpath, 2);
|
|
3825
|
+
rb_define_module_function(m_native, "xpath_result_rows_bulk",
|
|
3826
|
+
nf_xpath_result_rows_bulk, 2);
|
|
3656
3827
|
rb_define_module_function(m_native, "set_binding_name",
|
|
3657
3828
|
nf_set_binding_name, 3);
|
|
3658
3829
|
rb_define_module_function(m_native, "set_binding_text",
|
|
@@ -3697,6 +3868,8 @@ LEPTRIS_INIT_EXPORT void Init_native(void)
|
|
|
3697
3868
|
nf_snapshot_subtree, 2);
|
|
3698
3869
|
rb_define_module_function(m_native, "snapshot_rows",
|
|
3699
3870
|
nf_snapshot_rows, 2);
|
|
3871
|
+
rb_define_module_function(m_native, "walk_columnar",
|
|
3872
|
+
nf_walk_columnar, 1);
|
|
3700
3873
|
rb_define_module_function(m_native, "attribute_pairs",
|
|
3701
3874
|
nf_attribute_pairs, 2);
|
|
3702
3875
|
rb_define_module_function(m_native, "plan_structs",
|
data/lib/leptris/version.rb
CHANGED
data/lib/leptris/xml/element.rb
CHANGED
|
@@ -117,6 +117,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
117
117
|
# Drop any full-face memos from a prior version — otherwise a
|
|
118
118
|
# post-mutation cold [] would stamp the new version onto a
|
|
119
119
|
# stale @attributes hash and attributes would serve it.
|
|
120
|
+
# ruby#526: only @attributes shares @attributes_version with
|
|
121
|
+
# @attr_values (both restamped below), so only it needs the
|
|
122
|
+
# drop; @attribute_nodes/@keys/@values carry their OWN stamps.
|
|
123
|
+
# Niling those WITHOUT restamping left a nil memo whose stamp
|
|
124
|
+
# still matched the (unadvanced) document version — the next
|
|
125
|
+
# attribute_nodes/keys/values call memo-hit the nil and served
|
|
126
|
+
# it (consumers reading `attribute_nodes || []` saw []).
|
|
127
|
+
# Dropping a face means dropping both halves.
|
|
120
128
|
ensure_alive!
|
|
121
129
|
v = if @addr_reads_fast
|
|
122
130
|
Leptris::XML::Native.fast_attribute(@c_address, name)
|
|
@@ -126,8 +134,11 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
126
134
|
@attr_values = { name => v }
|
|
127
135
|
@attributes = nil
|
|
128
136
|
@attribute_nodes = nil
|
|
137
|
+
@attribute_nodes_version = nil
|
|
129
138
|
@keys = nil
|
|
139
|
+
@keys_version = nil
|
|
130
140
|
@values = nil
|
|
141
|
+
@values_version = nil
|
|
131
142
|
@attributes_version = @document.version
|
|
132
143
|
return v
|
|
133
144
|
end
|
|
@@ -188,10 +199,14 @@ class Leptris::XML::Element < Leptris::XML::Node
|
|
|
188
199
|
# Versioned attribute memo first (TODO.perf/21): a hit proves
|
|
189
200
|
# presence without the engine round-trip; the memo cannot
|
|
190
201
|
# prove absence for a partial fill, so misses fall through.
|
|
202
|
+
# ruby#526 sibling: a PARTIAL fill's key proves only "queried"
|
|
203
|
+
# — the cold [] caches queried-absent names as nil, so a nil
|
|
204
|
+
# entry without the full face is ambiguous exactly as in #[]'
|
|
205
|
+
# version-matched read and must fall through to the engine.
|
|
191
206
|
n = name.to_s
|
|
192
207
|
values = @attr_values
|
|
193
208
|
if @document && values && @attributes_version == @document.version &&
|
|
194
|
-
values.key?(n)
|
|
209
|
+
values.key?(n) && (!values[n].nil? || @attributes)
|
|
195
210
|
return true
|
|
196
211
|
end
|
|
197
212
|
return true if Leptris::XML::FFI.leptris_element_has_attribute(
|
data/lib/leptris/xml/native.so
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -109,6 +109,21 @@ class Leptris::XML::Document
|
|
|
109
109
|
Leptris::XML::Native.register_doc_klasses(self, map)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# leptris-ruby#529: the whole XPath node-set result as an Array of
|
|
113
|
+
# NativeNodes in ONE C call — the iterate-every-row lane's bulk
|
|
114
|
+
# face. Rows mint through the registered wrapper klasses (#340)
|
|
115
|
+
# with native-cache identity; synthetic attribute/sequence rows
|
|
116
|
+
# keep the ResultAttr/ResultText wrappers the eager path minted.
|
|
117
|
+
# Takes the raw result pointer eval_ptrs returned and CONSUMES it
|
|
118
|
+
# (nodesets free the handle in C); a non-nodeset result answers
|
|
119
|
+
# nil and leaves the pointer with the caller — route it through
|
|
120
|
+
# Searchable.wrap_xpath_result for the scalar faces.
|
|
121
|
+
def xpath_result_rows_bulk(result_ptr)
|
|
122
|
+
Leptris::XML::Native.xpath_result_rows_bulk(
|
|
123
|
+
self, result_ptr.respond_to?(:address) ? result_ptr.address
|
|
124
|
+
: result_ptr)
|
|
125
|
+
end
|
|
126
|
+
|
|
112
127
|
# Builder factories that return NativeNodes (#149): one C call
|
|
113
128
|
# and a TypedData wrap — no FFI::Pointer, no wrap_fresh path.
|
|
114
129
|
# (context, adapter) pre-writes the consumer's contract ivars in
|
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.380.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ffi
|