leptris 1.9.162.7-aarch64-linux → 1.9.162.9-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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fd7bbeac883e66c231e399cd28012ab81f1b521f975d53cfd5bb6177325f7454
4
- data.tar.gz: fc3b8e4f3a7ca3452dbf29fa1a82d6bf779f2cc50889dc7e9ae6019ded764609
3
+ metadata.gz: 930a88268698162d62816a60f82f19f5258cb5fd14f2b89106e234823d0a5363
4
+ data.tar.gz: eae4cad381d8792f156210a068c504569b9005963279742d95d842b6750be5ab
5
5
  SHA512:
6
- metadata.gz: b900b46f8b64857907a949865f26b82835c3e2fd75641673ff2de1c44b3d7e7c7ae97c42c8f89382f87abb9a72d8a46dd5b7258d171e0a2b38964fc26b59e706
7
- data.tar.gz: 30c3600e9a9e0595cc2d1f9b2a1c01379ab532bce6818cc869c7ff06e13190e0bf4963afbba7b217ba24625073eab808b1a7bcf692517aa882b627823f72c4d0
6
+ metadata.gz: edab3b443d0e7ea7ecf664e23f149c49136f1f6c33244712eadc9254685d0143dcb5d9bcf574f0d7ca61d900fef9c7665a81fe13216bd565d6b3ab9338491733
7
+ data.tar.gz: '012368af4478e4cd8d3d8ba0b5f3789adf5c12c3adf21d939d1c7d87759e74b4c5cd4a475280a64e027f9556f498a4aa1069480352a920677031fae97f0bcfb4'
data/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ 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.162.9] - 2026-09-14
9
+
10
+ ### Performance
11
+
12
+ - **TODO.perf/01 — ext-accelerated default reads**: with the
13
+ native layer loaded, the DEFAULT binding classes' hot reads
14
+ (`Element#name`, `#content`, `#prefix`, `#[]`'s engine lookups)
15
+ call the ext directly (one C-API dispatch + UTF-8 string build —
16
+ no FFI marshaling): name 276->84ns, attribute 373->199ns per
17
+ read. Warm loops are memo-served and unaffected; the win lands
18
+ on cold one-shot passes (fresh doc, single read per node):
19
+ 13-25% on the #187 workload shape. Scope-owned (iterparse)
20
+ elements stay on the FFI path (pool addresses recycle).
21
+ - **TODO.perf/02 — auto-enable**: `require "leptris"` quietly
22
+ activates the native acceleration when the compiled bundle is
23
+ present (platform gems). Falls back to pure FFI when absent
24
+ (ruby-variant on TruffleRuby/JRuby). `LEPTRIS_NO_NATIVE=1`
25
+ forces the FFI path. The explicit `native_layer` require remains
26
+ the parallel-API entry for NativeNode builders.
27
+
28
+ ## [1.9.162.8] - 2026-09-14
29
+
30
+ ### Added
31
+
32
+ - **Zero-setup TruffleRuby/JRuby (#160)**: the `ruby`-platform gem
33
+ now vendors `libleptris` + `libutf8proc` for arm64/x86_64-darwin
34
+ and x86_64/aarch64-linux (plus musl fallbacks) under
35
+ `lib/leptris/vendor/<platform>/`; `FFI.libleptris_candidates`
36
+ probes them first at require time. Both engines activate and
37
+ load with no system library and no env vars — the release
38
+ assembly gathers the binaries from every platform build leg.
39
+ MRI behavior is unchanged (platform gems resolve first); a
40
+ forced `--platform ruby` MRI install also works via the vendor
41
+ tree. Fixed a split-library hazard along the way: the native
42
+ read layer now resolves the exact image the FFI layer loads
43
+ (two differently-pathed copies split per-document state).
44
+
8
45
  ## [1.9.162.7] - 2026-09-14
9
46
 
10
47
  ### Added
data/README.adoc CHANGED
@@ -60,20 +60,21 @@ export LEPTRIS_LIB_PATH=/usr/local/lib/libleptris.dylib
60
60
  If Leptris can't find the library at startup, every `parse` call raises
61
61
  `LoadError`.
62
62
 
63
- === Alternative engines: TruffleRuby and JRuby (#160)
63
+ === Alternative engines: TruffleRuby and JRuby (#160) — zero setup
64
64
 
65
- The gem ships a plain `ruby`-platform variant alongside the precompiled
66
- platform gems, so Bundler activates `leptris` on TruffleRuby and JRuby
67
- (the binding is FFI-based — no MRI C extension anywhere). Those engines
68
- filter out precompiled platform gems at activation and always resolve
69
- the `ruby` variant, which does not vendor the native library:
65
+ The `ruby`-platform variant (what TruffleRuby and JRuby resolve)
66
+ vendors precompiled `libleptris` + `libutf8proc` binaries for the
67
+ common engine platforms under `lib/leptris/vendor/`:
70
68
 
71
- 1. Install `libleptris` on the host — build from source (above) or set
72
- `LEPTRIS_LIB_PATH=/path/to/libleptris.{so,dylib}`.
73
- 2. `gem install leptris` (or `bundle install`) — activation succeeds
74
- like on MRI, and FFI loads the system library at require time.
69
+ - `arm64-darwin`, `x86_64-darwin`
70
+ - `x86_64-linux`, `aarch64-linux` (glibc; the musl variants are
71
+ tried as fallback for Alpine hosts)
75
72
 
76
- The startup `LoadError` names this remedy when the library is missing.
73
+ `gem install leptris` just works on both engines — the FFI layer
74
+ selects the matching vendored binary at require time (the binding
75
+ is FFI-based; no MRI C extension anywhere). `LEPTRIS_LIB_PATH`
76
+ still overrides, and a system library remains the fallback for
77
+ OSes the variant does not carry.
77
78
 
78
79
  == Parsing
79
80
 
data/Rakefile CHANGED
@@ -189,9 +189,15 @@ task "gem:native:any" do
189
189
  sh "rake platform:any gem"
190
190
  end
191
191
 
192
- desc "Define the gem task to build the pure-Ruby gem"
192
+ desc "Define the gem task to build the ruby-platform gem"
193
193
  task "platform:any" do
194
194
  spec = Gem::Specification::load("leptris.gemspec").dup
195
+ # Zero-setup TruffleRuby/JRuby (#160): per-OS binaries staged
196
+ # under lib/leptris/vendor/<platform>/ by the release assembly
197
+ # (or by hand: cp from each platform build). Absent on a plain
198
+ # checkout — the variant then carries no binaries and the
199
+ # system-library / LEPTRIS_LIB_PATH path applies as before.
200
+ spec.files += Dir.glob("lib/leptris/vendor/**/*")
195
201
  task = Gem::PackageTask.new(spec)
196
202
  task.define
197
203
  end
@@ -0,0 +1,25 @@
1
+ # 01 — Ext-accelerated default reads (no FFI marshaling on hot paths)
2
+
3
+ Status: SHIPPED (1.9.162.9) — name/content/prefix/[]-cold-path ride
4
+ the ext when the bundle is loaded. Per-read 276->84ns (3.3x,
5
+ pre-address) / attr 373->199ns. HONEST GATE FINDING: warm loops
6
+ cannot see it (memos serve after first touch) — the gate is the
7
+ COLD one-shot pass (fresh doc, one read per node): 13-25% on the
8
+ #187 shape. UTF-8 encoding via rb_utf8_str_new_cstr (suite specs
9
+ run both paths in one process: native_layer_spec sets the flag
10
+ mid-suite). Remaining: children bulk for binding classes.
11
+
12
+ The default binding's hot reads each pay FFI per-call marshaling
13
+ (:string returns allocate through FFI, args marshal through the
14
+ boundary — ~200-500ns vs ~50ns for a direct ext call). The native
15
+ bundle already dlsyms every reader; add address-based C functions
16
+ the DEFAULT binding classes call when the bundle is loaded:
17
+
18
+ - Element#name / #content / #[]
19
+ - Node#children / #element_children (bulk: one C pass allocating
20
+ binding wrappers — the bulk_wrap shape, 1.27-1.30x measured)
21
+ - Element#expanded_name (three out-params today)
22
+
23
+ Gate: suite green with AND without the bundle; drift-record rows
24
+ (attr reads 1.88x, text 6.18x) must improve; cold-walk gate must
25
+ not regress (cold-walk-regression-gate memory).
@@ -0,0 +1,19 @@
1
+ # 02 — Auto-enable the native layer when the bundle is present
2
+
3
+ Status: SHIPPED (1.9.162.9) — require "leptris" quietly
4
+ probes for the native bundle and activates when present.
5
+ LEPTRIS_NO_NATIVE=1 forces the FFI path (suite exercises both).
6
+ The explicit native_layer require remains the parallel-API entry
7
+ for NativeNode builders.
8
+
9
+ Today `require "leptris/xml/native_layer"` is opt-in. End-state:
10
+ `require "leptris"` quietly probes for the vendored native.bundle
11
+ and, when present, activates the acceleration (01 makes the
12
+ default classes benefit). Silent fallback to FFI when absent
13
+ (ruby-variant on TruffleRuby/JRuby, custom builds). The explicit
14
+ native_layer require remains the parallel-API entry
15
+ (NativeNode builders).
16
+
17
+ Requires: 01 (so auto-enable accelerates the DEFAULT API, not a
18
+ parallel one), and a divergence test — same specs pass in both
19
+ modes (the suite already runs both; make it explicit).
@@ -0,0 +1,13 @@
1
+ # 03 — Bulk XPath result materialization
2
+
3
+ Status: TODO
4
+
5
+ NodeSet#fetch materializes results per node (wrap per item +
6
+ ResultText/ResultAttr captures). The #185 profile counted
7
+ NodeSet#initialize at 2.8% and Class#new 11.7% of walk wall —
8
+ result materialization shares that class of cost. Batch via the
9
+ ext: one C pass over leptris_xpath_result_get_node(i) building
10
+ the Ruby array with kinds, capturing text values in C.
11
+
12
+ Gate: xpath-heavy specs unchanged; xpath row (28,878 ips small)
13
+ improves on the battery.
@@ -0,0 +1,11 @@
1
+ # 04 — Serialization buffer cycle in C
2
+
3
+ Status: TODO
4
+
5
+ FFI.serialize_into_string does the two-call sized protocol
6
+ (needed probe, then copy) with Ruby-side buffer management per
7
+ to_xml call. Bind the cycle in the ext: one C function taking the
8
+ ffi_function pointer + node address, returning a Ruby String.
9
+
10
+ Gate: serialize row (3.24x baseline) improves; c14n specs
11
+ byte-identical.
@@ -0,0 +1,12 @@
1
+ # 05 — Complete the NativeNode read surface
2
+
3
+ Status: TODO
4
+
5
+ Missing on NativeNode vs Element: #attributes (bulk attr hash),
6
+ #attribute_nodes, #namespaces / #prefix, #line / #byte_offset,
7
+ #inner_html / #to_xml (delegate to binding serialization), write
8
+ faces beyond add_child (set_attribute, set_text, remove). Reads
9
+ first; writes stay FFI (cold) unless measured otherwise.
10
+
11
+ Gate: native_layer specs mirror the binding feature specs;
12
+ descriptor_walk parity where applicable.
@@ -0,0 +1,10 @@
1
+ # 06 — Native-layer legs in the perf battery + drift record
2
+
3
+ Status: TODO
4
+
5
+ benchmark/ gains native-layer legs (walk/build/attr rows via
6
+ NativeNode beside the binding rows) so the drift record tracks
7
+ both surfaces; TODO.restructure/17's record gains a native table.
8
+
9
+ Gate: one command (rake benchmark:all or the two scripts) emits
10
+ both tables; load-gated per the shared-machine discipline.
@@ -21,6 +21,7 @@ typedef int (*children_ex_fn)(void *, void **, int *, int);
21
21
  typedef int (*node_type_fn)(void *);
22
22
  typedef void *(*next_sibling_fn)(void *);
23
23
  typedef void *(*parent_fn)(void *);
24
+ typedef const char *(*elem_prefix_fn)(void *);
24
25
  typedef const char *(*element_text_fn)(void *);
25
26
  typedef void *(*doc_create_fn)(void);
26
27
  typedef void *(*elem_create_fn)(void *, const char *);
@@ -37,6 +38,7 @@ static children_ex_fn f_children_ex;
37
38
  static node_type_fn f_node_type;
38
39
  static next_sibling_fn f_next_sibling;
39
40
  static parent_fn f_parent;
41
+ static elem_prefix_fn f_elem_prefix;
40
42
  static element_text_fn f_element_text;
41
43
  static doc_create_fn f_doc_create;
42
44
  static elem_create_fn f_elem_create;
@@ -94,6 +96,7 @@ static void resolve_symbols(const char *lib_path)
94
96
  f_node_type = (node_type_fn)lib_sym(h, "leptris_node_get_type");
95
97
  f_next_sibling = (next_sibling_fn)lib_sym(h, "leptris_node_next_sibling");
96
98
  f_parent = (parent_fn)lib_sym(h, "leptris_node_parent");
99
+ f_elem_prefix = (elem_prefix_fn)lib_sym(h, "leptris_element_prefix");
97
100
  f_element_text = (element_text_fn)lib_sym(h, "leptris_element_text");
98
101
  f_doc_create = (doc_create_fn)lib_sym(h, "leptris_document_create");
99
102
  f_elem_create = (elem_create_fn)lib_sym(h, "leptris_element_create");
@@ -105,7 +108,8 @@ static void resolve_symbols(const char *lib_path)
105
108
  if (!f_elem_name || !f_text_content || !f_attr ||
106
109
  !f_children_ex || !f_node_type || !f_next_sibling || !f_parent ||
107
110
  !f_element_text || !f_doc_create || !f_elem_create || !f_text_create ||
108
- !f_create_child || !f_append_child || !f_set_root || !f_doc_free)
111
+ !f_create_child || !f_append_child || !f_set_root || !f_doc_free ||
112
+ !f_elem_prefix)
109
113
  rb_raise(rb_eRuntimeError, "libleptris symbols missing");
