hyperuuid 0.2.0-aarch64-linux → 0.3.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 +76 -2
- data/lib/hyperuuid/3.4/hyperuuid_native.so +0 -0
- data/lib/hyperuuid/4.0/hyperuuid_native.so +0 -0
- data/lib/hyperuuid/native/README.md +4 -1
- data/lib/hyperuuid/native/linux-arm64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/linux-x64/libhyperuuid.so +0 -0
- data/lib/hyperuuid/native/osx-arm64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/osx-x64/libhyperuuid.dylib +0 -0
- data/lib/hyperuuid/native/wasm32-wasip1/hyperuuid.wasm +0 -0
- data/lib/hyperuuid/native/win-arm64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/native/win-x64/hyperuuid.dll +0 -0
- data/lib/hyperuuid/runtime.rb +10 -0
- data/lib/hyperuuid/wasm_runtime.rb +241 -0
- data/lib/hyperuuid.rb +33 -3
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f236489589d106e3922b7ea6afc388da14ee34b7eeae2398d13a26e6de842ba8
|
|
4
|
+
data.tar.gz: ebadf7adf1ff518f468fcb00d28abe9004e26e29013c9f393063752ea07fdd31
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c83e2025e8f493d5762607b0ef5acc1238252dd7b7998a9afe4462f4a2bb382af52298713abce4b79830e274b6da317e51ff14db21146d6ae7f8cfc1e1e74d72
|
|
7
|
+
data.tar.gz: b57eca92e941b4ec157457d5610d3db7ed36af4057f0f91e053c461681f36228714344e8a177985659ed5af0545a7f3f93a7de6eb91bcc743e2a4c5d859da8b8
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
**Ruby's own stdlib stops at `SecureRandom.uuid` — random v4, full stop. No v5, no v6, no v7. This gem is the whole RFC, with zero gem dependency beyond `Fiddle` (which ships with every Ruby install) — and it's faster than `SecureRandom.uuid` too.**
|
|
7
7
|
|
|
8
8
|
RFC 9562 UUID v4 (random), v5 (deterministic), v6 and v7 (time-sortable) generation, with
|
|
9
|
-
|
|
9
|
+
three backends sharing one public surface. The fast path is a native extension built with
|
|
10
10
|
[Magnus](https://github.com/matsadler/magnus) — the Rust core linked directly into the Ruby
|
|
11
11
|
VM, auto-selected when loadable — which redefines the low-level `Runtime` methods in place
|
|
12
12
|
on require; everything above them (`Uuid`, the module doors, batch slicing) is shared
|
|
@@ -15,7 +15,10 @@ shared library via [`Fiddle`](https://docs.ruby-lang.org/en/master/Fiddle.html)
|
|
|
15
15
|
dlopen/dlsym plus a raw C-ABI call, no runtime bridge, nothing to compile on
|
|
16
16
|
`bundle install`. Set `HYPERUUID_PURE=1` to force the Fiddle backend;
|
|
17
17
|
`HyperUuid::BACKEND` reports which one is live. Bundles a native build for every supported
|
|
18
|
-
platform (linux/darwin/windows × x64/arm64) and picks the right one at runtime.
|
|
18
|
+
platform (linux/darwin/windows × x64/arm64) and picks the right one at runtime. A third
|
|
19
|
+
backend runs the same core as a WebAssembly module inside the
|
|
20
|
+
[`wasmtime`](https://rubygems.org/gems/wasmtime) gem, for any platform with no native build
|
|
21
|
+
at all — see [WebAssembly (wasmtime)](#webassembly-wasmtime).
|
|
19
22
|
|
|
20
23
|
```ruby
|
|
21
24
|
require "hyperuuid"
|
|
@@ -109,6 +112,77 @@ Batch generation still amortizes per-call cost on both backends — one native c
|
|
|
109
112
|
|
|
110
113
|
The batch multiplier shrank from 11x to ~3.8x for the best reason available: the individual calls got 3x faster, so there's less waste left to amortize. If you need v5/v6/v7, need many at once, or need this Ruby service's IDs to agree byte-for-byte with a Go or Python service's, that's what this gem is for — and now it's the fast option too, not just the capable one.
|
|
111
114
|
|
|
115
|
+
## WebAssembly (wasmtime)
|
|
116
|
+
|
|
117
|
+
The Rust core also ships inside this gem as a `wasm32-wasip1` module
|
|
118
|
+
(`lib/hyperuuid/native/wasm32-wasip1/hyperuuid.wasm`), and the
|
|
119
|
+
[`wasmtime`](https://rubygems.org/gems/wasmtime) gem can run it in-process. This is the
|
|
120
|
+
inverse of ruby.wasm — not Ruby inside a wasm sandbox, but a wasm module inside Ruby — and
|
|
121
|
+
it is the one backend that needs no shared library for the platform it runs on: no
|
|
122
|
+
`dlopen`, no Magnus extension, nothing compiled against this Ruby's ABI. Everything above
|
|
123
|
+
`Runtime` (`Uuid`, the module doors, batch slicing) is the same code the other two backends
|
|
124
|
+
run, and `spec/wasm_backend_spec.rb` pins that the outputs agree with the Fiddle backend
|
|
125
|
+
byte for byte.
|
|
126
|
+
|
|
127
|
+
`wasmtime` is deliberately **not** a dependency of this gem; a consumer who wants this path
|
|
128
|
+
installs it:
|
|
129
|
+
|
|
130
|
+
```sh
|
|
131
|
+
gem install wasmtime
|
|
132
|
+
HYPERUUID_WASM=1 ruby -rhyperuuid -e 'p HyperUuid::BACKEND' # => :wasm
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`HYPERUUID_WASM=1` forces the backend (and raises a `LoadError` naming the gem if it is
|
|
136
|
+
missing). Without it, the wasm backend is only ever chosen automatically when there is no
|
|
137
|
+
native library for this platform at all — no Magnus extension and no `libhyperuuid` for the
|
|
138
|
+
RID — and `wasmtime` happens to be installed. No supported platform's behavior changes just
|
|
139
|
+
because this backend exists.
|
|
140
|
+
|
|
141
|
+
Two things are different under the sandbox, both by necessity. A wasm guest only sees its own
|
|
142
|
+
linear memory, so every buffer the core fills comes from the module's own exported `malloc`
|
|
143
|
+
(the same wasi-libc allocator Rust's std uses on that target) and is read back with
|
|
144
|
+
`Memory#read` — using the guest's allocator rather than a host-picked offset is what keeps a
|
|
145
|
+
batch from being clobbered by the guest's next allocation. And a `Wasmtime::Store` is
|
|
146
|
+
single-threaded, so every call is serialized under one Mutex around one shared instance,
|
|
147
|
+
which is also what keeps the core's v7 counter (it lives inside the instance) monotonic
|
|
148
|
+
across threads and batches, exactly as the one dlopen'd library does natively.
|
|
149
|
+
|
|
150
|
+
Measured, same box as the benchmarks above (Ruby 4.0.6, linux-arm64, wasmtime 47.0.3):
|
|
151
|
+
|
|
152
|
+
| Call | wasmtime | native (Magnus) |
|
|
153
|
+
|---|---:|---:|
|
|
154
|
+
| `uuid_new_v7`, single, per call | 867 ns | ~450 ns |
|
|
155
|
+
| `uuid_new_v7_batch(1000)`, per call | 40.6 µs (50.6 µs with the 16 KB read back into Ruby) | 24 µs |
|
|
156
|
+
|
|
157
|
+
So roughly 2x the native cost per call, and the batch doors amortize it the same way they do
|
|
158
|
+
for Fiddle. The guest's own work is not where the time goes — the identical module runs at
|
|
159
|
+
14 µs per thousand under a JIT-compiled host — it is the crossing, and Ruby's is one of the
|
|
160
|
+
cheaper ones.
|
|
161
|
+
|
|
162
|
+
## Verifying provenance
|
|
163
|
+
|
|
164
|
+
Every gem RubyGems.org serves — the universal fallback and each of the six precompiled
|
|
165
|
+
platform gems — carries its own GitHub build-provenance attestation, signed directly by
|
|
166
|
+
this repo's own `release.yml` (the `rubygems-publish` job attests `ruby/pkg/*.gem` right
|
|
167
|
+
before the push), so plain `--repo` verifies any of them:
|
|
168
|
+
|
|
169
|
+
```sh
|
|
170
|
+
gem fetch hyperuuid -v X.Y.Z --platform <platform> # or omit --platform for the universal gem
|
|
171
|
+
gh attestation verify hyperuuid-X.Y.Z-<platform>.gem --repo SkunkWerkx/HyperUuid
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
That's the release's second layer of checking, not the only one: before any gem gets built,
|
|
175
|
+
the same job verifies all ten native binaries it packs (six FFI libs, four Magnus
|
|
176
|
+
extensions) against *their own* attestations — those are signed from `SkunkWerkx/.github`
|
|
177
|
+
by `hyper-build-native.yml`, so that check needs `--signer-repo SkunkWerkx/.github` added —
|
|
178
|
+
and refuses to proceed on an unverified one. RubyGems.org has no unpublish and no
|
|
179
|
+
duplicate-version overwrite, so this all happens while a bad artifact is still reversible.
|
|
180
|
+
The release run's job summary then re-fetches every gem from the CDN and records
|
|
181
|
+
attested-vs-served digests, turning "rubygems.org stores an upload verbatim" into a
|
|
182
|
+
per-release measurement rather than an assumption — see
|
|
183
|
+
[csharp/README.md's provenance section](../csharp/README.md#native-binary-provenance) for
|
|
184
|
+
more on why `--signer-repo` is needed for some artifacts here and not others.
|
|
185
|
+
|
|
112
186
|
## Install
|
|
113
187
|
|
|
114
188
|
```sh
|
|
Binary file
|
|
Binary file
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# native/
|
|
2
2
|
|
|
3
3
|
Populated per-RID with the platform's native `libhyperuuid` build (`native/{rid}/{lib}`) by CI
|
|
4
|
-
and by `cargo build --release` for local dev — see `../../../.gitignore
|
|
4
|
+
and by `cargo build --release` for local dev — see `../../../.gitignore` — plus
|
|
5
|
+
`wasm32-wasip1/hyperuuid.wasm`, the same core as a WebAssembly module for the `wasmtime`
|
|
6
|
+
backend (`../wasm_runtime.rb`), staged the same way from
|
|
7
|
+
`cargo build --release --target wasm32-wasip1` run inside `rust/`. This file exists so
|
|
5
8
|
the directory has at least one tracked file on a fresh checkout, matching the Go/Swift
|
|
6
9
|
bindings' `native/`/`NativeLibs/` placeholder convention.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/hyperuuid/runtime.rb
CHANGED
|
@@ -104,6 +104,16 @@ module HyperUuid
|
|
|
104
104
|
rewrite(:v6_to_rfc_order, bytes)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
|
+
# Whether this platform has a shared library for Fiddle to dlopen at all: a known RID
|
|
108
|
+
# and the file actually present in this install. Backend selection (hyperuuid.rb)
|
|
109
|
+
# asks this before falling back to the WebAssembly backend, which needs neither.
|
|
110
|
+
def fiddle_library_available?
|
|
111
|
+
rid, lib_name = NativePlatform.rid_and_library_name
|
|
112
|
+
File.exist?(File.join(NATIVE_DIR, rid, lib_name))
|
|
113
|
+
rescue NativePlatform::UnsupportedPlatformError
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
|
|
107
117
|
private
|
|
108
118
|
|
|
109
119
|
# One 16-byte scratch allocation per thread, reused by every single-item call —
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
require "wasmtime"
|
|
2
|
+
|
|
3
|
+
module HyperUuid
|
|
4
|
+
# The WebAssembly backend: the same Rust core, compiled once to a `wasm32-wasip1` module
|
|
5
|
+
# (lib/hyperuuid/native/wasm32-wasip1/hyperuuid.wasm) and run inside this process by the
|
|
6
|
+
# `wasmtime` gem — no per-platform shared library needed, and nothing dlopen'd. This is the
|
|
7
|
+
# inverse of ruby.wasm: not Ruby running inside a wasm sandbox, but a wasm module running
|
|
8
|
+
# inside Ruby.
|
|
9
|
+
#
|
|
10
|
+
# Same integration shape as the Magnus extension: on require (after runtime.rb has defined
|
|
11
|
+
# the Fiddle backend) this redefines the `HyperUuid::Runtime` singleton methods **in place**
|
|
12
|
+
# — no delegation layer, no second surface. Everything above Runtime (the `Uuid` class, the
|
|
13
|
+
# module doors, batch slicing) stays shared byte-for-byte across all three backends, and the
|
|
14
|
+
# same `Runtime::RandomSourceError` / `Runtime::TimestampOutOfRangeError` classes carry the
|
|
15
|
+
# same messages.
|
|
16
|
+
#
|
|
17
|
+
# Two things differ from the native backends, both forced by the sandbox:
|
|
18
|
+
#
|
|
19
|
+
# * A wasm guest only sees its own linear memory, so no Fiddle::Pointer can cross. Every
|
|
20
|
+
# buffer the core writes into is obtained from the module's own exported `malloc` (the
|
|
21
|
+
# wasi-libc allocator Rust's std already uses on this target) and read back with
|
|
22
|
+
# `Memory#read`. Using the guest's allocator rather than a host-picked offset is
|
|
23
|
+
# load-bearing: dlmalloc claims the tail of the initial memory on first use, and a batch
|
|
24
|
+
# written at a guessed offset was observed corrupted by the guest's next allocation.
|
|
25
|
+
# * A `Wasmtime::Store` is single-threaded, so every call is serialized under one Mutex.
|
|
26
|
+
# One shared instance per process is also what keeps the core's v7 counter — which lives
|
|
27
|
+
# inside the instance — monotonic across threads and batches, exactly as the one dlopen'd
|
|
28
|
+
# library does natively.
|
|
29
|
+
module Runtime
|
|
30
|
+
WASM_MODULE_PATH = File.join(NATIVE_DIR, "wasm32-wasip1", "hyperuuid.wasm")
|
|
31
|
+
|
|
32
|
+
# One instantiated module: the exported functions, the exported memory, and the two
|
|
33
|
+
# 16-byte scratch buffers (one in, one out) the single-item doors reuse for the life of
|
|
34
|
+
# the process. Batches and v5 names get a `malloc`/`free` pair per call instead — one
|
|
35
|
+
# allocation amortized over `count` IDs, the same shape as the Fiddle backend's per-batch
|
|
36
|
+
# Pointer.malloc.
|
|
37
|
+
class WasmInstance
|
|
38
|
+
attr_reader :memory, :in, :out
|
|
39
|
+
|
|
40
|
+
EXPORTS = %i[
|
|
41
|
+
malloc free
|
|
42
|
+
uuid_new_v4 uuid_new_v5 uuid_new_v6 uuid_v6_unix_millis uuid_new_v6_batch
|
|
43
|
+
uuid_new_v7 uuid_v7_unix_millis uuid_new_v7_batch
|
|
44
|
+
uuid_v7_to_sql_order uuid_v7_to_rfc_order uuid_v6_to_sql_order uuid_v6_to_rfc_order
|
|
45
|
+
].freeze
|
|
46
|
+
|
|
47
|
+
def initialize(path)
|
|
48
|
+
unless File.exist?(path)
|
|
49
|
+
raise LoadError,
|
|
50
|
+
"hyperuuid: #{path} not found (this gem was built without its WebAssembly module)"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
engine = Wasmtime::Engine.new
|
|
54
|
+
mod = Wasmtime::Module.from_file(engine, path)
|
|
55
|
+
linker = Wasmtime::Linker.new(engine)
|
|
56
|
+
# The module imports five WASI preview1 functions (random_get is the one that
|
|
57
|
+
# matters: it is the core's only entropy source; the rest are wasi-libc's startup
|
|
58
|
+
# plumbing). A default WasiConfig — no stdio, no filesystem, no environment — is all
|
|
59
|
+
# any of them need.
|
|
60
|
+
Wasmtime::WASI::P1.add_to_linker_sync(linker)
|
|
61
|
+
store = Wasmtime::Store.new(engine, wasi_p1_config: Wasmtime::WasiConfig.new)
|
|
62
|
+
instance = linker.instantiate(store, mod)
|
|
63
|
+
|
|
64
|
+
@memory = instance.export("memory").to_memory
|
|
65
|
+
@fn = EXPORTS.to_h { |name| [name, instance.export(name.to_s).to_func] }
|
|
66
|
+
@in = malloc(16)
|
|
67
|
+
@out = malloc(16)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fn(name)
|
|
71
|
+
@fn.fetch(name)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def malloc(size)
|
|
75
|
+
ptr = @fn[:malloc].call(size)
|
|
76
|
+
raise NoMemoryError, "hyperuuid: wasm malloc(#{size}) failed" if ptr.zero?
|
|
77
|
+
ptr
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def free(ptr)
|
|
81
|
+
@fn[:free].call(ptr)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Runs `block` with a `size`-byte guest buffer, freeing it afterwards even on error.
|
|
85
|
+
def with_buffer(size)
|
|
86
|
+
ptr = malloc(size)
|
|
87
|
+
begin
|
|
88
|
+
yield ptr
|
|
89
|
+
ensure
|
|
90
|
+
free(ptr)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@wasm_mutex = Mutex.new
|
|
96
|
+
@wasm = nil
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
def new_v4
|
|
100
|
+
wasm do |w|
|
|
101
|
+
rc = w.fn(:uuid_new_v4).call(w.out)
|
|
102
|
+
raise RandomSourceError, "uuid_new_v4 failed with code #{rc}" unless rc.zero?
|
|
103
|
+
w.memory.read(w.out, 16)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def new_v5(namespace_bytes, name_bytes)
|
|
108
|
+
wasm do |w|
|
|
109
|
+
w.memory.write(w.in, namespace_bytes)
|
|
110
|
+
# A zero-length name still needs a valid (non-null) pointer inside the guest —
|
|
111
|
+
# the `in` scratch, already holding the namespace, serves; nothing reads past
|
|
112
|
+
# a zero length.
|
|
113
|
+
rc =
|
|
114
|
+
if name_bytes.empty?
|
|
115
|
+
w.fn(:uuid_new_v5).call(w.in, w.in, 0, w.out)
|
|
116
|
+
else
|
|
117
|
+
w.with_buffer(name_bytes.bytesize) do |name|
|
|
118
|
+
w.memory.write(name, name_bytes)
|
|
119
|
+
w.fn(:uuid_new_v5).call(w.in, name, name_bytes.bytesize, w.out)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
raise RandomSourceError, "uuid_new_v5 failed with code #{rc}" unless rc.zero?
|
|
123
|
+
w.memory.read(w.out, 16)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def new_v6(unix_millis)
|
|
128
|
+
wasm do |w|
|
|
129
|
+
rc = w.fn(:uuid_new_v6).call(i64(unix_millis), w.out)
|
|
130
|
+
case rc
|
|
131
|
+
when 0 then w.memory.read(w.out, 16)
|
|
132
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis does not fit the 60-bit v6 timestamp field"
|
|
133
|
+
else raise RandomSourceError, "uuid_new_v6 failed with code #{rc}"
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def v6_unix_millis(bytes)
|
|
139
|
+
wasm do |w|
|
|
140
|
+
w.memory.write(w.in, bytes)
|
|
141
|
+
u64(w.fn(:uuid_v6_unix_millis).call(w.in))
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def new_v6_batch(count, unix_millis)
|
|
146
|
+
return "" if count.zero?
|
|
147
|
+
wasm do |w|
|
|
148
|
+
w.with_buffer(count * 16) do |out|
|
|
149
|
+
rc = w.fn(:uuid_new_v6_batch).call(i64(unix_millis), count, out)
|
|
150
|
+
case rc
|
|
151
|
+
when 0 then w.memory.read(out, count * 16)
|
|
152
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis does not fit the 60-bit v6 timestamp field"
|
|
153
|
+
else raise RandomSourceError, "uuid_new_v6_batch failed with code #{rc}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def new_v7(unix_millis)
|
|
160
|
+
wasm do |w|
|
|
161
|
+
rc = w.fn(:uuid_new_v7).call(i64(unix_millis), w.out)
|
|
162
|
+
case rc
|
|
163
|
+
when 0 then w.memory.read(w.out, 16)
|
|
164
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis must fit within the RFC 9562 48-bit field"
|
|
165
|
+
else raise RandomSourceError, "uuid_new_v7 failed with code #{rc}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def v7_unix_millis(bytes)
|
|
171
|
+
wasm do |w|
|
|
172
|
+
w.memory.write(w.in, bytes)
|
|
173
|
+
u64(w.fn(:uuid_v7_unix_millis).call(w.in))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def new_v7_batch(count, unix_millis)
|
|
178
|
+
return "" if count.zero?
|
|
179
|
+
wasm do |w|
|
|
180
|
+
w.with_buffer(count * 16) do |out|
|
|
181
|
+
rc = w.fn(:uuid_new_v7_batch).call(i64(unix_millis), count, out)
|
|
182
|
+
case rc
|
|
183
|
+
when 0 then w.memory.read(out, count * 16)
|
|
184
|
+
when 2 then raise TimestampOutOfRangeError, "unix_millis must fit within the RFC 9562 48-bit field"
|
|
185
|
+
else raise RandomSourceError, "uuid_new_v7_batch failed with code #{rc}"
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def v7_to_sql_order(bytes)
|
|
192
|
+
rewrite(:uuid_v7_to_sql_order, bytes)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def v7_to_rfc_order(bytes)
|
|
196
|
+
rewrite(:uuid_v7_to_rfc_order, bytes)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def v6_to_sql_order(bytes)
|
|
200
|
+
rewrite(:uuid_v6_to_sql_order, bytes)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def v6_to_rfc_order(bytes)
|
|
204
|
+
rewrite(:uuid_v6_to_rfc_order, bytes)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
# The in-place byte-order rewrites: copy the caller's frozen bytes into the guest,
|
|
210
|
+
# let the core rewrite them there, read the result back.
|
|
211
|
+
def rewrite(export, bytes)
|
|
212
|
+
wasm do |w|
|
|
213
|
+
w.memory.write(w.in, bytes)
|
|
214
|
+
w.fn(export).call(w.in)
|
|
215
|
+
w.memory.read(w.in, 16)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Every call holds the one lock: the Store is single-threaded, and the lazily created
|
|
220
|
+
# instance is shared by every thread for the life of the process (never torn down,
|
|
221
|
+
# same as the dlopen'd library natively).
|
|
222
|
+
def wasm
|
|
223
|
+
@wasm_mutex.synchronize do
|
|
224
|
+
@wasm ||= WasmInstance.new(WASM_MODULE_PATH)
|
|
225
|
+
yield @wasm
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# wasm has no unsigned integers: a u64 crosses as an i64. The core's own range checks
|
|
230
|
+
# (48-bit v7, 60-bit v6) still see the exact value the caller passed, they just see
|
|
231
|
+
# it through a two's-complement reinterpretation on the way in and back.
|
|
232
|
+
def i64(value)
|
|
233
|
+
value >= 2**63 ? value - 2**64 : value
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def u64(value)
|
|
237
|
+
value.negative? ? value + 2**64 : value
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
data/lib/hyperuuid.rb
CHANGED
|
@@ -13,7 +13,7 @@ require_relative "hyperuuid/runtime"
|
|
|
13
13
|
module HyperUuid
|
|
14
14
|
# This gem's own version — distinct from the RFC 9562 UUID *versions* (v4/v5/v6/v7) the
|
|
15
15
|
# rest of this module generates.
|
|
16
|
-
VERSION = "0.
|
|
16
|
+
VERSION = "0.3.0"
|
|
17
17
|
|
|
18
18
|
# Creates a random UUID version 4 (RFC 9562 §5.4).
|
|
19
19
|
def self.new_v4
|
|
@@ -115,8 +115,24 @@ end
|
|
|
115
115
|
# remain the universal zero-compile fallback; precompiled platform gems are how the
|
|
116
116
|
# extension ships without ever making a consumer compile anything. Set HYPERUUID_PURE=1 to
|
|
117
117
|
# force Fiddle.
|
|
118
|
+
#
|
|
119
|
+
# The third backend is WebAssembly (lib/hyperuuid/wasm_runtime.rb): the same core as a
|
|
120
|
+
# wasm32-wasip1 module, run in-process by the `wasmtime` gem, which is deliberately not a
|
|
121
|
+
# runtime dependency of this gem — a consumer who wants it installs it. HYPERUUID_WASM=1
|
|
122
|
+
# forces it (and fails loudly if wasmtime is missing); otherwise it is only ever chosen when
|
|
123
|
+
# there is no native library for this platform at all and wasmtime happens to be available,
|
|
124
|
+
# so no supported platform's behavior changes by its existence.
|
|
118
125
|
HyperUuid::BACKEND =
|
|
119
|
-
if ENV["
|
|
126
|
+
if ENV["HYPERUUID_WASM"]
|
|
127
|
+
begin
|
|
128
|
+
require "wasmtime"
|
|
129
|
+
rescue LoadError
|
|
130
|
+
raise LoadError,
|
|
131
|
+
"hyperuuid: HYPERUUID_WASM=1 needs the wasmtime gem — `gem install wasmtime` (or add it to your Gemfile)"
|
|
132
|
+
end
|
|
133
|
+
require_relative "hyperuuid/wasm_runtime"
|
|
134
|
+
:wasm
|
|
135
|
+
elsif ENV["HYPERUUID_PURE"]
|
|
120
136
|
:fiddle
|
|
121
137
|
else
|
|
122
138
|
# Two layouts, and both have to work. A released platform gem is a "fat" gem carrying one
|
|
@@ -136,7 +152,21 @@ HyperUuid::BACKEND =
|
|
|
136
152
|
require "hyperuuid_native"
|
|
137
153
|
:native
|
|
138
154
|
rescue LoadError
|
|
139
|
-
|
|
155
|
+
if HyperUuid::Runtime.fiddle_library_available?
|
|
156
|
+
:fiddle
|
|
157
|
+
else
|
|
158
|
+
# No shared library for this platform either. wasmtime, if the consumer has it,
|
|
159
|
+
# is the only backend left that can run here; without it, stay on Fiddle so the
|
|
160
|
+
# first call raises its own precise "not found" LoadError rather than a vaguer one
|
|
161
|
+
# from here.
|
|
162
|
+
begin
|
|
163
|
+
require "wasmtime"
|
|
164
|
+
require_relative "hyperuuid/wasm_runtime"
|
|
165
|
+
:wasm
|
|
166
|
+
rescue LoadError
|
|
167
|
+
:fiddle
|
|
168
|
+
end
|
|
169
|
+
end
|
|
140
170
|
end
|
|
141
171
|
end
|
|
142
172
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hyperuuid
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: aarch64-linux
|
|
6
6
|
authors:
|
|
7
7
|
- Brian Buvinghausen
|
|
@@ -84,11 +84,13 @@ files:
|
|
|
84
84
|
- lib/hyperuuid/native/linux-x64/libhyperuuid.so
|
|
85
85
|
- lib/hyperuuid/native/osx-arm64/libhyperuuid.dylib
|
|
86
86
|
- lib/hyperuuid/native/osx-x64/libhyperuuid.dylib
|
|
87
|
+
- lib/hyperuuid/native/wasm32-wasip1/hyperuuid.wasm
|
|
87
88
|
- lib/hyperuuid/native/win-arm64/hyperuuid.dll
|
|
88
89
|
- lib/hyperuuid/native/win-x64/hyperuuid.dll
|
|
89
90
|
- lib/hyperuuid/native_platform.rb
|
|
90
91
|
- lib/hyperuuid/runtime.rb
|
|
91
92
|
- lib/hyperuuid/uuid.rb
|
|
93
|
+
- lib/hyperuuid/wasm_runtime.rb
|
|
92
94
|
homepage: https://github.com/SkunkWerkx/HyperUuid
|
|
93
95
|
licenses:
|
|
94
96
|
- MIT
|