Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"installDir": "${sourceDir}/install/${presetName}",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
"CMAKE_CXX_FLAGS": "-Wall -ftemplate-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden"
"CMAKE_CXX_FLAGS": "-Wall -Wno-array-bounds -ftemplate-backtrace-limit=0 -fvisibility=hidden -fvisibility-inlines-hidden"
}
},
{
Expand Down Expand Up @@ -81,7 +81,7 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_CXX_COMPILER": "g++.exe",
"CMAKE_CXX_FLAGS": "-Wall -ftemplate-backtrace-limit=0 -Wa,-mbig-obj -fvisibility=hidden -fvisibility-inlines-hidden",
"CMAKE_CXX_FLAGS": "-Wall -Wno-array-bounds -ftemplate-backtrace-limit=0 -Wa,-mbig-obj -fvisibility=hidden -fvisibility-inlines-hidden",
"CMAKE_CXX_FLAGS_DEBUG": "-g3 -Og -fno-omit-frame-pointer -fno-inline -gsplit-dwarf"
},
"condition": {
Expand All @@ -97,7 +97,7 @@
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_CXX_COMPILER": "g++.exe",
"CMAKE_CXX_FLAGS": "-Wall -ftemplate-backtrace-limit=0 -Wa,-mbig-obj -fvisibility=hidden -fvisibility-inlines-hidden",
"CMAKE_CXX_FLAGS": "-Wall -Wno-array-bounds -ftemplate-backtrace-limit=0 -Wa,-mbig-obj -fvisibility=hidden -fvisibility-inlines-hidden",
"CMAKE_CXX_FLAGS_RELEASE": "-O3 -DNDEBUG",
"CMAKE_SHARED_LINKER_FLAGS_RELEASE": "-Wl,--exclude-all-symbols"
},
Expand Down
Empty file modified bin/rice-doc.rb
100644 → 100755
Empty file.
Empty file modified bin/rice-rbs.rb
100644 → 100755
Empty file.
22 changes: 20 additions & 2 deletions docs/packaging/build_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ Rice extensions requires several compiler settings to be set. These are captured
For Clang and GCC:

```bash
-std=c++17 -Wa,-mbig-obj -ftemplate-backtrace-limit=0
-std=c++17 -Wno-array-bounds -Wa,-mbig-obj -ftemplate-backtrace-limit=0
```

For MINGW:

```bash
-std=c++17, -Wa,-mbig-obj
-std=c++17 -Wno-array-bounds -Wa,-mbig-obj
```

For Microsoft Visual C++ and Windows Clang:
Expand Down Expand Up @@ -44,6 +44,14 @@ By default, MSVC does not update the `__cplusplus` preprocessor macro to reflect

