rudy4 0.2.0

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.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/LICENSE +21 -0
  4. data/Makefile +137 -0
  5. data/README.md +400 -0
  6. data/bin/rudy4 +6 -0
  7. data/examples/hello_gem/README.md +91 -0
  8. data/examples/hello_gem/ext/hello_gem/extconf.rb +75 -0
  9. data/examples/hello_gem/ext/hello_gem/hello_gem.d +122 -0
  10. data/examples/hello_gem/hello_gem.gemspec +35 -0
  11. data/examples/hello_gem/lib/hello_gem/version.rb +5 -0
  12. data/examples/hello_gem/lib/hello_gem.rb +32 -0
  13. data/examples/hello_gem/test/smoke.sh +81 -0
  14. data/experiments/exp_compact.d +178 -0
  15. data/experiments/exp_convert.d +93 -0
  16. data/experiments/exp_cross.d +144 -0
  17. data/experiments/exp_defargs.d +301 -0
  18. data/experiments/exp_exc.d +265 -0
  19. data/experiments/exp_gcstorm.d +169 -0
  20. data/experiments/exp_http.d +254 -0
  21. data/experiments/exp_nonregression.d +209 -0
  22. data/experiments/exp_objproto.d +123 -0
  23. data/experiments/exp_perf.d +81 -0
  24. data/experiments/exp_wrap.d +186 -0
  25. data/lib/rudy4/cli.rb +204 -0
  26. data/lib/rudy4/doctor.rb +295 -0
  27. data/lib/rudy4/error.rb +10 -0
  28. data/lib/rudy4/generator.rb +172 -0
  29. data/lib/rudy4/templates.rb +484 -0
  30. data/lib/rudy4/vendor.rb +36 -0
  31. data/lib/rudy4/version.rb +5 -0
  32. data/lib/rudy4.rb +16 -0
  33. data/scripts/abi_audit.sh +64 -0
  34. data/scripts/memcheck.sh +77 -0
  35. data/scripts/vendor_check.sh +33 -0
  36. data/scripts/vendor_sync.sh +25 -0
  37. data/src/dexitest.d +278 -0
  38. data/src/dextest.d +119 -0
  39. data/src/dtypetest.d +159 -0
  40. data/src/rudy/args.d +253 -0
  41. data/src/rudy/bind/value.d +306 -0
  42. data/src/rudy/convert.d +221 -0
  43. data/src/rudy/defs.d +440 -0
  44. data/src/rudy/objproto.d +282 -0
  45. data/src/rudy/typeddata.d +143 -0
  46. data/test/dextest_loader.rb +6 -0
  47. data/test/exitest.rb +113 -0
  48. data/test/rudy4_test.rb +291 -0
  49. data/test/run.rb +74 -0
  50. data/test/typedtest.rb +69 -0
  51. data/vendor/rudy/args.d +253 -0
  52. data/vendor/rudy/bind/value.d +306 -0
  53. data/vendor/rudy/convert.d +221 -0
  54. data/vendor/rudy/defs.d +440 -0
  55. data/vendor/rudy/objproto.d +282 -0
  56. data/vendor/rudy/typeddata.d +143 -0
  57. metadata +106 -0
