hypercast-wasm 0.6.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +495 -0
- data/ext/hypercast_native/3.4/hypercast_native.a.gz +0 -0
- data/ext/hypercast_native/4.0/hypercast_native.a.gz +0 -0
- data/ext/hypercast_native/extconf.rb +107 -0
- data/lib/hypercast/native_platform.rb +28 -0
- data/lib/hypercast/runtime.rb +128 -0
- data/lib/hypercast.rb +538 -0
- metadata +84 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module HyperCast
|
|
2
|
+
# Maps the running RUBY_PLATFORM to the RID-style directory (matching the other bindings'
|
|
3
|
+
# runtimes/{rid}/native/ / native/{rid}/ convention) and native library filename to load.
|
|
4
|
+
module NativePlatform
|
|
5
|
+
class UnsupportedPlatformError < StandardError; end
|
|
6
|
+
|
|
7
|
+
# +platform+ is a parameter only so the specs can walk the whole table from one host;
|
|
8
|
+
# every real caller takes the default.
|
|
9
|
+
def self.rid_and_library_name(platform = RUBY_PLATFORM)
|
|
10
|
+
arch = platform.match?(/arm64|aarch64/) ? "arm64" : "x64"
|
|
11
|
+
|
|
12
|
+
case platform
|
|
13
|
+
when /mingw|mswin|windows/
|
|
14
|
+
["win-#{arch}", "hypercast.dll"]
|
|
15
|
+
when /darwin/
|
|
16
|
+
["osx-#{arch}", "libhypercast.dylib"]
|
|
17
|
+
when /linux/
|
|
18
|
+
# Two C libraries, two builds: a glibc-linked library cannot be relied on to dlopen
|
|
19
|
+
# into a musl process (Alpine), so musl gets RIDs of its own. Ruby names the libc
|
|
20
|
+
# in its platform string there ("x86_64-linux-musl") and leaves it off on glibc.
|
|
21
|
+
os = platform.include?("musl") ? "linux-musl" : "linux"
|
|
22
|
+
["#{os}-#{arch}", "libhypercast.so"]
|
|
23
|
+
else
|
|
24
|
+
raise UnsupportedPlatformError, "hypercast: unsupported platform RUBY_PLATFORM=#{platform}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Autoloaded, not required: the platform gems carry no library for Fiddle to open and do not
|
|
2
|
+
# declare fiddle, so loading it eagerly would make `require "hypercast"` fail under Bundler
|
|
3
|
+
# wherever fiddle is a bundled rather than a default gem (Ruby 4.0). It loads the first time
|
|
4
|
+
# the Fiddle backend runs, and every path there goes through `functions` first, so a platform
|
|
5
|
+
# gem forced onto Fiddle still gets missing_library_message rather than a bare LoadError.
|
|
6
|
+
autoload :Fiddle, "fiddle"
|
|
7
|
+
|
|
8
|
+
module HyperCast
|
|
9
|
+
# Fiddle plumbing for the native libhypercast shared library — dlopen/dlsym plus raw
|
|
10
|
+
# C-ABI calls, no runtime bridge. Fiddle ships with every Ruby install; it's a plain gem
|
|
11
|
+
# dependency of the universal gem (see hypercast.gemspec) rather than a third-party one.
|
|
12
|
+
# A Ruby gem's files are already plain files on disk once installed, so native/{rid}/{lib}
|
|
13
|
+
# dlopen's directly — no extraction.
|
|
14
|
+
module Runtime
|
|
15
|
+
NATIVE_DIR = File.join(__dir__, "native")
|
|
16
|
+
|
|
17
|
+
# Each door's C signature, by shape — resolved to Fiddle types only in load_functions,
|
|
18
|
+
# so nothing here touches Fiddle until the Fiddle backend actually runs: plain is
|
|
19
|
+
# (text, len, out, fault), numeric adds the packed NumFormat pointer before out, and
|
|
20
|
+
# declared a caller-declared u32 (precision, epoch, field order) in the same place.
|
|
21
|
+
DOORS = {
|
|
22
|
+
cast_bool: :plain,
|
|
23
|
+
cast_i8: :numeric, cast_i16: :numeric, cast_i32: :numeric, cast_i64: :numeric,
|
|
24
|
+
cast_u8: :numeric, cast_u16: :numeric, cast_u32: :numeric, cast_u64: :numeric,
|
|
25
|
+
cast_f32: :numeric, cast_f64: :numeric, cast_decimal: :numeric,
|
|
26
|
+
cast_uuid: :plain,
|
|
27
|
+
cast_timestamp: :plain, cast_unix: :declared, cast_excel_serial: :declared,
|
|
28
|
+
cast_date: :plain, cast_date_ordered: :declared, cast_datetime: :declared,
|
|
29
|
+
cast_time: :plain, cast_duration: :plain
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
# The one export that is not a door: the zero-argument version probe, returning the
|
|
33
|
+
# core's packed version word (major << 16 | minor << 8 | patch) rather than a verdict
|
|
34
|
+
# code — so it gets its own Fiddle signature beside DOORS instead of a row in it.
|
|
35
|
+
VERSION_PROBE = :hypercast_version
|
|
36
|
+
|
|
37
|
+
@mutex = Mutex.new
|
|
38
|
+
@functions = nil
|
|
39
|
+
|
|
40
|
+
class << self
|
|
41
|
+
# The door's (or VERSION_PROBE's) Fiddle::Function, for the caller to invoke directly
|
|
42
|
+
# — a splat-through `call(symbol, *args)` here built and re-splatted an Array on
|
|
43
|
+
# every cast.
|
|
44
|
+
def function(symbol)
|
|
45
|
+
functions.fetch(symbol)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Every Fiddle allocation goes through here, and loads the library first: that is what
|
|
49
|
+
# keeps a missing library reported as missing_library_message instead of as whatever
|
|
50
|
+
# touching Fiddle raises first. The per-thread scratch buffer and each packed NumFormat
|
|
51
|
+
# (hypercast.rb) pay for it once apiece.
|
|
52
|
+
def buffer(size)
|
|
53
|
+
functions
|
|
54
|
+
Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# The shared library to dlopen: this install's native/{rid}/{lib}, or — the
|
|
60
|
+
# development loop — the in-repo cargo build, exactly what the other bindings' local
|
|
61
|
+
# staging does. Nil when neither exists.
|
|
62
|
+
def library_path
|
|
63
|
+
rid, lib_name = NativePlatform.rid_and_library_name
|
|
64
|
+
path = File.join(NATIVE_DIR, rid, lib_name)
|
|
65
|
+
return path if File.exist?(path)
|
|
66
|
+
|
|
67
|
+
repo_build = File.expand_path(File.join(__dir__, "../../../rust/target/release", lib_name))
|
|
68
|
+
File.exist?(repo_build) ? repo_build : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Why Fiddle found nothing to load. A precompiled platform gem is the one install where
|
|
72
|
+
# that is by design rather than a gap: it carries only its Magnus extensions, and the
|
|
73
|
+
# Fiddle backend is reached there only by forcing it (HYPERCAST_PURE) or because none
|
|
74
|
+
# of its extensions loaded — a gem RubyGems matched to a Ruby it was not built for,
|
|
75
|
+
# such as the glibc Linux gem that `gem install` on RubyGems 3.x picks on Alpine. So
|
|
76
|
+
# that case names its fix, the universal gem, which carries every platform's library,
|
|
77
|
+
# instead of a missing path that reads like a packaging bug. Both arguments are
|
|
78
|
+
# parameters only so the specs can ask for every wording.
|
|
79
|
+
def missing_library_message(gem_platform = Gem.loaded_specs["hypercast"]&.platform,
|
|
80
|
+
forced = ENV.key?("HYPERCAST_PURE"))
|
|
81
|
+
rid, lib_name = NativePlatform.rid_and_library_name
|
|
82
|
+
missing = File.join(NATIVE_DIR, rid, lib_name)
|
|
83
|
+
if gem_platform && gem_platform.to_s != Gem::Platform::RUBY
|
|
84
|
+
reason =
|
|
85
|
+
if forced
|
|
86
|
+
"HYPERCAST_PURE forces the Fiddle backend (unset it to use the extension)"
|
|
87
|
+
else
|
|
88
|
+
"none of its extensions loads on this Ruby (#{RUBY_VERSION}, #{RUBY_PLATFORM})"
|
|
89
|
+
end
|
|
90
|
+
"hypercast: this #{gem_platform} platform gem carries only Magnus extensions, no " \
|
|
91
|
+
"Fiddle library, and #{reason}. The universal gem has the Fiddle backend for every " \
|
|
92
|
+
"platform: `gem install hypercast --platform ruby`, or Bundler's force_ruby_platform " \
|
|
93
|
+
"(#{missing} not found)"
|
|
94
|
+
else
|
|
95
|
+
"hypercast: #{missing} not found (unsupported platform, or this gem was built " \
|
|
96
|
+
"without a native library for it)"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Loaded lazily and exactly once; the native library and its function pointers live
|
|
101
|
+
# for the process's lifetime, same as every other binding (never dlclose'd). The
|
|
102
|
+
# unsynchronized read is the fast path — a per-call mutex acquisition measured as a
|
|
103
|
+
# real slice of the door cost; the benign race re-checks under the lock.
|
|
104
|
+
def functions
|
|
105
|
+
@functions || @mutex.synchronize { @functions ||= load_functions }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def load_functions
|
|
109
|
+
path = library_path
|
|
110
|
+
raise LoadError, missing_library_message if path.nil?
|
|
111
|
+
|
|
112
|
+
handle = Fiddle.dlopen(path)
|
|
113
|
+
plain = [Fiddle::TYPE_VOIDP, Fiddle::TYPE_SIZE_T, Fiddle::TYPE_VOIDP, Fiddle::TYPE_VOIDP]
|
|
114
|
+
signatures = {
|
|
115
|
+
plain: plain,
|
|
116
|
+
numeric: plain.dup.insert(2, Fiddle::TYPE_VOIDP),
|
|
117
|
+
declared: plain.dup.insert(2, Fiddle::TYPE_UINT32_T)
|
|
118
|
+
}
|
|
119
|
+
functions = DOORS.to_h do |name, shape|
|
|
120
|
+
[name, Fiddle::Function.new(handle[name.to_s], signatures.fetch(shape), Fiddle::TYPE_INT32_T)]
|
|
121
|
+
end
|
|
122
|
+
functions[VERSION_PROBE] =
|
|
123
|
+
Fiddle::Function.new(handle[VERSION_PROBE.to_s], [], Fiddle::TYPE_UINT32_T)
|
|
124
|
+
functions
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|