For Visual C++, the default exception [model](https://learn.microsoft.com/en-us/cpp/build/reference/eh-exception-handling-model?view=msvc-170) setting of `/EHsc` crashes Ruby when calling longjmp with optimizations enabled (/O2). Therefore you must `/EHs` instead.

### Array Bounds Warning

g++ 15 produces false positive `-Warray-bounds` warnings when inlining through Ruby's `RSTRING` macro (in `ruby/internal/core/rstring.h`). This is not a bug in Rice or Ruby. The warning is harmless but becomes a build failure if `-Werror` is enabled. Rice suppresses it with:

```bash
-Wno-array-bounds
```

### Template Backtrace

For g++, you must set `-ftemplate-backtrace-limit=0` to avoid compilation errors.
Expand Down Expand Up @@ -125,6 +133,16 @@ $CXXFLAGS << " /GL"
$LDFLAGS << " /LTCG"
```

### GCC 15 LTO Assembler Bug

g++ 15.2.1 with binutils 2.45.1 (shipped in Fedora 43) can trigger an internal assembler segfault when LTO is enabled. If you hit this, disable LTO as a workaround:

```ruby
$CXXFLAGS += " -fno-lto"
```

Or with CMake, set `CMAKE_INTERPROCEDURAL_OPTIMIZATION` to `OFF`. This only affects the specific GCC/binutils version combination and should be resolved in a future binutils release.

### Debug Symbol Splitting (GCC/Clang)

For debug builds with GCC or Clang, consider using `-gsplit-dwarf` to separate debug information into `.dwo` files. This keeps the main binary smaller while preserving full debug capability:
Expand Down
28 changes: 17 additions & 11 deletions include/rice/rice.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -859,6 +859,7 @@ namespace Rice::detail
// ========= Anchor.hpp =========

#include <ruby.h>
#include <ruby/vm.h>

namespace Rice
{
Expand Down Expand Up @@ -893,7 +894,6 @@ namespace Rice
VALUE get() const;

private:
static void disable(VALUE);
static void registerExitHandler();

inline static bool enabled_ = true;
Expand Down Expand Up @@ -1667,23 +1667,28 @@ namespace Rice
{
namespace detail
{
inline Anchor::Anchor(VALUE value) : value_(value)
inline Anchor::Anchor(VALUE value)
{
// rb_gc_register_address() can trigger GC, so we must register the
// empty this->value_ slot before storing a heap VALUE in it.
// RB_GC_GUARD(value) keeps the ctor argument alive through the end of
// this method until the registered slot has been updated.
if (!RB_SPECIAL_CONST_P(value))
{
Anchor::registerExitHandler();
detail::protect(rb_gc_register_address, &this->value_);
this->registered_ = true;
}
this->value_ = value;
RB_GC_GUARD(value);
}

inline Anchor::~Anchor()
{
if (Anchor::enabled_ && this->registered_)
{
detail::protect(rb_gc_unregister_address, &this->value_);
rb_gc_unregister_address(&this->value_);
}
// Ruby auto detects VALUEs in the stack, so make sure up in case this object is on the stack
this->registered_ = false;
this->value_ = Qnil;
}
Expand All @@ -1693,17 +1698,18 @@ namespace Rice
return this->value_;
}

// This will be called by ruby at exit - we want to disable further unregistering
inline void Anchor::disable(VALUE)
{
Anchor::enabled_ = false;
}

inline void Anchor::registerExitHandler()
{
if (!Anchor::exitHandlerRegistered_)
{
detail::protect(rb_set_end_proc, &Anchor::disable, Qnil);
// Use ruby_vm_at_exit which fires AFTER the VM is destroyed,
// not rb_set_end_proc which fires BEFORE. rb_set_end_proc
// runs as an end_proc in LIFO order alongside at_exit blocks,
// so its timing depends on require order — if the extension
// loads after minitest/autorun, the disable callback runs
// before tests execute, causing Anchor destructors to skip
// rb_gc_unregister_address and leave dangling root pointers.
ruby_vm_at_exit([](ruby_vm_t*) { Anchor::enabled_ = false; });
Anchor::exitHandlerRegistered_ = true;
}
}
Expand Down
4 changes: 2 additions & 2 deletions lib/mkmf-rice.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ def cpp_command(outfile, opt="")
$CXXFLAGS += " /std:c++#{std} /EHs /permissive- /bigobj /utf-8 /Zc:__cplusplus"
$CPPFLAGS += " -D_ALLOW_KEYWORD_MACROS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_NONSTDC_NO_DEPRECATE"
elsif IS_MINGW
$CXXFLAGS += " -std=c++#{std} -Wa,-mbig-obj"
$CXXFLAGS += " -std=c++#{std} -Wa,-mbig-obj -Wno-array-bounds"
else
$CXXFLAGS += " -std=c++#{std}"
$CXXFLAGS += " -std=c++#{std} -Wno-array-bounds"
end

# Rice needs to include its header. Let's setup the include path
Expand Down
2 changes: 1 addition & 1 deletion rice/detail/Anchor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define Rice__detail__Anchor__hpp_

#include <ruby.h>
#include <ruby/vm.h>

namespace Rice
{
Expand Down Expand Up @@ -36,7 +37,6 @@ namespace Rice
VALUE get() const;

private:
static void disable(VALUE);
static void registerExitHandler();

inline static bool enabled_ = true;
Expand Down
26 changes: 16 additions & 10 deletions rice/detail/Anchor.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,28 @@ namespace Rice
{
namespace detail
{
inline Anchor::Anchor(VALUE value) : value_(value)
inline Anchor::Anchor(VALUE value)
{
// rb_gc_register_address() can trigger GC, so we must register the
// empty this->value_ slot before storing a heap VALUE in it.
// RB_GC_GUARD(value) keeps the ctor argument alive through the end of
// this method until the registered slot has been updated.
if (!RB_SPECIAL_CONST_P(value))
{
Anchor::registerExitHandler();
detail::protect(rb_gc_register_address, &this->value_);
this->registered_ = true;
}
this->value_ = value;
RB_GC_GUARD(value);
}

inline Anchor::~Anchor()
{
if (Anchor::enabled_ && this->registered_)
{
detail::protect(rb_gc_unregister_address, &this->value_);
rb_gc_unregister_address(&this->value_);
}
// Ruby auto detects VALUEs in the stack, so make sure up in case this object is on the stack
this->registered_ = false;
this->value_ = Qnil;
}
Expand All @@ -28,17 +33,18 @@ namespace Rice
return this->value_;
}

// This will be called by ruby at exit - we want to disable further unregistering
inline void Anchor::disable(VALUE)
{
Anchor::enabled_ = false;
}

inline void Anchor::registerExitHandler()
{
if (!Anchor::exitHandlerRegistered_)
{
detail::protect(rb_set_end_proc, &Anchor::disable, Qnil);
// Use ruby_vm_at_exit which fires AFTER the VM is destroyed,
// not rb_set_end_proc which fires BEFORE. rb_set_end_proc
// runs as an end_proc in LIFO order alongside at_exit blocks,
// so its timing depends on require order — if the extension
// loads after minitest/autorun, the disable callback runs
// before tests execute, causing Anchor destructors to skip
// rb_gc_unregister_address and leave dangling root pointers.
ruby_vm_at_exit([](ruby_vm_t*) { Anchor::enabled_ = false; });
Anchor::exitHandlerRegistered_ = true;
}
}
Expand Down
Loading