data/src/rudy/args.d ADDED
@@ -0,0 +1,253 @@
1
+ /// Parameter wrappers for `def!` (rudy/defs.d) that reach past the
2
+ /// plain positional-argument model. The 2010 RuDy had none of these;
3
+ /// a `def!`'d function could neither receive the caller's block nor
4
+ /// take rest/keyword arguments.
5
+ ///
6
+ /// void with_block(RubyBlock blk) { blk.call(42); } // receives the
7
+ /// // caller's block
8
+ /// long rest_sum(long first, Rest rest) { ... } // 1 + *rest
9
+ /// D str_kw(KwArgs kw) { kw.get!string("sep", "-") } // **kwargs
10
+ ///
11
+ /// All wrappers are VALUE-sized (passable as fixed-arity C args); the
12
+ /// interesting VALUE lives inside (block proc, rest array, kwargs
13
+ /// hash). See defs.d for how the trampolines populate them.
14
+
15
+ module rudy.args;
16
+
17
+ import rudy.bind.value;
18
+ import rudy.convert;
19
+ import std.string : toStringz;
20
+
21
+ // lazily-interned method names (rb_intern on every call is a symbol-table
22
+ // hash probe; the VM is alive inside any method call, so lazy init is safe)
23
+ private __gshared ID rudy_id_call;
24
+ private __gshared ID rudy_id_arity;
25
+ private __gshared ID rudy_id_length;
26
+ private __gshared ID rudy_id_to_a;
27
+ private __gshared ID rudy_id_to_s;
28
+
29
+ private ID id_call() { if (!rudy_id_call) rudy_id_call = rb_intern("call"); return rudy_id_call; }
30
+ private ID id_arity() { if (!rudy_id_arity) rudy_id_arity = rb_intern("arity"); return rudy_id_arity; }
31
+ private ID id_length() { if (!rudy_id_length) rudy_id_length = rb_intern("length"); return rudy_id_length; }
32
+ private ID id_to_a() { if (!rudy_id_to_a) rudy_id_to_a = rb_intern("to_a"); return rudy_id_to_a; }
33
+ private ID id_to_s() { if (!rudy_id_to_s) rudy_id_to_s = rb_intern("to_s"); return rudy_id_to_s; }
34
+
35
+ // ---------------------------------------------------------------- RubyBlock
36
+
37
+ /// A caller's block, materialized as a Proc. `def!` fills it in via
38
+ /// rb_scan_args' "&" slot inside the trampoline (Qnil when no block was
39
+ /// passed), before the D function runs.
40
+ ///
41
+ /// blk.call(args...) — invoke the block
42
+ /// blk.arity — the Proc's arity
43
+ /// blk.given — was a block actually passed?
44
+ ///
45
+ /// A block-less method raises LocalJumpError from Ruby the moment
46
+ /// call() runs — same failure mode as bare `yield` in Ruby. Check
47
+ /// `given` first when that must not happen.
48
+ struct RubyBlock
49
+ {
50
+ VALUE proc = Qnil_;
51
+
52
+ /// Was a block actually passed to this method?
53
+ bool given() const @safe pure nothrow @nogc { return proc != Qnil_; }
54
+
55
+ /// The wrapped Proc, as raw VALUE (for API-passing scenarios).
56
+ Raw!VALUE raw() const @safe pure nothrow @nogc { return Raw!VALUE(proc); }
57
+
58
+ /// Number of arguments the block expects (Proc#arity).
59
+ long arity() { return d_type!long(rb_funcallv(proc, id_arity(), 0, null)); }
60
+
61
+ /// Call the block with args; returns whatever the block returned.
62
+ /// LocalJumpError (from Ruby) if called with no block given; any
63
+ /// exception the block raises propagates straight through the D frame.
64
+ VALUE call() { return lje_or_call(proc, 0, null); }
65
+
66
+ VALUE call(VALUE a1)
67
+ {
68
+ VALUE[1] av = [a1];
69
+ return lje_or_call(proc, 1, av.ptr);
70
+ }
71
+
72
+ VALUE call(VALUE a1, VALUE a2)
73
+ {
74
+ VALUE[2] av = [a1, a2];
75
+ return lje_or_call(proc, 2, av.ptr);
76
+ }
77
+
78
+ VALUE call(VALUE a1, VALUE a2, VALUE a3)
79
+ {
80
+ VALUE[3] av = [a1, a2, a3];
81
+ return lje_or_call(proc, 3, av.ptr);
82
+ }
83
+
84
+ /// Call with a pre-built argv (n args).
85
+ VALUE callv(int n, const(VALUE)* argv)
86
+ {
87
+ return lje_or_call(proc, n, cast(VALUE*) argv);
88
+ }
89
+ }
90
+
91
+ /// RubyBlock#call contract helper: a block-less call is the same error
92
+ /// a bare `yield` with no block would give (rb_funcallv on Qnil would
93
+ /// give a misleading NoMethodError instead).
94
+ private VALUE lje_or_call(VALUE proc, int n, VALUE* argv)
95
+ {
96
+ if (proc == Qnil_)
97
+ rb_raise(rb_eLocalJumpError, "no block given (yield)");
98
+ return rb_funcallv(proc, id_call(), n, argv);
99
+ }
100
+
101
+ // ---------------------------------------------------------------- Self
102
+
103
+ /// The method's receiver (self), passed through raw. First-parameter
104
+ /// convention; lets def!'d functions inspect the object they were
105
+ /// called on (class, ivars, singleton-ness) without dropping to a
106
+ /// hand-written trampoline.
107
+ ///
108
+ /// long klass_name(Self self) { ... rb_obj_class(self.v) ... }
109
+ struct Self
110
+ {
111
+ VALUE v = Qnil_;
112
+
113
+ /// The receiver as raw VALUE.
114
+ Raw!VALUE raw() const @safe pure nothrow @nogc { return Raw!VALUE(v); }
115
+
116
+ /// implicit conversion so `self.v` and passing Self around stays terse
117
+ VALUE value() const @safe pure nothrow @nogc { return v; }
118
+ }
119
+
120
+ // ---------------------------------------------------------------- Rest
121
+
122
+ /// Captured `*rest` arguments, as a Ruby Array. The trampoline owns the
123
+ /// VALUE (GC-rooted for the call's duration); the D function sees this
124
+ /// slice-style wrapper.
125
+ struct Rest
126
+ {
127
+ VALUE ary = Qnil_;
128
+
129
+ /// Number of captured rest arguments (0 for an unused slot).
130
+ @property size_t length() const
131
+ {
132
+ if (ary == Qnil_)
133
+ return 0;
134
+ return cast(size_t) d_type!long(rb_funcallv(ary, id_length(), 0, null));
135
+ }
136
+
137
+ /// True when nothing was captured. NB: rb_scan_args' "*" slot fills
138
+ /// an EMPTY ARRAY (not Qnil) when the caller passes zero rest args —
139
+ /// this must agree with Ruby-side `rest.empty?`, so it checks the
140
+ /// length, not just the slot's Qnil-ness.
141
+ bool empty() { return length == 0; }
142
+
143
+ /// i-th captured argument (0-based).
144
+ VALUE opIndex(size_t i)
145
+ {
146
+ return rb_ary_entry(ary, cast(long) i);
147
+ }
148
+
149
+ /// Iterate the captured arguments (foreach over a Rest works).
150
+ int opApply(scope int delegate(ref VALUE) dg)
151
+ {
152
+ long n = cast(long) length;
153
+ foreach (i; 0 .. n)
154
+ {
155
+ VALUE v = rb_ary_entry(ary, i);
156
+ int r = dg(v);
157
+ if (r)
158
+ return r;
159
+ }
160
+ return 0;
161
+ }
162
+
163
+ /// The raw Ruby Array, as raw VALUE.
164
+ Raw!VALUE raw() const @safe pure nothrow @nogc { return Raw!VALUE(ary); }
165
+
166
+ /// Convert every captured arg to D type T (allocates a D array).
167
+ T[] to(T)()
168
+ {
169
+ long n = cast(long) length;
170
+ T[] outv;
171
+ outv.length = cast(size_t) n;
172
+ foreach (i; 0 .. n)
173
+ outv[cast(size_t) i] = d_type!T(rb_ary_entry(ary, i));
174
+ return outv;
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------- KwArgs
179
+
180
+ /// Captured keyword arguments, as a Ruby Hash. `def!` fills it via
181
+ /// rb_scan_args' `:` slot (Qnil_ when no keywords were passed — see
182
+ /// `given`).
183
+ ///
184
+ /// kw.get!long("sep", 1) — decoded with a D default when absent
185
+ /// kw["sep"] — raw VALUE (Qnil_ when absent)
186
+ /// kw.to!(long, string)() — whole hash as a D AA
187
+ struct KwArgs
188
+ {
189
+ VALUE hash = Qnil_;
190
+
191
+ /// Were any keywords passed?
192
+ bool given() const @safe pure nothrow @nogc { return hash != Qnil_; }
193
+
194
+ /// Raw value of keyword `key` (Qnil_ when absent). Ruby keyword keys
195
+ /// are Symbols (`m(sep: x)`), but string keys are legal too
196
+ /// (`m("sep": x)` / string-keyed **hash) — probe both.
197
+ VALUE opIndex(string key)
198
+ {
199
+ if (!given)
200
+ return Qnil_;
201
+ ID id = rb_intern(toStringz(key));
202
+ VALUE v = rb_hash_lookup2(hash, rb_id2sym(id), Qnil_);
203
+ if (v != Qnil_)
204
+ return v;
205
+ VALUE[1] av = [rb_str_new(key.ptr, cast(C_long) key.length)];
206
+ return rb_hash_lookup2(hash, av[0], Qnil_);
207
+ }
208
+
209
+ /// Keyword decoded to D type T; `dflt` when the keyword was absent.
210
+ T get(T)(string key, T dflt)
211
+ {
212
+ VALUE v = opIndex(key);
213
+ if (v == Qnil_ || v == Qundef_)
214
+ return dflt;
215
+ return d_type!T(v);
216
+ }
217
+
218
+ /// All keywords as a D associative array (empty when none passed).
219
+ V[K] to(V, K)()
220
+ {
221
+ V[K] outv;
222
+ if (!given)
223
+ return outv;
224
+ // Hash#to_a -> [[k, v], ...]: plain exported-API walking, no
225
+ // callbacks (rb_block_call's return value can't accumulate).
226
+ VALUE pairs = rb_funcallv(hash, id_to_a(), 0, null);
227
+ long n = d_type!long(rb_funcallv(pairs, id_length(), 0, null));
228
+ foreach (i; 0 .. n)
229
+ {
230
+ VALUE pair = rb_ary_entry(pairs, i);
231
+ VALUE k = rb_ary_entry(pair, 0);
232
+ static if (is(K == string))
233
+ {
234
+ // kwargs keys are normally Symbols; d_type!string of a
235
+ // Symbol raises TypeError. Normalize through to_s
236
+ // (Symbol#to_s -> name String, String#to_s -> self) so
237
+ // string-keyed D AAs see every key. Non-string K keys
238
+ // go through d_type directly.
239
+ k = rb_funcallv(k, id_to_s(), 0, null);
240
+ }
241
+ static if (is(V == Raw!VALUE))
242
+ {
243
+ // raw VALUE escape hatch: bits unchanged, no decode
244
+ outv[d_type!K(k)] = Raw!VALUE(rb_ary_entry(pair, 1));
245
+ }
246
+ else
247
+ {
248
+ outv[d_type!K(k)] = d_type!V(rb_ary_entry(pair, 1));
249
+ }
250
+ }
251
+ return outv;
252
+ }
253
+ }
@@ -0,0 +1,306 @@
1
+ /// Hand-written binding to the Ruby C API (Ruby 3.x / 4.x).
2
+ ///
3
+ /// Design notes:
4
+ /// - `VALUE` is `uintptr_t` on modern 64-bit CRuby. We never touch RBasic
5
+ /// or any R* struct layout: all access goes through exported functions
6
+ /// (rb_float_value, rb_ary_entry, rb_string_value_ptr, ...), the way the
7
+ /// Ruby project intends binary extensions to work post-1.9.
8
+ /// - Global variables (rb_cObject, rb_eArgError, ...) are BSS data symbols;
9
+ /// bound with `extern(C) extern __gshared`.
10
+ /// - `C_long` aliases mirror the old RuDy/PyD trick so the same templates
11
+ /// work on 32/64-bit.
12
+
13
+ module rudy.bind.value;
14
+
15
+ extern (C):
16
+
17
+ // ---------------------------------------------------------------- core types
18
+
19
+ alias ulong VALUE; /// Ruby object reference (uintptr_t, tagged)
20
+ alias long SIGNED_VALUE; /// intptr_t
21
+
22
+ // special constants (values are tag bits, identical on all modern rubies)
23
+ enum VALUE Qnil_ = cast(VALUE)0x04; // RUBY_Qnil
24
+ enum VALUE Qtrue_ = cast(VALUE)0x14; // RUBY_Qtrue
25
+ enum VALUE Qfalse_ = cast(VALUE)0x00; // RUBY_Qfalse
26
+ enum VALUE Qundef_ = cast(VALUE)0x24; // RUBY_Qundef (internal)
27
+
28
+ // Fixed-arity method callback shapes. For a fixed arity N, Ruby calls the
29
+ // C function as `VALUE f(VALUE self, VALUE a1, .., VALUE aN)`; the
30
+ // variadic (argc/argv) style is only used when the registered arity is < 0.
31
+ // rudy's def! always registers fixed arity, so trampolines below use these.
32
+ alias VALUE function(VALUE self) AnyMethod_0;
33
+ alias VALUE function(VALUE self, VALUE a1) AnyMethod_1;
34
+ alias VALUE function(VALUE self, VALUE a1, VALUE a2) AnyMethod_2;
35
+ alias VALUE function(VALUE self, VALUE a1, VALUE a2, VALUE a3) AnyMethod_3;
36
+ alias void* AnyMethod; // what rb_define_method actually takes
37
+
38
+ /// Callback shape for rb_protect: `VALUE (*protected)(VALUE data)`
39
+ alias VALUE function(VALUE) ProtectFn;
40
+
41
+ // ---------------------------------------------------------------- API fns
42
+
43
+ // ---- definitions ---------------------------------------------------------
44
+
45
+ VALUE rb_define_class(const(char)* name, VALUE superclass);
46
+ VALUE rb_define_module(const(char)* name);
47
+ VALUE rb_define_class_under(VALUE outer, const(char)* name, VALUE superclass);
48
+ VALUE rb_define_module_under(VALUE outer, const(char)* name);
49
+ void rb_extend_object(VALUE mod);
50
+ void rb_define_method(VALUE klass, const(char)* name, AnyMethod func, int argc);
51
+ void rb_define_module_function(VALUE mod, const(char)* name, AnyMethod func, int argc);
52
+ void rb_define_global_function(const(char)* name, AnyMethod func, int argc);
53
+ void rb_define_alloc_func(VALUE klass, VALUE function(VALUE) alloc);
54
+ void rb_define_alias(VALUE klass, const(char)* alt, const(char)* old);
55
+ void rb_include_module(VALUE klass, VALUE mod);
56
+ void rb_define_attr(VALUE klass, const(char)* name, int read, int write);
57
+ void rb_define_const(VALUE klass, const(char)* name, VALUE value);
58
+ void rb_define_class_variable(VALUE klass, const(char)* name, VALUE value);
59
+
60
+ ID rb_intern(const(char)* name);
61
+ alias long ID;
62
+
63
+ // ---- calling / conversions ------------------------------------------------
64
+
65
+ VALUE rb_funcall(VALUE recv, ID mid, int argc, ...);
66
+ VALUE rb_funcallv(VALUE recv, ID mid, int argc, const(VALUE)* argv);
67
+ VALUE rb_funcallv_public(VALUE recv, ID mid, int argc, const(VALUE)* argv);
68
+ // call mid with argv and a Proc/block (use for methods taking a block:
69
+ // sort, each, map...) — NOT passable as a positional arg via rb_funcallv
70
+ VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const(VALUE)* argv, VALUE proc);
71
+ VALUE rb_obj_call_init(VALUE obj, int argc, const(VALUE)* argv);
72
+ VALUE rb_eval_string(const(char)* str);
73
+ VALUE rb_obj_alloc(VALUE klass);
74
+
75
+ // numbers
76
+ long rb_num2long(VALUE);
77
+ long rb_num2ll(VALUE);
78
+ double rb_num2dbl(VALUE);
79
+ VALUE rb_ull2inum(C_ulonglong);
80
+ VALUE rb_uint2inum(C_ulong);
81
+ ulong rb_num2ulong(VALUE);
82
+ VALUE rb_ll2inum(long);
83
+ int rb_num2int(VALUE); // raises RangeError outside int range
84
+ uint rb_num2uint(VALUE); // raises RangeError outside uint range
85
+ VALUE rb_int2inum(C_long); // C ABI: intptr_t — was misbound as int (torture
86
+ // finding #4: wrong width silently truncates on
87
+ // SysV x86-64 because of register reuse)
88
+ VALUE rb_float_new(double);
89
+ double rb_float_value(VALUE);
90
+ VALUE rb_obj_is_instance_of(VALUE, VALUE); // exported; rb_type is macro-only
91
+ VALUE rb_Integer(VALUE);
92
+
93
+ // strings
94
+ VALUE rb_str_new(const(char)* ptr, long len);
95
+ VALUE rb_str_new_cstr(const(char)* ptr);
96
+ VALUE rb_str_dup(VALUE);
97
+ void rb_str_modify(VALUE);
98
+ VALUE rb_str_cat(VALUE str, const(char)* ptr, long len);
99
+ VALUE rb_str_buf_cat(VALUE str, const(char)* ptr, long len);
100
+ VALUE rb_string_value(VALUE* value);
101
+ /// Correct signature: takes a pointer to the slot (may replace its
102
+ /// contents, hence volatile). See rstring.h:289.
103
+ char* rb_string_value_ptr(VALUE* ptr);
104
+ const(char)* rb_string_value_cstr(VALUE str);
105
+ long rb_str_strlen(VALUE);
106
+ VALUE rb_str_to_str(VALUE str);
107
+ VALUE rb_obj_as_string(VALUE obj);
108
+ VALUE rb_any_to_s(VALUE obj);
109
+
110
+ // arrays
111
+ VALUE rb_ary_new();
112
+ VALUE rb_ary_new_capa(C_long capa);
113
+ VALUE rb_ary_new_from_values(C_long n, const(VALUE)* vals);
114
+ VALUE rb_ary_push(VALUE ary, VALUE item);
115
+ VALUE rb_ary_entry(VALUE ary, long offset);
116
+ VALUE rb_ary_store(VALUE ary, long idx, VALUE item);
117
+ VALUE rb_ary_join(VALUE ary, VALUE sep);
118
+ VALUE rb_ary_to_s(VALUE ary);
119
+ VALUE rb_obj_class(VALUE obj);
120
+ const(char)* rb_class2name(VALUE klass); // exported (class.c); name of class/module
121
+
122
+ // hashes
123
+ VALUE rb_hash_new();
124
+ VALUE rb_hash_lookup2(VALUE hash, VALUE key, VALUE def); // no Hash#default raise
125
+ VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val);
126
+ VALUE rb_hash_aref(VALUE hash, VALUE key);
127
+ size_t rb_hash_size(VALUE hash);
128
+
129
+ // ivars / constants
130
+ VALUE rb_iv_get(VALUE obj, const(char)* name);
131
+ VALUE rb_iv_set(VALUE obj, const(char)* name, VALUE value);
132
+ VALUE rb_const_get(VALUE klass, ID name);
133
+ void rb_const_set(VALUE klass, ID name, VALUE value);
134
+
135
+ // equality / inspection
136
+ VALUE rb_equal(VALUE a, VALUE b);
137
+ VALUE rb_obj_id(VALUE obj);
138
+ VALUE rb_inspect(VALUE obj);
139
+ VALUE rb_id2sym(ID);
140
+ VALUE rb_sym2str(VALUE sym);
141
+ const(char)* rb_id2name(ID);
142
+ VALUE rb_obj_frozen_p(VALUE obj);
143
+ void rb_check_frozen(VALUE obj); // raises FrozenError when frozen
144
+ VALUE rb_obj_freeze(VALUE obj); // returns self, sets FL_FREEZE
145
+ int rb_typeddata_is_kind_of(VALUE obj, in void* data_type); // non-raising
146
+ // (check_typeddata raises; this doesn't)
147
+
148
+ // exceptions / control flow
149
+ // (rb_raise/rb_fatal never return; D has no `noreturn` attribute for extern(C))
150
+ void rb_raise(VALUE exc, const(char)* fmt, ...);
151
+ void rb_fatal(const(char)* fmt, ...);
152
+ void rb_throw(const(char)* tag, VALUE val);
153
+ void rb_throw_obj(VALUE tag, VALUE val);
154
+ void rb_exit(int status);
155
+ VALUE rb_protect(ProtectFn fn, VALUE data, int* state);
156
+ // Re-raise an existing exception object (the only way to re-raise the SAME
157
+ // exception VALUE from D — rb_raise formats a NEW exception). noreturn.
158
+ void rb_exc_raise(VALUE exc);
159
+ // After a rb_protect state != 0 (or rb_rescue/rb_ensure rescue path), the
160
+ // pending exception is fetched via rb_errinfo. Without binding it D cannot
161
+ // inspect what Ruby raised while crossing the boundary.
162
+ VALUE rb_errinfo();
163
+ void rb_set_errinfo(VALUE err);
164
+ // int rb_check_arity(int argc, int min, int max); -- inline-only in ruby/internal/intern/error.h, NOT exported; keep out of bindings
165
+
166
+ // GC
167
+ void rb_gc_register_address(VALUE* address);
168
+ void rb_gc_unregister_address(VALUE* address);
169
+ VALUE rb_gc_location(VALUE); // remap a VALUE after GC.compact moved it
170
+ void rb_gc_mark(VALUE); // conservative mark (no compaction update)
171
+ void rb_gc_mark_movable(VALUE); // mark + this slot is updated via rb_gc_location in dcompact
172
+ VALUE rb_gc_enable();
173
+ VALUE rb_gc_disable();
174
+
175
+ // introspection
176
+ int rb_check_funcall(VALUE recv, ID mid, int argc, const(VALUE)* argv);
177
+ int rb_respond_to(VALUE obj, ID mid);
178
+
179
+ // blocks / iteration (bound for PR#4 — issue #4)
180
+ // rb_block_call_func signature (ruby/internal/iterator.h):
181
+ // VALUE f(VALUE yielded_arg, VALUE callback_arg, int argc,
182
+ // const VALUE* argv, VALUE blockarg)
183
+ alias VALUE function(VALUE yielded_arg, VALUE callback_arg, int argc,
184
+ const(VALUE)* argv, VALUE blockarg) BlockCallFunc;
185
+ alias VALUE function(VALUE data) BodyFunc; // rb_rescue/rb_ensure b_proc
186
+ alias VALUE function(VALUE data2, VALUE exc) RescueFunc; // rb_rescue r_proc
187
+ VALUE rb_yield(VALUE val);
188
+ VALUE rb_yield_values(int n, ...);
189
+ VALUE rb_yield_values2(int n, const(VALUE)* argv);
190
+ VALUE rb_yield_splat(VALUE ary);
191
+ VALUE rb_yield_block(VALUE yielded_arg, VALUE callback_arg, int argc,
192
+ const(VALUE)* argv, VALUE blockarg); // as rb_block_call_func
193
+ VALUE rb_block_call(VALUE obj, ID mid, int argc, const(VALUE)* argv,
194
+ BlockCallFunc proc, VALUE data2);
195
+ int rb_block_given_p();
196
+ VALUE rb_block_proc(); // materialize the passed block as a Proc VALUE
197
+ VALUE rb_each(VALUE obj);
198
+
199
+ // structured exception handling from D (only rb_protect was bound before)
200
+ VALUE rb_rescue(BodyFunc b_proc, VALUE data1, RescueFunc r_proc, VALUE data2);
201
+ // rb_rescue2: variadic list of exception classes terminated by (VALUE)0.
202
+ // D has no C varargs forwarding — bind rb_vrescue2 (takes va_list) is not
203
+ // portable either; the common single-class case can use rb_rescue. For
204
+ // multiple classes call with the variadic ABI is NOT provided; use
205
+ // rb_protect + rb_respond_to rescue semantics at Ruby level instead.
206
+ // (Kept out deliberately: calling variadic C from D is not possible.)
207
+ VALUE rb_ensure(BodyFunc b_proc, VALUE data1, BodyFunc e_proc, VALUE data2);
208
+
209
+ // per-object methods
210
+ void rb_define_singleton_method(VALUE obj, const(char)* mid, AnyMethod func, int arity);
211
+ void rb_define_private_method(VALUE klass, const(char)* mid, AnyMethod func, int arity);
212
+ void rb_define_protected_method(VALUE klass, const(char)* mid, AnyMethod func, int arity);
213
+
214
+ // GVL (Ruby threading interop)
215
+ alias void function(void*) UnblockFunc; // rb_unblock_function_t
216
+ alias void* function(void*) NoGvlFunc; // func passed to *_without_gvl
217
+ void* rb_thread_call_without_gvl(NoGvlFunc func, void* data1,
218
+ UnblockFunc ubf, void* data2);
219
+ void* rb_thread_call_without_gvl2(NoGvlFunc func, void* data1,
220
+ UnblockFunc ubf, void* data2);
221
+ void* rb_thread_call_with_gvl(NoGvlFunc func, void* data1);
222
+ enum void* RUBY_UBF_IO = cast(void*) -1; // special UBF for blocking IO
223
+
224
+ // argument scanning (the C variadic stores into VALUE* slots)
225
+ int rb_scan_args(int argc, const(VALUE)* argv, const(char)* fmt, ...);
226
+
227
+ // struct wrapping (minimal; not used by core rudy)
228
+ alias void function(void* ptr) RUBY_DATA_FUNC_MARK;
229
+ alias void function(void* ptr) RUBY_DATA_FUNC_FREE;
230
+ VALUE rb_data_object_wrap(VALUE klass, void* ptr,
231
+ RUBY_DATA_FUNC_MARK mark, RUBY_DATA_FUNC_FREE free);
232
+ // void* rb_data_object_get(VALUE obj); -- inline-only in ruby/internal/core/rdata.h, NOT exported (use typeddata helper)
233
+
234
+ // typed-data (rb_data_object_get is an inline macro; these are real
235
+ // exported functions — see rudy/typeddata.d for a D-friendly helper)
236
+ // NOTE: RbDataType (rb_data_type_t) is defined in rudy/typeddata.d to keep
237
+ // this raw binding free of struct definitions; cast accordingly.
238
+ alias size_t function(const void*) RUBY_DATA_FUNC_DSIZE;
239
+ VALUE rb_data_typed_object_wrap(VALUE klass, void* ptr, in void* data_type);
240
+ void* rb_check_typeddata(VALUE obj, in void* data_type);
241
+ // NB: the C prototype is (VALUE klass, size_t size, const rb_data_type_t*)
242
+ // — the pre-audit binding dropped `size` and silently worked through
243
+ // register reuse (finding #21). Signature now matches the header.
244
+ void* rb_data_typed_object_zalloc(VALUE klass, size_t size, in void* data_type);
245
+
246
+ // ---------------------------------------------------------------- globals
247
+
248
+ extern __gshared VALUE rb_cObject;
249
+ extern __gshared VALUE rb_cInteger;
250
+ extern __gshared VALUE rb_cFloat;
251
+ extern __gshared VALUE rb_cString;
252
+ extern __gshared VALUE rb_cArray;
253
+ extern __gshared VALUE rb_cHash;
254
+ extern __gshared VALUE rb_mKernel;
255
+
256
+ extern __gshared VALUE rb_eException;
257
+ extern __gshared VALUE rb_eStandardError;
258
+ extern __gshared VALUE rb_eRuntimeError;
259
+ extern __gshared VALUE rb_eTypeError;
260
+ extern __gshared VALUE rb_eArgError;
261
+ extern __gshared VALUE rb_eRangeError;
262
+ extern __gshared VALUE rb_eNameError;
263
+ extern __gshared VALUE rb_eNoMethodError;
264
+ extern __gshared VALUE rb_eNotImpError;
265
+ extern __gshared VALUE rb_eStopIteration;
266
+ extern __gshared VALUE rb_eKeyError;
267
+ extern __gshared VALUE rb_eIndexError;
268
+ extern __gshared VALUE rb_eLoadError;
269
+ extern __gshared VALUE rb_eSystemExit;
270
+ extern __gshared VALUE rb_eSysStackError; // Ruby 4 exports rb_eSysStackError (rb_eSystemStackError never existed)
271
+ extern __gshared VALUE rb_eZeroDivError;
272
+ extern __gshared VALUE rb_eLocalJumpError; // yield with no block / blk.call when none given
273
+ extern __gshared VALUE rb_eFrozenError; // rb_check_frozen on a frozen object
274
+
275
+ // type tags (ruby_value_type) — subset rudy needs
276
+ enum int RUBY_T_NONE = 0x00;
277
+ enum int RUBY_T_OBJECT = 0x01;
278
+ enum int RUBY_T_CLASS = 0x02;
279
+ enum int RUBY_T_MODULE = 0x03;
280
+ enum int RUBY_T_FLOAT = 0x0b;
281
+ enum int RUBY_T_STRING = 0x0d;
282
+ enum int RUBY_T_SYMBOL = 0x0f;
283
+ enum int RUBY_T_ARRAY = 0x11;
284
+ enum int RUBY_T_HASH = 0x13;
285
+ enum int RUBY_T_TRUE = 0x20;
286
+ enum int RUBY_T_FALSE = 0x21;
287
+ enum int RUBY_T_NIL = 0x22;
288
+ enum int RUBY_T_DATA = 0x26;
289
+ enum int RUBY_T_COMPLEX = 0x2a;
290
+
291
+ // C `long` on this platform (LP64 = 64 bits). Keeps to_ruby_value/d_type
292
+ // templates portable to 32-bit builds.
293
+ version (X86_64) {
294
+ alias long C_long;
295
+ alias ulong C_ulong;
296
+ } else version (X86) {
297
+ alias int C_long;
298
+ alias uint C_ulong;
299
+ } else {
300
+ alias long C_long;
301
+ alias ulong C_ulong;
302
+ }
303
+ alias long C_longlong;
304
+ alias ulong C_ulonglong;
305
+
306
+