From feaf7943c206e6d4f4b037828c8f24b64fa3624c Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 9 Jun 2026 15:39:14 -0400 Subject: [PATCH 1/3] Extract out `ruby_compat.h` --- ext/rubydex/graph.c | 1 + ext/rubydex/handle.h | 2 +- ext/rubydex/ruby_compat.h | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 ext/rubydex/ruby_compat.h diff --git a/ext/rubydex/graph.c b/ext/rubydex/graph.c index 149263399..79b6a1c5f 100644 --- a/ext/rubydex/graph.c +++ b/ext/rubydex/graph.c @@ -5,6 +5,7 @@ #include "location.h" #include "reference.h" #include "ruby/internal/globals.h" +#include "ruby_compat.h" #include "rustbindings.h" #include "utils.h" diff --git a/ext/rubydex/handle.h b/ext/rubydex/handle.h index 427608223..f6f8b4ca1 100644 --- a/ext/rubydex/handle.h +++ b/ext/rubydex/handle.h @@ -1,7 +1,7 @@ #ifndef RUBYDEX_HANDLE_H #define RUBYDEX_HANDLE_H -#include "ruby.h" +#include "ruby_compat.h" typedef struct { VALUE graph_obj; // Ruby Graph object to keep it alive diff --git a/ext/rubydex/ruby_compat.h b/ext/rubydex/ruby_compat.h new file mode 100644 index 000000000..8481087ac --- /dev/null +++ b/ext/rubydex/ruby_compat.h @@ -0,0 +1,6 @@ +#ifndef RUBYDEX_RUBY_COMPAT_H +#define RUBYDEX_RUBY_COMPAT_H + +#include "ruby.h" + +#endif // RUBYDEX_RUBY_COMPAT_H From fc86de00bd26e8954db3fdfb417094caed7ceede Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 9 Jun 2026 15:39:54 -0400 Subject: [PATCH 2/3] Use embedded TypedData allocations --- ext/rubydex/extconf.rb | 6 ++++++ ext/rubydex/graph.c | 7 ++++++- ext/rubydex/handle.h | 7 ++++++- ext/rubydex/ruby_compat.h | 11 +++++++++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/ext/rubydex/extconf.rb b/ext/rubydex/extconf.rb index c17f5de15..47c8c33ca 100644 --- a/ext/rubydex/extconf.rb +++ b/ext/rubydex/extconf.rb @@ -39,6 +39,12 @@ append_cflags("-Werror=unused-but-set-variable") append_cflags("-Werror=implicit-function-declaration") +# Compiles a minimal program to test if the `RUBY_TYPED_EMBEDDABLE` C constant exists ( Ruby 3.3 or newer). +# * If it exists, the generated makefile will set a preprocessor macro: `#define HAVE_CONST_RUBY_TYPED_EMBEDDABLE 1` +# * else: `#define HAVE_CONST_RUBY_TYPED_EMBEDDABLE 0` +# See https://ruby-doc.org/3.3.4/stdlibs/mkmf/MakeMakefile.html#method-i-have_const +have_const("RUBY_TYPED_EMBEDDABLE", "ruby.h") + # There's an error on Windows with function pointer types not matching. This has been fixed and backported in Ruby, but # it seems that RubyInstaller sometimes picks an older patch version on CI and it breaks compilation. This isn't # actually a problem, so we're ignoring it temporarily only on Windows diff --git a/ext/rubydex/graph.c b/ext/rubydex/graph.c index 79b6a1c5f..4a16afaf0 100644 --- a/ext/rubydex/graph.c +++ b/ext/rubydex/graph.c @@ -45,8 +45,13 @@ static void graph_free(void *ptr) { // let the Rust side drop the Graph struct internally. rdx_graph_drop(ptr); +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + // The storage is embedded in the TypedData Ruby object itself. + // Don't free `ptr`, because the GC will do that for us. +#else // Free the TypeData Ruby object itself xfree(ptr); +#endif } } @@ -60,7 +65,7 @@ const rb_data_type_t graph_type = { }, .parent = NULL, .data = NULL, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, }; // Custom allocator for the Graph class. diff --git a/ext/rubydex/handle.h b/ext/rubydex/handle.h index f6f8b4ca1..f54fc15f9 100644 --- a/ext/rubydex/handle.h +++ b/ext/rubydex/handle.h @@ -17,7 +17,12 @@ static void handle_mark(void *ptr) { static void handle_free(void *ptr) { if (ptr) { +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE +// The storage is embedded in the TypedData Ruby object itself. +// Don't free `ptr`, because the GC will do that for us. +#else xfree(ptr); +#endif } } @@ -31,7 +36,7 @@ static const rb_data_type_t handle_type = { }, .parent = NULL, .data = NULL, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, + .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_EMBEDDABLE, }; static VALUE rdxr_handle_alloc(VALUE klass) { diff --git a/ext/rubydex/ruby_compat.h b/ext/rubydex/ruby_compat.h index 8481087ac..e0f5255a9 100644 --- a/ext/rubydex/ruby_compat.h +++ b/ext/rubydex/ruby_compat.h @@ -3,4 +3,15 @@ #include "ruby.h" +#ifdef RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +#else +# ifdef HAVE_CONST_RUBY_TYPED_EMBEDDABLE +# define RUBY_TYPED_EMBEDDABLE RUBY_TYPED_EMBEDDABLE +# define HAVE_RUBY_TYPED_EMBEDDABLE 1 +# else +# define RUBY_TYPED_EMBEDDABLE 0 +# endif +#endif + #endif // RUBYDEX_RUBY_COMPAT_H From d14b96b75f3f77844b7c698bc4c0ec4b2fb7ab6a Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Wed, 10 Jun 2026 16:12:18 -0400 Subject: [PATCH 3/3] Use `RUBY_DEFAULT_FREE` in embedded case Co-authored-by: John Hawthorn --- ext/rubydex/handle.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ext/rubydex/handle.h b/ext/rubydex/handle.h index f54fc15f9..1f5a26f3e 100644 --- a/ext/rubydex/handle.h +++ b/ext/rubydex/handle.h @@ -15,22 +15,23 @@ static void handle_mark(void *ptr) { } } +#ifndef HAVE_RUBY_TYPED_EMBEDDABLE static void handle_free(void *ptr) { if (ptr) { -#ifdef HAVE_RUBY_TYPED_EMBEDDABLE -// The storage is embedded in the TypedData Ruby object itself. -// Don't free `ptr`, because the GC will do that for us. -#else xfree(ptr); -#endif } } +#endif static const rb_data_type_t handle_type = { .wrap_struct_name = "RubydexHandle", .function = { .dmark = handle_mark, +#ifdef HAVE_RUBY_TYPED_EMBEDDABLE + .dfree = RUBY_DEFAULT_FREE, +#else .dfree = handle_free, +#endif .dsize = NULL, .dcompact = NULL, },