Module: Toy::MRI
- Defined in:
- lib/toy/mri.rb
Overview
MRI-side runtime support for the Spinel FFI DSL.
Defined Under Namespace
Classes: NativeCallError, ShimCollisionError
Constant Summary collapse
- NATIVE_LIB =
The Stage B shared library (CPU ggml + tinynn, self-contained —‘make libtinynn_shared`). Repo-relative: this file lives at <root>/lib/toy/mri.rb, the artifact at <root>/tinynn/.
File.("../../tinynn/libtinynn_ggml_shared.so", __dir__)
- INT_ARRAY_FMT =
native long == int64_t on LP64
"l!*"- FLOAT_ARRAY_FMT =
C double
"d*"- OFF_STATE_NATIVES =
The deliberate NON-RAISING declarations of the STUB ARM ONLY —every other declared function raises NativeCallError there. In the NATIVE arm this table is BYPASSED ENTIRELY: tnn_null_ptr and the trace family bind through Fiddle like everything else (real calls, real off-state values from tinynn_trace.c, real NULL → 0 from tnn_null_ptr). Two families, both with an exact, documented pure-value semantic (no computation is being masked):
-
‘tnn_null_ptr` — returns a typed NULL `void *`; exists only as a Spinel type-inference workaround (`:ptr` ivars seed with it instead of `nil`, else post-85a4670 inference boxes them as sp_RbVal — see lib/toy/ffi/tinynn.rb FFNFFICache#initialize). Under MRI the honest equivalent of a typed NULL handle is `nil`. Without this, `TransformerLM.new` — the PURE-RUBY teaching model, the whole point of Stage A — raises at construction despite never executing an FFI branch.
-
The trace OFF-state (tinynn/tinynn_trace.c): until ‘tnn_trace_open` succeeds, natively begin→0, end/mark→nothing, active→0, op_capture_active→0. Under MRI tracing can never open (`tnn_trace_open` RAISES — a state change we cannot honor, so it fails loud, as does tnn_trace_set_op_capture), so the off-state values ARE the native semantics, forever. Without this, every Mat op raises — Mat#matmul et al. are unconditionally instrumented with tnn_trace_begin/end.
-
{ tnn_null_ptr: nil, tnn_trace_begin: 0, tnn_trace_end: nil, tnn_trace_mark: nil, tnn_trace_active: 0, tnn_trace_op_capture_active: 0, }.freeze
Class Attribute Summary collapse
-
.cflags ⇒ Object
readonly
Public registry — the Fiddle arm binds against exactly these.
-
.declarations ⇒ Object
readonly
Public registry — the Fiddle arm binds against exactly these.
-
.libs ⇒ Object
readonly
Public registry — the Fiddle arm binds against exactly these.
-
.native_handle ⇒ Object
readonly
Public registry — the Fiddle arm binds against exactly these.
Class Method Summary collapse
-
.fiddle_type(t) ⇒ Object
Spinel’s FFI lowering, pinned by probing generated C (a699cf9): :ptr → void* (Ruby side: integer-valued handle) :int → int :long → long :size_t → size_t :double → double :str → const char* :void → void :int_array → const int64_t* (Spinel Array<Integer> storage; == native long on LP64 → pack(“l!*”)) :float_array → const double* (Array<Float> storage — NOT float; the C side narrows where needed → pack(“d*”)) The tinynn headers declare exactly these widths per family (verified: int64_t*/long* for the 2 int_array sigs, double* for the 6 float_array sigs).
-
.native? ⇒ Boolean
True when the Stage B native arm is live (shared lib dlopened).
-
.native_impl(mod, name, argtypes, rettype) ⇒ Object
Build the native (Fiddle-backed) implementation for one declaration.
- .record_cflags(mod, flags) ⇒ Object
- .record_func(mod, name, argtypes, rettype) ⇒ Object
- .record_lib(mod, name) ⇒ Object
-
.try_open_native! ⇒ Object
── Stage B: the Fiddle binder ────────────────────────────────── Called once at load.
Class Attribute Details
.cflags ⇒ Object (readonly)
Public registry — the Fiddle arm binds against exactly these.
62 63 64 |
# File 'lib/toy/mri.rb', line 62 def cflags @cflags end |
.declarations ⇒ Object (readonly)
Public registry — the Fiddle arm binds against exactly these.
62 63 64 |
# File 'lib/toy/mri.rb', line 62 def declarations @declarations end |
.libs ⇒ Object (readonly)
Public registry — the Fiddle arm binds against exactly these.
62 63 64 |
# File 'lib/toy/mri.rb', line 62 def libs @libs end |
.native_handle ⇒ Object (readonly)
Public registry — the Fiddle arm binds against exactly these.
62 63 64 |
# File 'lib/toy/mri.rb', line 62 def native_handle @native_handle end |
Class Method Details
.fiddle_type(t) ⇒ Object
Spinel’s FFI lowering, pinned by probing generated C (a699cf9):
:ptr → void* (Ruby side: integer-valued handle)
:int → int :long → long :size_t → size_t
:double → double :str → const char* :void → void
:int_array → const int64_t* (Spinel Array<Integer> storage;
== native long on LP64 → pack("l!*"))
:float_array → const double* (Array<Float> storage — NOT
float; the C side narrows where needed → pack("d*"))
The tinynn headers declare exactly these widths per family (verified: int64_t*/long* for the 2 int_array sigs, double* for the 6 float_array sigs).
110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
# File 'lib/toy/mri.rb', line 110 def fiddle_type(t) case t when :ptr, :str, :int_array, :float_array then Fiddle::TYPE_VOIDP when :int then Fiddle::TYPE_INT when :long then Fiddle::TYPE_LONG when :size_t then Fiddle::TYPE_SIZE_T when :double then Fiddle::TYPE_DOUBLE when :void then Fiddle::TYPE_VOID else raise NativeCallError, "unknown FFI type #{t.inspect} in a declaration — extend " \ "Toy::MRI.fiddle_type in lockstep with Spinel's DSL (toy#71)" end end |
.native? ⇒ Boolean
True when the Stage B native arm is live (shared lib dlopened).
65 66 67 |
# File 'lib/toy/mri.rb', line 65 def native? !@native_handle.nil? end |
.native_impl(mod, name, argtypes, rettype) ⇒ Object
Build the native (Fiddle-backed) implementation for one declaration. Spinel’s array specs are ZERO-COPY (the C side reads AND writes the Ruby array’s storage); Fiddle can’t share storage with an MRI Array, so we mirror the semantics: pack → call →unpack → Array#replace IN PLACE on the caller’s array. That keeps output-array conventions (pre-sized Array filled by C, e.g. tnn_read_i32_file, tnn_download_to_f64_array) working unchanged.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/toy/mri.rb', line 135 def native_impl(mod, name, argtypes, rettype) sym = begin @native_handle[name.to_s] rescue Fiddle::DLError nil end if sym.nil? # Loud, named, at the CALL site (a require-time raise would # take down the whole surface for one stale artifact). return lambda do |*_args| raise NativeCallError, "native call `#{name}` (declared in #{mod}) is missing from " \ "#{File.basename(NATIVE_LIB)} — stale artifact? rebuild with " \ "`make libtinynn_shared` (toy#71)" end end fn = Fiddle::Function.new(sym, argtypes.map { |t| fiddle_type(t) }, fiddle_type(rettype)) lambda do |*args| if args.length != argtypes.length raise ArgumentError, "#{mod}.#{name}: #{args.length} args for " \ "#{argtypes.length}-arg native declaration" end arrays = nil cargs = Array.new(args.length) i = 0 while i < args.length a = args[i] cargs[i] = case argtypes[i] when :ptr then a.nil? ? 0 : a when :int, :long, :size_t then Integer(a) when :double then Float(a) when :str then a # String buffer → const char* when :int_array buf = a.pack(INT_ARRAY_FMT) (arrays ||= []) << [a, buf, INT_ARRAY_FMT] buf when :float_array buf = a.pack(FLOAT_ARRAY_FMT) (arrays ||= []) << [a, buf, FLOAT_ARRAY_FMT] buf end i += 1 end r = fn.call(*cargs) arrays&.each { |orig, buf, fmt| orig.replace(buf.unpack(fmt)) } case rettype when :ptr then r.to_i when :str then r.null? ? nil : r.to_s when :void then nil else r end end end |
.record_cflags(mod, flags) ⇒ Object
74 75 76 77 |
# File 'lib/toy/mri.rb', line 74 def record_cflags(mod, flags) (@cflags[mod] ||= []) << flags nil end |
.record_func(mod, name, argtypes, rettype) ⇒ Object
79 80 81 82 |
# File 'lib/toy/mri.rb', line 79 def record_func(mod, name, argtypes, rettype) (@declarations[mod] ||= []) << [name.to_sym, argtypes, rettype] nil end |
.record_lib(mod, name) ⇒ Object
69 70 71 72 |
# File 'lib/toy/mri.rb', line 69 def record_lib(mod, name) (@libs[mod] ||= []) << name nil end |
.try_open_native! ⇒ Object
── Stage B: the Fiddle binder ──────────────────────────────────Called once at load. Honors TOY_MRI_NATIVE=0 (explicit stub-arm opt-out, silent); otherwise dlopens the shared lib when present, or hints (one stderr line) at the make target when absent.
88 89 90 91 92 93 94 95 96 97 |
# File 'lib/toy/mri.rb', line 88 def try_open_native! return if ENV["TOY_MRI_NATIVE"] == "0" if File.file?(NATIVE_LIB) require "fiddle" @native_handle = Fiddle.dlopen(NATIVE_LIB) else warn "toy/mri: stub arm (native calls raise) — `make libtinynn_shared` " \ "builds tinynn/libtinynn_ggml_shared.so for real MRI compute (toy#71)" end end |