110
114
  }
111
115
 
@@ -115,7 +119,7 @@ static VALUE nn_name(VALUE self)
115
119
  const char *s;
116
120
  TypedData_Get_Struct(self, struct native_node, &nn_type, n);
117
121
  s = f_elem_name(n->ptr);
118
- return s ? rb_str_new_cstr(s) : Qnil;
122
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
119
123
  }
120
124
 
121
125
  static VALUE nn_content(VALUE self)
@@ -127,7 +131,7 @@ static VALUE nn_content(VALUE self)
127
131
  * nodes carry it directly. */
128
132
  s = f_node_type(n->ptr) == 0 ? f_element_text(n->ptr)
129
133
  : f_text_content(n->ptr);
130
- return s ? rb_str_new_cstr(s) : Qnil;
134
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
131
135
  }
132
136
 
133
137
  static VALUE nn_attribute(VALUE self, VALUE name)
@@ -136,7 +140,7 @@ static VALUE nn_attribute(VALUE self, VALUE name)
136
140
  const char *s;
137
141
  TypedData_Get_Struct(self, struct native_node, &nn_type, n);
138
142
  s = f_attr(n->ptr, StringValueCStr(name));
139
- return s ? rb_str_new_cstr(s) : Qnil;
143
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
140
144
  }
