hypercast 0.0.2-aarch64-linux → 0.2.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/README.md +78 -11
- data/lib/hypercast/3.4/hypercast_native.so +0 -0
- data/lib/hypercast/4.0/hypercast_native.so +0 -0
- data/lib/hypercast/native/linux-arm64/libhypercast.so +0 -0
- data/lib/hypercast/native/linux-x64/libhypercast.so +0 -0
- data/lib/hypercast/native/osx-arm64/libhypercast.dylib +0 -0
- data/lib/hypercast/native/osx-x64/libhypercast.dylib +0 -0
- data/lib/hypercast/native/win-arm64/hypercast.dll +0 -0
- data/lib/hypercast/native/win-x64/hypercast.dll +0 -0
- data/lib/hypercast/runtime.rb +4 -2
- data/lib/hypercast.rb +49 -23
- metadata +8 -4
- data/lib/hypercast_native.so +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4413f5f3ec544107c724ad495a86ca78f0b5d88c06caacbece6c7e5c75b39746
|
|
4
|
+
data.tar.gz: d5e49cb6a3bacdbbeeabafe78954bea6de0ead9973fb22e945339a5c3f19ed97
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05ce5c94b6f3f1ccfd0e9d40bfcb6710acb0b5332141703519a8b74a244d273d9948cf8048873c80fcc475adfaa9c5c85c421559044fa653f6f4343440ea456b
|
|
7
|
+
data.tar.gz: fbec449055ddc9b1d0cee98b325e1287feedf493f4c69d3b76a3ba3c99e6588c38349b0da343ebd7561a20e2993d924e13a7b582aa0026a19d5ca93db7ac5a50
|
data/README.md
CHANGED
|
@@ -48,9 +48,17 @@ midnight, and durations come back as exact `Rational` seconds across the core's
|
|
|
48
48
|
duration door. The Fiddle fallback lands at ~3.5 µs: parity with `Time.iso8601`, sitting
|
|
49
49
|
on Fiddle's measured 1.6 µs per-call marshalling floor.
|
|
50
50
|
|
|
51
|
-
Separator detection is free here: `1.234.567,89` under `NumFormat::DETECT` runs
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
Separator detection is free here: `1.234.567,89` under `NumFormat::DETECT` runs at
|
|
52
|
+
591 ns against 570 ns for the same text under a declared eurozone format — inside the
|
|
53
|
+
error bars. Both of those were ~990 ns in 0.1.0, for a reason that had nothing to do
|
|
54
|
+
with parsing: every format other than `INVARIANT` paid three method dispatches and two
|
|
55
|
+
`String` allocations per call to read its separators back out of the `Data`. `DETECT`
|
|
56
|
+
is now identity-matched like `INVARIANT`, and any other format is resolved once per
|
|
57
|
+
thread and memoized by identity — anchored in a thread-variable so the memo's key can
|
|
58
|
+
never be a recycled address — which turned the per-call cost into one pointer compare.
|
|
59
|
+
The three reason Symbols and the option Symbols (`:seconds`, `:month_day_year`, …) are
|
|
60
|
+
cached the same way, so a fault or a declared option is a pointer compare too, never a
|
|
61
|
+
`Symbol#name` materialization.
|
|
54
62
|
|
|
55
63
|
**The honest trade-off, and Ruby's one real loss:** the civil date-time door is *slower*
|
|
56
64
|
than `strptime` — 1.30 µs against `DateTime.strptime`'s 1.02 µs, and the date door 1.04 µs
|
|
@@ -63,9 +71,38 @@ defaults to `+00:00`, which is an artifact of the type, not a zone the parse ass
|
|
|
63
71
|
|
|
64
72
|
On the Fiddle fallback the doors are parity-at-best — Fiddle's
|
|
65
73
|
per-call floor is the mechanism's price, kept because it's the universal zero-compile
|
|
66
|
-
path.
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
path. 0.2.0 still took ~20% off its numeric doors (`i32` 3.37 → 2.62 µs, `f64` 3.31 →
|
|
75
|
+
2.76 µs, same session) by not building things per call that never changed: the integer
|
|
76
|
+
doors interpolated and interned their `:cast_*` Symbol on every call, every door went
|
|
77
|
+
through a splat-and-resplat dispatcher, and every numeric call copied the format's 12
|
|
78
|
+
packed bytes into scratch — each format now owns one native pointer, memoized by identity,
|
|
79
|
+
passed straight through. (Benchmark forensics worth knowing: the doors read 4.3 µs until
|
|
80
|
+
per-call `Fiddle::Pointer.malloc` finalizers were hoisted to thread-local scratch —
|
|
81
|
+
receipts include their own archaeology.)
|
|
82
|
+
|
|
83
|
+
## Verifying provenance
|
|
84
|
+
|
|
85
|
+
Every gem RubyGems.org serves — the universal fallback and each of the six precompiled
|
|
86
|
+
platform gems — carries its own GitHub build-provenance attestation, signed directly by
|
|
87
|
+
this repo's own `release.yml` (the `rubygems-publish` job attests `ruby/pkg/*.gem` right
|
|
88
|
+
before the push), so plain `--repo` verifies any of them:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
gem fetch hypercast -v X.Y.Z --platform <platform> # or omit --platform for the universal gem
|
|
92
|
+
gh attestation verify hypercast-X.Y.Z-<platform>.gem --repo SkunkWerkx/HyperCast
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
That's the release's second layer of checking, not the only one: before any gem gets built,
|
|
96
|
+
the same job verifies every native binary it packs (six FFI libs, twelve Magnus extensions —
|
|
97
|
+
two ABIs per platform) against *their own* attestations — those are signed from
|
|
98
|
+
`SkunkWerkx/.github` by `hyper-build-native.yml`, so that check needs
|
|
99
|
+
`--signer-repo SkunkWerkx/.github` added — and refuses to proceed on an unverified one.
|
|
100
|
+
RubyGems.org has no unpublish and no duplicate-version overwrite, so this all happens while a
|
|
101
|
+
bad artifact is still reversible. The release run's job summary then re-fetches every gem
|
|
102
|
+
from the CDN and records attested-vs-served digests, turning "rubygems.org stores an upload
|
|
103
|
+
verbatim" into a per-release measurement rather than an assumption — see
|
|
104
|
+
[csharp/README.md's provenance section](../csharp/README.md#native-binary-provenance) for
|
|
105
|
+
more on why `--signer-repo` is needed for some artifacts here and not others.
|
|
69
106
|
|
|
70
107
|
## Install
|
|
71
108
|
|
|
@@ -73,11 +110,41 @@ include their own archaeology.)
|
|
|
73
110
|
gem install hypercast
|
|
74
111
|
```
|
|
75
112
|
|
|
76
|
-
|
|
77
|
-
platforms' natives bundled) plus
|
|
78
|
-
`aarch64-linux`, `x86_64-darwin`, `arm64-darwin
|
|
79
|
-
match. Nobody ever compiles anything
|
|
80
|
-
|
|
113
|
+
Seven gems are published per release: one universal `ruby`-platform gem (pure Fiddle, all
|
|
114
|
+
six platforms' natives bundled) plus six precompiled Magnus platform gems (`x86_64-linux`,
|
|
115
|
+
`aarch64-linux`, `x86_64-darwin`, `arm64-darwin`, `x64-mingw-ucrt`, `aarch64-mingw-ucrt`)
|
|
116
|
+
that `gem install` auto-selects when they match. Nobody ever compiles anything.
|
|
117
|
+
|
|
118
|
+
Selection has **two** axes here, unlike every other binding in this repo. A Magnus extension
|
|
119
|
+
is bound to one Ruby minor ABI — there is no `abi3` equivalent to collapse the version axis
|
|
120
|
+
the way [the Python binding's](../python/) wheels do — so each platform gem is a "fat" gem
|
|
121
|
+
carrying one compiled extension per supported Ruby, under `lib/hypercast/<minor>/`, and picks
|
|
122
|
+
one at `require` time:
|
|
123
|
+
|
|
124
|
+
| Ruby | the six platforms above | anywhere else (musl/Alpine, …) |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| 4.0 (primary) | Magnus, `backend: :native` | Fiddle |
|
|
127
|
+
| 3.4 (floor, until its EOL 2028-03-31) | Magnus, `backend: :native` | Fiddle |
|
|
128
|
+
| 3.2 / 3.3 | Fiddle | Fiddle |
|
|
129
|
+
|
|
130
|
+
The platform gems declare `required_ruby_version >= 3.4, < 4.1` precisely so RubyGems
|
|
131
|
+
*declines* them outside that range and resolves the universal gem instead — a wrong-ABI
|
|
132
|
+
extension must never be installed in the first place. On Windows it would at least fail to
|
|
133
|
+
load cleanly (the extension imports `<arch>-ucrt-ruby<minor>.dll` by name —
|
|
134
|
+
`x64-ucrt-ruby400.dll` on x64, `aarch64-ucrt-ruby400.dll` on ARM), but Linux extensions don't
|
|
135
|
+
link libruby at all, so one can load successfully against the wrong ABI and misbehave later.
|
|
136
|
+
Every cell in that table replays the same corpus green.
|
|
137
|
+
|
|
138
|
+
Both Windows architectures get a Magnus gem, and the reasoning that once kept them on Fiddle
|
|
139
|
+
was backwards: MinGW is the *only* Windows flavour `rb-sys` targets (`x64-mingw-ucrt` and
|
|
140
|
+
`aarch64-mingw-ucrt`, both `supported: true` in its own `data/toolchains.json`); the one it
|
|
141
|
+
has no support for is MSVC. Windows is also where the Fiddle fallback cost the most, so these
|
|
142
|
+
are the most worthwhile gems in the set. Both extensions are built for the `gnullvm` Rust
|
|
143
|
+
targets rather than `gnu` — the same mingw-w64/UCRT ABI RubyInstaller's Ruby uses, linked
|
|
144
|
+
with LLVM and compiler-rt instead of GCC and a statically-linked libgcc, which is what keeps
|
|
145
|
+
the shipped extension small. The build script, and the two flags that are load-bearing on
|
|
146
|
+
the ARM leg (a static libunwind, and a clang-spelled `--target` for bindgen), live in the
|
|
147
|
+
forge's `hyper-build-native.yml`, shared with every other Hyper* repo.
|
|
81
148
|
|
|
82
149
|
See [the repo root README](../README.md) for the full door table, the receipts, and the
|
|
83
150
|
state of every other language binding.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/hypercast/runtime.rb
CHANGED
|
@@ -29,8 +29,10 @@ module HyperCast
|
|
|
29
29
|
@functions = nil
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
# The door's Fiddle::Function, for the caller to invoke directly — a splat-through
|
|
33
|
+
# `call(symbol, *args)` here built and re-splatted an Array on every cast.
|
|
34
|
+
def function(symbol)
|
|
35
|
+
functions.fetch(symbol)
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
private
|
data/lib/hypercast.rb
CHANGED
|
@@ -23,7 +23,7 @@ require_relative "hypercast/runtime"
|
|
|
23
23
|
module HyperCast
|
|
24
24
|
# This gem's own version — kept in lockstep with hypercast.gemspec by the
|
|
25
25
|
# prepare-release workflow, so the two can never drift apart again.
|
|
26
|
-
VERSION = "0.0
|
|
26
|
+
VERSION = "0.2.0"
|
|
27
27
|
|
|
28
28
|
# The success case of a verdict: a cast value.
|
|
29
29
|
Success = Data.define(:value)
|
|
@@ -120,11 +120,14 @@ module HyperCast
|
|
|
120
120
|
.each do |door, unpack|
|
|
121
121
|
sizes = { "c" => 1, "C" => 1, "s<" => 2, "S<" => 2, "l<" => 4, "L<" => 4, "q<" => 8, "Q<" => 8 }
|
|
122
122
|
size = sizes.fetch(unpack)
|
|
123
|
+
# Resolved once here, not inside the method: interpolating a Symbol per call built a
|
|
124
|
+
# String and interned it on every integer cast.
|
|
125
|
+
symbol = :"cast_#{door}"
|
|
123
126
|
# Integer doors: the target type's own range, declared grouping, accounting parens,
|
|
124
127
|
# non-negative exponent, and 0x/&H/0b two's-complement radix prefixes. Ruby Integer
|
|
125
128
|
# is unbounded, so u64 comes back as the true unsigned value.
|
|
126
129
|
define_method(door) do |text, format|
|
|
127
|
-
numeric(
|
|
130
|
+
numeric(symbol, text, format, size) { |out| out.unpack1(unpack) }
|
|
128
131
|
end
|
|
129
132
|
end
|
|
130
133
|
|
|
@@ -161,7 +164,7 @@ module HyperCast
|
|
|
161
164
|
code = UNIX_PRECISIONS.fetch(precision)
|
|
162
165
|
bytes = utf8(text)
|
|
163
166
|
out, fault, = scratch
|
|
164
|
-
rc = Runtime.
|
|
167
|
+
rc = Runtime.function(:cast_unix).call(input_ptr(bytes), bytes.bytesize, code, out, fault)
|
|
165
168
|
verdict(rc, fault) { instant(out[0, 16]) }
|
|
166
169
|
end
|
|
167
170
|
|
|
@@ -179,7 +182,7 @@ module HyperCast
|
|
|
179
182
|
code = EXCEL_EPOCHS.fetch(epoch)
|
|
180
183
|
bytes = utf8(text)
|
|
181
184
|
out, fault, = scratch
|
|
182
|
-
rc = Runtime.
|
|
185
|
+
rc = Runtime.function(:cast_excel_serial).call(input_ptr(bytes), bytes.bytesize, code, out, fault)
|
|
183
186
|
verdict(rc, fault) { instant(out[0, 16]) }
|
|
184
187
|
end
|
|
185
188
|
|
|
@@ -198,7 +201,7 @@ module HyperCast
|
|
|
198
201
|
code = DATE_ORDERS.fetch(order)
|
|
199
202
|
bytes = utf8(text)
|
|
200
203
|
out, fault, = scratch
|
|
201
|
-
rc = Runtime.
|
|
204
|
+
rc = Runtime.function(:cast_date_ordered).call(input_ptr(bytes), bytes.bytesize, code, out, fault)
|
|
202
205
|
verdict(rc, fault) do
|
|
203
206
|
year, month, day = out[0, 4].unpack("S<CC")
|
|
204
207
|
Date.new(year, month, day)
|
|
@@ -218,7 +221,7 @@ module HyperCast
|
|
|
218
221
|
code = DATE_ORDERS.fetch(order)
|
|
219
222
|
bytes = utf8(text)
|
|
220
223
|
out, fault, = scratch
|
|
221
|
-
rc = Runtime.
|
|
224
|
+
rc = Runtime.function(:cast_datetime).call(input_ptr(bytes), bytes.bytesize, code, out, fault)
|
|
222
225
|
verdict(rc, fault) do
|
|
223
226
|
year, month, day, nanos = out[0, 16].unpack("S<CCx4Q<")
|
|
224
227
|
second_of_day, frac = nanos.divmod(1_000_000_000)
|
|
@@ -261,14 +264,16 @@ module HyperCast
|
|
|
261
264
|
bytes.empty? ? nil : bytes
|
|
262
265
|
end
|
|
263
266
|
|
|
264
|
-
# One
|
|
265
|
-
# (16 bytes covers every door), fault span at 16
|
|
266
|
-
#
|
|
267
|
-
#
|
|
267
|
+
# One 24-byte scratch allocation per thread, reused by every call: out-value at 0
|
|
268
|
+
# (16 bytes covers every door), fault span at 16. Two Fiddle::Pointer.malloc(...,
|
|
269
|
+
# RUBY_FREE) calls per cast — each registering a GC finalizer — measured as the
|
|
270
|
+
# dominant per-call cost by an order of magnitude. The NumFormat no longer lives here:
|
|
271
|
+
# each format owns its own packed pointer (see packed_cache), so a numeric call copies
|
|
272
|
+
# nothing into scratch before crossing.
|
|
268
273
|
def scratch
|
|
269
274
|
Thread.current[:hypercast_scratch] ||= begin
|
|
270
|
-
base = Fiddle::Pointer.malloc(
|
|
271
|
-
[base, base + 16
|
|
275
|
+
base = Fiddle::Pointer.malloc(24, Fiddle::RUBY_FREE)
|
|
276
|
+
[base, base + 16]
|
|
272
277
|
end
|
|
273
278
|
end
|
|
274
279
|
|
|
@@ -288,24 +293,31 @@ module HyperCast
|
|
|
288
293
|
# The shared body of every format-free door: one native call over the scratch buffers.
|
|
289
294
|
def plain(symbol, text, out_size)
|
|
290
295
|
bytes = utf8(text)
|
|
291
|
-
out, fault
|
|
292
|
-
rc = Runtime.call(
|
|
296
|
+
out, fault = scratch
|
|
297
|
+
rc = Runtime.function(symbol).call(input_ptr(bytes), bytes.bytesize, out, fault)
|
|
293
298
|
verdict(rc, fault) { yield(out[0, out_size]) }
|
|
294
299
|
end
|
|
295
300
|
|
|
296
|
-
# The shared body of the integer/real doors: plain, plus the packed
|
|
301
|
+
# The shared body of the integer/real doors: plain, plus the format's own packed
|
|
302
|
+
# pointer, passed straight through — no per-call copy of the 12 bytes into scratch.
|
|
297
303
|
def numeric(symbol, text, format, out_size)
|
|
298
304
|
bytes = utf8(text)
|
|
299
|
-
out, fault
|
|
300
|
-
|
|
301
|
-
rc = Runtime.call(symbol, input_ptr(bytes), bytes.bytesize, raw_format, out, fault)
|
|
305
|
+
out, fault = scratch
|
|
306
|
+
rc = Runtime.function(symbol).call(input_ptr(bytes), bytes.bytesize, packed_cache[format], out, fault)
|
|
302
307
|
verdict(rc, fault) { yield(out[0, out_size]) }
|
|
303
308
|
end
|
|
304
309
|
|
|
305
|
-
# Identity-keyed memo
|
|
306
|
-
#
|
|
310
|
+
# Identity-keyed memo (compare_by_identity — a pointer hash, not Data's structural
|
|
311
|
+
# #hash over two Strings and an Integer) of a native 12-byte RawNumFormat per format
|
|
312
|
+
# object, filled once from NumFormat#packed. Formats are reused constants in practice,
|
|
313
|
+
# so the common call finds its pointer in one lookup and packs nothing. The Hash holds
|
|
314
|
+
# the format, so a key can never be a recycled address; the race is benign (idempotent).
|
|
307
315
|
def packed_cache
|
|
308
|
-
@packed_cache ||= Hash.new
|
|
316
|
+
@packed_cache ||= Hash.new do |cache, format|
|
|
317
|
+
pointer = Fiddle::Pointer.malloc(12, Fiddle::RUBY_FREE)
|
|
318
|
+
pointer[0, 12] = format.packed
|
|
319
|
+
cache[format] = pointer
|
|
320
|
+
end.compare_by_identity
|
|
309
321
|
end
|
|
310
322
|
|
|
311
323
|
# Builds a UTC Time from the core's protobuf-shaped {seconds, nanos} pair, exactly.
|
|
@@ -325,10 +337,24 @@ HyperCast::BACKEND =
|
|
|
325
337
|
if ENV["HYPERCAST_PURE"]
|
|
326
338
|
:fiddle
|
|
327
339
|
else
|
|
340
|
+
# Two layouts, and both have to work. A released platform gem is a "fat" gem carrying one
|
|
341
|
+
# extension per supported Ruby ABI under lib/hypercast/<minor>/ (see the Rakefile's
|
|
342
|
+
# native:gem task for why an ABI-per-file is unavoidable — Magnus has no `abi3`
|
|
343
|
+
# equivalent). CI's in-job staging and a local `cargo build --release --features ruby`
|
|
344
|
+
# instead drop a single extension flat at lib/. Trying the versioned path first and the
|
|
345
|
+
# flat one second means neither has to know the other exists.
|
|
346
|
+
#
|
|
347
|
+
# A miss on both is not an error: it means this Ruby/platform combination has no
|
|
348
|
+
# precompiled extension, which is precisely what the Fiddle backend is for.
|
|
328
349
|
begin
|
|
329
|
-
require "hypercast_native"
|
|
350
|
+
require "hypercast/#{RUBY_VERSION[/\d+\.\d+/]}/hypercast_native"
|
|
330
351
|
:native
|
|
331
352
|
rescue LoadError
|
|
332
|
-
|
|
353
|
+
begin
|
|
354
|
+
require "hypercast_native"
|
|
355
|
+
:native
|
|
356
|
+
rescue LoadError
|
|
357
|
+
:fiddle
|
|
358
|
+
end
|
|
333
359
|
end
|
|
334
360
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hypercast
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Buvinghausen
|
|
@@ -64,6 +64,8 @@ files:
|
|
|
64
64
|
- LICENSE
|
|
65
65
|
- README.md
|
|
66
66
|
- lib/hypercast.rb
|
|
67
|
+
- lib/hypercast/3.4/hypercast_native.so
|
|
68
|
+
- lib/hypercast/4.0/hypercast_native.so
|
|
67
69
|
- lib/hypercast/native/linux-arm64/libhypercast.so
|
|
68
70
|
- lib/hypercast/native/linux-x64/libhypercast.so
|
|
69
71
|
- lib/hypercast/native/osx-arm64/libhypercast.dylib
|
|
@@ -72,7 +74,6 @@ files:
|
|
|
72
74
|
- lib/hypercast/native/win-x64/hypercast.dll
|
|
73
75
|
- lib/hypercast/native_platform.rb
|
|
74
76
|
- lib/hypercast/runtime.rb
|
|
75
|
-
- lib/hypercast_native.so
|
|
76
77
|
homepage: https://github.com/SkunkWerkx/HyperCast
|
|
77
78
|
licenses:
|
|
78
79
|
- MIT
|
|
@@ -85,14 +86,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
85
86
|
requirements:
|
|
86
87
|
- - ">="
|
|
87
88
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '3.
|
|
89
|
+
version: '3.4'
|
|
90
|
+
- - "<"
|
|
91
|
+
- !ruby/object:Gem::Version
|
|
92
|
+
version: '4.1'
|
|
89
93
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
90
94
|
requirements:
|
|
91
95
|
- - ">="
|
|
92
96
|
- !ruby/object:Gem::Version
|
|
93
97
|
version: '0'
|
|
94
98
|
requirements: []
|
|
95
|
-
rubygems_version:
|
|
99
|
+
rubygems_version: 4.0.16
|
|
96
100
|
specification_version: 4
|
|
97
101
|
summary: Allocation-free scalar parsing as Success/Fault verdicts over a native Rust
|
|
98
102
|
core
|
data/lib/hypercast_native.so
DELETED
|
Binary file
|