Skip to content

Remove back pointer from Real to VALUE#344

Merged
mrkn merged 1 commit into
ruby:masterfrom
tompng:reduce_real_obj_reverse_reference
Jul 6, 2025
Merged

Remove back pointer from Real to VALUE#344
mrkn merged 1 commit into
ruby:masterfrom
tompng:reduce_real_obj_reverse_reference

Conversation

@tompng

@tompng tompng commented Jun 8, 2025

Copy link
Copy Markdown
Member

Fixes #340
((Real*)x)->obj, a reference from DATA to VALUE, will be garbled after gc compaction. Real should not hold a reference to VALUE.

Most diffs are just a line-by-line conversion.
Introduce BDVALUE, a pair of BigDecimal VALUE and struct Real, instead of relying on VALUE reference from struct Real.
BDVALUE_OR_NIL is an nullable version of BDVALUE.

// BEFORE

Real* x = GetVpValueWithPrec(v, -1, must);
if (x != NULL) { do_something(x); return x->obj; }

// AFTER

BDVALUE x = GetVpValueWithPrec(v, -1);
do_something(x.real); return x.bigdecimal;

NULLABLE_BDVALUE y = GetVpValueWithPrecMust(v, -1);
if (y.real_or_null != NULL) {
  BDVALUE z = bdvalue_nonnullable(y);
  do_something(z.real); return z.bigdecimal;
}

This is to reduce the possibility of embedding new bugs in this pull request.

GUARD_OBJ is not so important after this change. We can remove most of them and replace it to few RB_GC_GUARD.
But in this pull request, these are left as is, to make review easier.

@tompng tompng force-pushed the reduce_real_obj_reverse_reference branch 4 times, most recently from d30582e to ecac757 Compare June 10, 2025 16:26
@tompng tompng force-pushed the reduce_real_obj_reverse_reference branch from ecac757 to 86a84a6 Compare June 26, 2025 15:07
@tompng tompng changed the title Reduce using ((*Real)x)->obj Remove back pointer from Real to VALUE Jun 26, 2025
@tompng tompng marked this pull request as ready for review June 26, 2025 15:21
@tompng tompng force-pushed the reduce_real_obj_reverse_reference branch from 86a84a6 to e75cb08 Compare June 26, 2025 15:43

@tompng tompng left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most diffs are just a line-by-line conversion.
I commented where it is not a line-to-line conversion.

{
ENTER(1);
Real *p;
BDVALUE v;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real *p Real *pv Real *vp is renamed to v in this pull request.
I think p represents that it's a pointer.

bdvalue_nullable(BDVALUE v)
{
return (NULLABLE_BDVALUE) { v.bigdecimal, v.real };
}

@tompng tompng Jun 30, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BDVALUE NULLABLE_BDVALUE bdvalue_nonnullable bdvalue_nullable are introduced to reduce the possibility of bugs like this:

- Real *a = GetVpFooBarOrNull(x);
- if (a != NULL) return Baz(a);
+ BDVALUE a = GetFooBarOrNull(x);
+ if (a != NULL) return Baz(a.real); // Should be `if (a.real != NULL)`, but hard to detect

Using NULLABLE_BDVALUE:

BDVALUE a = GetFooBarOrNull(x); // Bug. Compile error
NULLABLE_BDVALUE a = GetFooBarOrNull(x); // OK
if (a != NULL) return Baz(a); // Bug. Compile error
if (a != NULL) return Baz(a.real); // Bug. Compile error
if (a != NULL) return Baz(a.real_or_null); // Bug. We can find this bug by grep
if (a.real_or_null != NULL) return Baz(a.real_or_null); // OK

The only dangerous parts are:

  • Where accessing unassigned BDVALUE
  • Where using v.real_or_null v.bigdecimal_or_nil
  • Where using bdvalue_nonnullable(x)

GetBDValueWithPrecMust(VALUE v, long prec)
{
return bdvalue_nonnullable(GetBDValueWithPrecInternal(v, prec, 1));
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real *real = GetVpValueWithPrec(v, prec, must=1);
Real *real_or_null = GetVpValueWithPrec(v, prec, must=0);
Real *real = GetVpValue(v, must=1)
Real *real_or_null = GetVpValue(v, must=0)

// ↓

BDVALUE x = GetVpValueWithPrecMust(v, prec);
NULLABLE_BDVALUE y = GetVpValueWithPrec(v, prec);
BDVALUE x = GetVpValueMust(v);
NULLABLE_BDVALUE y = GetVpValue(v);
// x.real, x.bigdecimal: always non-null/non-nil (except unassigned case)
// y.real_or_null, y.bigdecimal_or_nil

VP_EXPORT Real *
VpNewRbClass(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception)
static NULLABLE_BDVALUE
CreateFromString(size_t mx, const char *str, VALUE klass, bool strict_p, bool raise_exception)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VpNewRbClass, VpCreateRbObjectCreateFromString
The important part of this function is that it creates from string. The original function name doesn't represent it.
Merged these two functions into one because I couldn't think of a good name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good renaming.

b = bdvalue_nonnullable(b2);
}

if (!b) return DoSomeOne(self,r,'+');

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to else clause above

static Real *
rbd_reallocate_struct(Real *real, size_t const internal_digits)
static BDVALUE
rbd_reallocate_struct(BDVALUE value, size_t const internal_digits)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Take a pair of BigDecimal object and Real*, returns a pair of BigDecimal object and reallocated Real*

rbd_allocate_struct_zero_limited_wrap(int sign, size_t const digits)
{
return rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true);
return bdvalue_nonnullable(rbd_allocate_struct_zero_wrap_klass(rb_cBigDecimal, sign, digits, true));

@tompng tompng Jun 30, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rbd_allocate_struct_zero_wrap_klass can return null, but bigdecimal.c is/was assuming that the value is non-nullable.
This change just makes the fact clear.

typedef struct {
VALUE bigdecimal;
Real *real;
} BDVALUE;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BDVALUE: BigDecimal VALUE (+ Real*)
A pair of VALUE(instance of BigDecimal) and Real*.


static VALUE
VpCheckGetValue(Real *p)
CheckGetValue(BDVALUE v)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Takes BDVALUE, checks exception and returns bigdecimal.

Real *new_real = (Real *)ruby_xrealloc(value.real, size);
new_real->MaxPrec = internal_digits;
if (obj) {
new_real->obj = 0;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real does not have obj field anymore. Set/Reset/Validate real->obj is removed.

@mrkn mrkn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left two questions.

((Real*)x)->obj, a reference from DATA to VALUE, will be garbled after gc compaction.
Real should not hold a reference to VALUE.
@mrkn mrkn force-pushed the reduce_real_obj_reverse_reference branch from e75cb08 to 489cd85 Compare July 4, 2025 07:46
@mrkn mrkn merged commit b6d2987 into ruby:master Jul 6, 2025
78 checks passed
@tompng tompng deleted the reduce_real_obj_reverse_reference branch July 6, 2025 05:07
This was referenced Jul 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError/ArgumentError can't be coerced into BigDecimal or Segmentation fault after gc compaction

2 participants