141
145
 
142
146
  static VALUE nn_allocate(VALUE klass)
@@ -355,6 +359,43 @@ static VALUE nn_address(VALUE self)
355
359
  return ULL2NUM((uint64_t)(uintptr_t)n->ptr);
356
360
  }
357
361
 
362
+ /* ---- Address-based fast readers (TODO.perf/01) -----------------
363
+ * The DEFAULT binding classes call these when the bundle is
364
+ * loaded: one C-API dispatch + rb_utf8_str_new_cstr — no FFI
365
+ * marshaling. Addresses come from FFI::Pointer#address (a Ruby
366
+ * ivar read on the ffi gem's Pointer). */
367
+ static VALUE nf_name(VALUE self, VALUE addr)
368
+ {
369
+ const char *s;
370
+ (void)self;
371
+ s = f_elem_name((void *)(uintptr_t)NUM2ULL(addr));
372
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
373
+ }
374
+
375
+ static VALUE nf_element_text(VALUE self, VALUE addr)
376
+ {
377
+ const char *s;
378
+ (void)self;
379
+ s = f_element_text((void *)(uintptr_t)NUM2ULL(addr));
380
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
381
+ }
382
+
383
+ static VALUE nf_attribute(VALUE self, VALUE addr, VALUE name)
384
+ {
385
+ const char *s;
386
+ (void)self;
387
+ s = f_attr((void *)(uintptr_t)NUM2ULL(addr), StringValueCStr(name));
388
+ return s ? rb_utf8_str_new_cstr(s) : Qnil;
389
+ }
390
+
391
+ static VALUE nf_prefix(VALUE self, VALUE addr)
392
+ {
393
+ const char *s;
394
+ (void)self;
395
+ s = f_elem_prefix((void *)(uintptr_t)NUM2ULL(addr));
396
+ return (s && *s) ? rb_utf8_str_new_cstr(s) : Qnil;
397
+ }
398
+
358
399
  static VALUE nn_from(VALUE klass, VALUE document, VALUE element)
359
400
  {
360
401
  /* Element address comes through the binding's #c_ptr address;
@@ -383,7 +424,7 @@ static VALUE leptris_native_resolve_rb(VALUE self, VALUE lib_path)
383
424
 
384
425
  void Init_native(void)
385
426
  {
386
- VALUE m_leptris, m_xml;
427
+ VALUE m_leptris, m_xml, m_native;
387
428
 
388
429
  m_leptris = rb_define_module("Leptris");
389
430
  m_xml = rb_define_module_under(m_leptris, "XML");
@@ -411,4 +452,10 @@ void Init_native(void)
411
452
  rb_define_method(c_native_node, "node_type", nn_type_sym, 0);
412
453
  rb_define_singleton_method(c_native_node, "resolve!",
413
454
  leptris_native_resolve_rb, 1);
455
+
456
+ m_native = rb_define_module_under(m_xml, "Native");
457
+ rb_define_module_function(m_native, "fast_name", nf_name, 1);
458
+ rb_define_module_function(m_native, "fast_element_text", nf_element_text, 1);
459
+ rb_define_module_function(m_native, "fast_attribute", nf_attribute, 2);
460
+ rb_define_module_function(m_native, "fast_prefix", nf_prefix, 1);
414
461
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Leptris
4
- VERSION = "1.9.162.7"
4
+ VERSION = "1.9.162.9"
5
5
  end
@@ -1,10 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Leptris::XML::Element < Leptris::XML::Node
4
+ # TODO.perf/01: with the native bundle loaded, hot reads go
5
+ # through the ext (one C-API dispatch + rb_str_new_cstr — no FFI
6
+ # marshaling; ~3x per read). defined? is near-free when absent.
7
+ # Scope-owned (iterparse-yield) elements stay on the FFI path:
8
+ # their pool addresses recycle across yields and their lifetime
9
+ # rules ride the IterationScope seam — the ext fast path is for
10
+ # document-owned trees.
11
+ def native_fast?
12
+ defined?(Leptris::XML::NATIVE_FAST) && !scope_owned?
13
+ end
14
+
4
15
  def name
5
16
  return @name if @name
6
17
  ensure_alive!
7
- @name = Leptris::XML::FFI.leptris_element_name(@c_ptr)
18
+ @name = if native_fast?
19
+ Leptris::XML::Native.fast_name(@c_ptr.address)
20
+ else
21
+ Leptris::XML::FFI.leptris_element_name(@c_ptr)
22
+ end
8
23
  end
9
24
  alias_method :node_name, :name
10
25
 
@@ -19,7 +34,11 @@ class Leptris::XML::Element < Leptris::XML::Node
19
34
  def content
20
35
  return @content if memo_hit?(@content_version)
21
36
  ensure_alive!
22
- result = Leptris::XML::FFI.leptris_element_text(@c_ptr)
37
+ result = if native_fast?
38
+ Leptris::XML::Native.fast_element_text(@c_ptr.address)
39
+ else
40
+ Leptris::XML::FFI.leptris_element_text(@c_ptr)
41
+ end
23
42
  if @document
24
43
  @content = result
25
44
  @content_version = @document.version
@@ -60,7 +79,11 @@ class Leptris::XML::Element < Leptris::XML::Node
60
79
  # engine call resolves it, then the name is cached.
61
80
  return v if !v.nil? || @attributes
62
81
  ensure_alive!
63
- v = Leptris::XML::FFI.leptris_element_attribute(@c_ptr, name)
82
+ v = if native_fast?
83
+ Leptris::XML::Native.fast_attribute(@c_ptr.address, name)
84
+ else
85
+ Leptris::XML::FFI.leptris_element_attribute(@c_ptr, name)
86
+ end
64
87
  values[name] = v
65
88
  return v
66
89
  end
@@ -203,7 +226,12 @@ class Leptris::XML::Element < Leptris::XML::Node
203
226
  # or nil when the element has none.
204
227
  def prefix
205
228
  ensure_alive!
206
- Leptris::XML::FFI.leptris_element_prefix(@c_ptr)
229
+ if native_fast?
230
+ Leptris::XML::Native.fast_prefix(@c_ptr.address)
231
+ else
232
+ result = Leptris::XML::FFI.leptris_element_prefix(@c_ptr)
233
+ result if result && !result.empty?
234
+ end
207
235
  end
208
236
 
209
237
  # The written qualified name — "foo:child" with a prefix, the
@@ -7,6 +7,50 @@ module Leptris
7
7
  module FFI
8
8
  extend ::FFI::Library
9
9
 
10
+ # The per-OS vendor directory of the ruby-platform gem
11
+ # (zero-setup TruffleRuby/JRuby, #160): binaries for the
12
+ # common engines' OSes under lib/leptris/vendor/<platform>/.
13
+ # Returns [] in platform gems (no vendor tree) and on
14
+ # OSes the variant does not carry.
15
+ # Dir-name candidates for a RUBY_PLATFORM string, in
16
+ # preference order (glibc before musl — dlopen falls through
17
+ # on musl hosts).
18
+ def self.vendor_platforms_for(ruby_platform)
19
+ case ruby_platform
20
+ when /arm64.*darwin|aarch64.*darwin/ then %w[arm64-darwin]
21
+ when /x86_64.*darwin/ then %w[x86_64-darwin]
22
+ when /aarch64.*linux/ then %w[aarch64-linux aarch64-linux-musl]
23
+ when /x86_64.*linux/ then %w[x86_64-linux x86_64-linux-musl]
24
+ else []
25
+ end
26
+ end
27
+
28
+ def self.vendor_dirs
29
+ vendor_platforms_for(RUBY_PLATFORM)
30
+ .map { |d| File.expand_path("../../leptris/vendor/#{d}", __dir__) }
31
+ .select { |dir| File.directory?(dir) }
32
+ end
33
+
34
+ # THE libleptris candidate list, in order — shared with the
35
+ # native read layer's resolve!. Single source of truth is
36
+ # load-bearing: two differently-pathed copies of the library
37
+ # load as two images with split per-document state.
38
+ def self.libleptris_candidates
39
+ [
40
+ ENV["LEPTRIS_LIB_PATH"],
41
+ *vendor_dirs.flat_map { |d|
42
+ %w[libleptris.dylib libleptris.so libleptris.dll]
43
+ .map { |n| File.join(d, n) }
44
+ },
45
+ File.expand_path("../../libleptris.dylib", __dir__),
46
+ File.expand_path("../../libleptris.so", __dir__),
47
+ File.expand_path("../../libleptris.dll", __dir__),
48
+ "/usr/local/lib/libleptris.dylib",
49
+ "/usr/local/lib/libleptris.so",
50
+ "leptris",
51
+ ].compact
52
+ end
53
+
10
54
  # libleptris links utf8proc via @rpath (TODO.restructure/20
11
55
  # — fn:normalize-unicode). dlopen the VENDORED utf8proc
12
56
  # FIRST: once its install name is loaded, dyld resolves
@@ -17,6 +61,10 @@ module Leptris
17
61
  # never consults it.
18
62
  begin
19
63
  ffi_lib [
64
+ *Leptris::XML::FFI.vendor_dirs.flat_map { |d|
65
+ %w[libutf8proc.3.dylib libutf8proc.so.3 libutf8proc.so
66
+ utf8proc.dll].map { |n| File.join(d, n) }
67
+ },
20
68
  File.expand_path("../../libutf8proc.3.dylib", __dir__),
21
69
  File.expand_path("../../libutf8proc.so.3", __dir__),
22
70
  File.expand_path("../../libutf8proc.so", __dir__),
@@ -29,15 +77,7 @@ module Leptris
29
77
  # Issue leptris-ruby#49: name the remedy when the library is
30
78
  # missing (ruby-platform gem without a vendored libleptris).
31
79
  begin
32
- ffi_lib [
33
- ENV["LEPTRIS_LIB_PATH"],
34
- File.expand_path("../../libleptris.dylib", __dir__),
35
- File.expand_path("../../libleptris.so", __dir__),
36
- File.expand_path("../../libleptris.dll", __dir__),
37
- "/usr/local/lib/libleptris.dylib",
38
- "/usr/local/lib/libleptris.so",
39
- "leptris",
40
- ].compact
80
+ ffi_lib Leptris::XML::FFI.libleptris_candidates
41
81
  rescue LoadError => e
42
82
  raise LoadError, <<~MSG
43
83
  leptris: cannot load the vendored libleptris library.
Binary file
@@ -29,29 +29,30 @@ rescue LoadError => e
29
29
  end
30
30
 
31
31
  module Leptris::XML::Native
32
- LIB_CANDIDATES = [
33
- ENV["LEPTRIS_LIB_PATH"],
34
- File.expand_path("../../libleptris.dylib", __dir__),
35
- File.expand_path("../../libleptris.so", __dir__),
36
- File.expand_path("../../libleptris.dll", __dir__),
37
- "libleptris",
38
- ].compact.freeze
32
+ # FFI.libleptris_candidates is the single source of truth — the
33
+ # native layer MUST dlsym the exact image the FFI layer loaded,
34
+ # or two library instances split per-document state.
35
+ def self.lib_candidates
36
+ Leptris::XML::FFI.libleptris_candidates
37
+ end
39
38
  end
40
39
 
41
- candidates = Leptris::XML::Native::LIB_CANDIDATES
40
+ # TODO.perf/01: accelerate the DEFAULT binding classes while the
41
+ # bundle is loaded (name/content/prefix/[] hot reads).
42
+ Leptris::XML::NATIVE_FAST = true
43
+
44
+ candidates = Leptris::XML::Native.lib_candidates
42
45
  resolved = candidates.any? do |path|
43
- resolved = Leptris::XML::Native::LIB_CANDIDATES.any? do |path|
44
- begin
45
- Leptris::XML::NativeNode.resolve!(path)
46
- true
47
- rescue RuntimeError
48
- false
49
- end
46
+ begin
47
+ Leptris::XML::NativeNode.resolve!(path)
48
+ true
49
+ rescue RuntimeError
50
+ false
50
51
  end
51
- raise LoadError,
52
- "leptris: native layer loaded but libleptris could not be " \
53
- "resolved from #{candidates.inspect}" unless resolved
54
52
  end
53
+ raise LoadError,
54
+ "leptris: native layer loaded but libleptris could not be " \
55
+ "resolved from #{candidates.inspect}" unless resolved
55
56
 
56
57
  class Leptris::XML::Document
57
58
  # Separate identity cache for NativeNodes so binding Node.wrap
data/lib/leptris.rb CHANGED
@@ -31,3 +31,18 @@ rescue LoadError => e
31
31
  (Underlying error: #{e.message})
32
32
  MSG
33
33
  end
34
+
35
+ # TODO.perf/02: auto-enable the native acceleration layer when the
36
+ # compiled bundle is present (platform gems). Quietly falls back
37
+ # to pure FFI when absent (ruby-variant on TruffleRuby/JRuby,
38
+ # custom builds). LEPTRIS_NO_NATIVE=1 forces the FFI path (the
39
+ # suite uses it to exercise both surfaces). The explicit
40
+ # `require "leptris/xml/native_layer"` remains the parallel-API
41
+ # entry for NativeNode builders.
42
+ unless ENV["LEPTRIS_NO_NATIVE"] == "1"
43
+ begin
44
+ require "leptris/xml/native_layer"
45
+ rescue LoadError
46
+ nil
47
+ end
48
+ end
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.162.7
4
+ version: 1.9.162.9
5
5
  platform: aarch64-linux
6
6
  authors:
7
7
  - Ribose Inc.
@@ -69,6 +69,12 @@ files:
69
69
  - LICENSE.md
70
70
  - README.adoc
71
71
  - Rakefile
72
+ - TODO.perf/01-ext-accelerated-default-reads.md
73
+ - TODO.perf/02-auto-enable-native-layer.md
74
+ - TODO.perf/03-bulk-xpath-result-materialization.md
75
+ - TODO.perf/04-ext-bound-serialization.md
76
+ - TODO.perf/05-native-surface-completion.md
77
+ - TODO.perf/06-perf-battery-native-legs.md
72
78
  - TODO.restructure/01-constraint-compliance-audit.md
73
79
  - TODO.restructure/02-deep-copy-seam.md
74
80
  - TODO.restructure/03-evaluation-context-seam.md