FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Merge pull request #7363 from takumin/nan-identity · mruby/mruby@72ff8d3 · GitHub

/ mruby Public

Commit 72ff8d3

Browse files
authored
Merge pull request #7363 from takumin/nan-identity
etc.c: give every NaN an identity of its own
2 parents a582557 + 990fe5a commit 72ff8d3

11 files changed

Lines changed: 244 additions & 21 deletions

File tree

‎include/mruby.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,15 @@ struct mrb_state {
355355

356356
mrb_gc gc;
357357

358+
#if !defined(MRB_NO_FLOAT) && !defined(MRB_WORD_BOXING)
359+
/* Counts the NaNs made so far. A NaN is equal to nothing at all, its own
360+
operand included, so a container searching for one has only the object to
361+
go by; where a Float is a value and not an object, the count is what tells
362+
two of them apart. See the comment above `MRB_NAN_SERIAL_MAX` in
363+
`mruby/value.h`. */
364+
uint64_t nan_serial;
365+
#endif
366+
358367
mrb_bool bootstrapping;
359368

360369
#ifndef MRB_NO_METHOD_CACHE

‎include/mruby/boxing_nan.h‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,14 @@ mrb_nan_boxing_value_float(mrb_value v)
6262
uint64_t uval; \
6363
} float_uint_union; \
6464
if ((v) != (v)) { /* NaN */ \
65-
float_uint_union.uval = 0x7ff8000000000000UL; \
65+
/* A NaN is equal to nothing at all, its own operand included, so `==`
66+
can never find one and a container searching for the NaN it holds has
67+
only the object to go by. Each NaN takes a count of its own in the
68+
payload, which the tag space leaves free: the tag saying which value
69+
this is begins at bit 48, and everything below is the NaN's to spend.
70+
`mrb_float()` reads any of these back as a NaN. */ \
71+
float_uint_union.uval = 0x7ff8000000000000UL | \
72+
(uint64_t)(mrb_nan_serial_next(mrb) & MRB_NAN_SERIAL_MAX); \
6673
} \
6774
else { \
6875
float_uint_union.fval = (v); \

‎include/mruby/boxing_no.h‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,17 @@ typedef struct mrb_value {
4949
#define SET_INT_VALUE(mrb,r,n) BOXNO_SET_VALUE(r, MRB_TT_INTEGER, value.i, (n))
5050
#define SET_FIXNUM_VALUE(r,n) BOXNO_SET_VALUE(r, MRB_TT_INTEGER, value.i, (n))
5151
#ifndef MRB_NO_FLOAT
52-
#define SET_FLOAT_VALUE(mrb,r,v) BOXNO_SET_VALUE(r, MRB_TT_FLOAT, value.f, (v))
52+
/* A NaN is equal to nothing at all, its own operand included, so `==` can
53+
never find one and a container searching for the NaN it holds has only the
54+
object to go by. There is no object here, a Float being a value, so each NaN
55+
takes a count of its own in the payload, which no arithmetic reads;
56+
`mrb_obj_eq()` compares what a Float holds bit for bit so that a NaN is the
57+
same object as itself and no other. */
58+
#define SET_FLOAT_VALUE(mrb,r,v) do { \
59+
mrb_float boxno_f = (v); \
60+
if (boxno_f != boxno_f) boxno_f = mrb_nan_serialize(boxno_f, mrb_nan_serial_next(mrb)); \
61+
BOXNO_SET_VALUE(r, MRB_TT_FLOAT, value.f, boxno_f); \
62+
} while (0)
5363
#endif
5464
#define SET_SYM_VALUE(r,v) BOXNO_SET_VALUE(r, MRB_TT_SYMBOL, value.sym, (v))
5565
#define SET_OBJ_VALUE(r,v) BOXNO_SET_VALUE(r, (((struct RObject*)(v))->tt), value.p, (v))

‎include/mruby/boxing_word.h‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ enum mrb_special_consts {
129129
*
130130
* 64-bit word with inline float32 (MRB_USE_FLOAT32):
131131
* float : ...FFFF FF10 (float32 shifted left by 2)
132-
* (other values same as above)
132+
* (other values same as above; a NaN is heap-allocated as RFloat)
133133
*
134134
* word boxing without inline float (MRB_WORDBOX_NO_INLINE_FLOAT):
135135
* nil : ...0000 0000 (all bits are 0)
@@ -211,10 +211,9 @@ mrb_integer_func(mrb_value o) {
211211
#ifndef MRB_NO_FLOAT
212212
#ifdef MRB_WORDBOX_NO_INLINE_FLOAT
213213
#define mrb_float_p(o) WORDBOX_OBJ_TYPE_P(o, FLOAT)
214-
#elif defined(MRB_USE_FLOAT32) && defined(MRB_64BIT)
215-
#define mrb_float_p(o) WORDBOX_SHIFT_VALUE_P(o, FLOAT)
216214
#else
217-
/* rotation encoding: most floats inline, edge cases on heap */
215+
/* most floats inline; a NaN is always on the heap, and the rotation encoding
216+
sends its edge cases there too */
218217
#define mrb_float_p(o) (WORDBOX_SHIFT_VALUE_P(o, FLOAT) || WORDBOX_OBJ_TYPE_P(o, FLOAT))
219218
#endif
220219
#else

‎include/mruby/value.h‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,63 @@ MRB_API double mrb_float_read(const char *p, char **endp);
113113
#else
114114
typedef double mrb_float;
115115
#endif
116+
117+
#ifndef MRB_WORD_BOXING
118+
/* A NaN is equal to nothing at all, its own operand included, so `==` can
119+
never find one and a container searching for the NaN it holds has only the
120+
object to go by. Where a Float is a value and not an object, which is every
121+
boxing but `boxing_word.h`, there is no object to be had: what tells one NaN
122+
from another has to be carried by the NaN itself, and the only room for it
123+
is the payload, which no arithmetic reads. Each NaN made takes a count
124+
there.
125+
126+
The count is given every bit the payload can spare, which is what puts a
127+
wrap out of reach: 51 bits where the boxing leaves that many, 48 where the
128+
nan-boxing tag begins. Two NaNs made that far apart do hold the same bits.
129+
`MRB_USE_FLOAT32` is the one build where the bound is a near one, its
130+
payload being 22 bits and nothing more. `boxing_word.h` needs no count at
131+
all: a NaN is an object there, and two objects are never one. */
132+
#ifdef MRB_NAN_BOXING
133+
/* bit 48 is where the tag saying which nan-boxed value this is begins */
134+
# define MRB_NAN_SERIAL_MAX UINT64_C(0xffffffffffff)
135+
#elif defined(MRB_USE_FLOAT32)
136+
typedef uint32_t mrb_float_bits;
137+
# define MRB_NAN_SERIAL_MAX 0x3fffff /* the whole payload under the quiet bit */
138+
# define MRB_NAN_QUIET_BIT 0x400000
139+
#else
140+
typedef uint64_t mrb_float_bits;
141+
# define MRB_NAN_SERIAL_MAX UINT64_C(0x7ffffffffffff)
142+
# define MRB_NAN_QUIET_BIT UINT64_C(0x8000000000000)
116143
#endif
117144

145+
/* The count lives in `mrb_state`, whose layout is not known here: this header
146+
is where `mrb_float_value()` expands the boxing's own float macro, and that
147+
is above the definition of the struct. */
148+
MRB_API uint64_t mrb_nan_serial_next(mrb_state *mrb);
149+
150+
#ifndef MRB_NAN_BOXING
151+
/* `boxing_nan.h` writes the count into a pattern it builds itself, so what
152+
follows is for the boxing that has a whole Float to put it in. */
153+
static inline mrb_float
154+
mrb_nan_serialize(mrb_float f, uint64_t n)
155+
{
156+
union { mrb_float f; mrb_float_bits u; } x;
157+
158+
x.f = f;
159+
/* The quiet bit is set as well because that is what keeps the pattern a
160+
NaN. A signaling NaN can carry every bit it has inside the field the
161+
count takes, and clearing those for a count of zero would leave the
162+
exponent alone, which is an infinity. Quieting is what arithmetic reading
163+
such a NaN does with it anyway. */
164+
x.u = (x.u & ~(mrb_float_bits)MRB_NAN_SERIAL_MAX) |
165+
(mrb_float_bits)MRB_NAN_QUIET_BIT |
166+
(mrb_float_bits)(n & MRB_NAN_SERIAL_MAX);
167+
return x.f;
168+
}
169+
#endif /* MRB_NAN_BOXING */
170+
#endif /* MRB_WORD_BOXING */
171+
#endif /* MRB_NO_FLOAT */
172+
118173
#if defined _MSC_VER && _MSC_VER < 1900
119174
MRB_API int mrb_msvc_vsnprintf(char *s, size_t n, const char *format, va_list arg);
120175
MRB_API int mrb_msvc_snprintf(char *s, size_t n, const char *format, ...);

‎mrbgems/mruby-array-ext/test/array.rb‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,31 @@ def assert_repeated_combination(exp, receiver, *args)
8686
assert_equal [["student", "sam"], ["teacher", "matz"]], b.uniq { |s| s.first }
8787
end
8888

89+
assert("Array#uniq, Array#- and Array#include? with a NaN") do
90+
# A NaN is equal to no value, its own included, so none of these can find one
91+
# by what it is equal to; they search for the object instead, and every NaN
92+
# made is one of its own, so that two made apart are two objects.
93+
skip unless Object.const_defined?(:Float)
94+
z = [0.0][0]
95+
a = z / z
96+
b = z / z
97+
98+
assert_equal 1, [a, a].uniq.size
99+
assert_equal 2, [a, b].uniq.size
100+
assert_equal 0, ([a] - [a]).size
101+
assert_equal 1, ([a] - [b]).size
102+
assert_equal 1, ([a] & [a]).size
103+
assert_equal 0, ([a] & [b]).size
104+
assert_true [a].include?(a)
105+
assert_false [a].include?(b)
106+
assert_true [a].member?(a)
107+
108+
# a Float that is equal to itself is found by what it is equal to
109+
x = z + 1.5
110+
y = z + 1.5
111+
assert_true [x].include?(y)
112+
end
113+
89114
assert("Array#-") do
90115
# Test basic functionality
91116
a = [1, 2, 3, 1]

‎mrbgems/mruby-enum-ext/test/enum.rb‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,3 +343,17 @@ def each; yield 1; yield 2; yield 2; end
343343
end.new
344344
assert_equal 1, one.count(never)
345345
end
346+
347+
assert("Array#count with a NaN") do
348+
# A NaN is equal to no value, its own included, so `count` cannot find one by
349+
# what it is equal to; it searches for the object, and every NaN made is one
350+
# of its own, so that two made apart are two objects.
351+
skip unless Object.const_defined?(:Float)
352+
z = [0.0][0]
353+
a = z / z
354+
b = z / z
355+
356+
assert_equal 1, [a].count(a)
357+
assert_equal 0, [a].count(b)
358+
assert_equal 2, [a, a].count(a)
359+
end

‎mrbgems/mruby-pack/test/pack.rb‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ def assert_pack tmpl, packed, unpacked
162162
end
163163
end
164164

165+
assert 'unpack a NaN that signals' do
166+
skip unless Object.const_defined?(:Float)
167+
# A NaN that signals has every bit it is set in the low payload, which is
168+
# where a build that keeps no object for a NaN writes what tells one from
169+
# another. Reading one back has to leave a NaN rather than the infinity an
170+
# empty payload under that exponent would be, and two reads have to leave
171+
# two objects, as they do for a NaN made any other way.
172+
s = "\x01\x00\x00\x00\x00\x00\xf0\x7f"
173+
a = s.unpack1("E")
174+
b = s.unpack1("E")
175+
176+
assert_predicate(a, :nan?)
177+
assert_predicate(b, :nan?)
178+
assert_false(a.equal?(b))
179+
end
180+
165181
assert 'pack/unpack "i"' do
166182
int_size = [0].pack('i').size
167183
raise "pack('i').size is too small (#{int_size})" if int_size < 2

‎src/etc.c‎

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ mrb_obj_to_sym(mrb_state *mrb, mrb_value name)
9595
return 0; /* not reached */
9696
}
9797

98+
#if !defined(MRB_NO_FLOAT) && !defined(MRB_WORD_BOXING)
99+
/*
100+
* Hands out the count that tells one NaN from the next. See the comment above
101+
* `MRB_NAN_SERIAL_MAX` in `mruby/value.h`.
102+
*/
103+
MRB_API uint64_t
104+
mrb_nan_serial_next(mrb_state *mrb)
105+
{
106+
return mrb->nan_serial++;
107+
}
108+
#endif
109+
98110
#if !defined(MRB_NO_FLOAT) && !defined(MRB_NAN_BOXING)
99111
static mrb_int
100112
mrb_float_id(mrb_float f)
@@ -125,7 +137,13 @@ mrb_obj_id(mrb_value obj)
125137
if (mrb_integer_p(obj)) return mrb_integer(obj);
126138
#ifndef MRB_NO_FLOAT
127139
if (mrb_float_p(obj)) {
128-
return mrb_float_id(mrb_float(obj));
140+
mrb_float f = mrb_float(obj);
141+
/* A NaN is an object of its own here, and two of them hold the same
142+
bits, so what the bits hash to is one id for objects that `equal?`
143+
has already told apart. The object answers for a NaN instead, which
144+
is what the line below does for everything that is not a Float. */
145+
if (f != f) return (mrb_int)obj.w;
146+
return mrb_float_id(f);
129147
}
130148
#endif
131149
}
@@ -185,6 +203,8 @@ mrb_obj_id(mrb_value obj)
185203
* - 64-bit float64: rotation encoding, lossless for exponents [-255, +256].
186204
* - 32-bit float32: rotation encoding, lossless for exponents [-32, +31].
187205
* Floats outside the inline range are heap-allocated as RFloat.
206+
* A NaN is heap-allocated whatever the width, being the one float that has to
207+
* be told from another of the same value.
188208
*/
189209

190210
#if !defined(MRB_WORDBOX_NO_INLINE_FLOAT) && \
@@ -197,7 +217,7 @@ mrb_obj_id(mrb_value obj)
197217
* 2 bits == 10 (WORDBOX_FLOAT_FLAG).
198218
* Decode: rotl(tagged_value, N-3) + ADDEND recovers the original bits.
199219
*
200-
* Special values (0.0, -0.0, +Inf, -Inf, NaN) are encoded as small
220+
* Special values (0.0, -0.0, +Inf, -Inf) are encoded as small
201221
* sentinel constants that also have bottom 2 bits == 10. This avoids
202222
* heap allocation for these common values.
203223
*/
@@ -208,8 +228,13 @@ mrb_obj_id(mrb_value obj)
208228
#define WORDBOX_FLOAT_NZERO 0x06 /* -0.0 */
209229
#define WORDBOX_FLOAT_PINF 0x0a /* +Infinity */
210230
#define WORDBOX_FLOAT_NINF 0x0e /* -Infinity */
211-
#define WORDBOX_FLOAT_NAN 0x12 /* NaN (all NaN bit patterns normalize to this) */
212-
#define WORDBOX_FLOAT_SENTINEL_MAX WORDBOX_FLOAT_NAN
231+
/* A NaN gets no sentinel of its own. It is equal to nothing at all, its own
232+
operand included, so `==` can never find one and a container searching for
233+
the NaN it holds has only the object to go by. A NaN therefore takes the
234+
heap, where the object it is answers for it and two of them are never one.
235+
Every float a sentinel does stand for is equal to itself, so one word does
236+
for all the copies of it there will ever be. */
237+
#define WORDBOX_FLOAT_SENTINEL_MAX WORDBOX_FLOAT_NINF
213238

214239
#if defined(MRB_USE_FLOAT32) && !defined(MRB_64BIT)
215240
/*
@@ -291,9 +316,18 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f)
291316
mrb_rfloat_set(v.fp, f);
292317
v.bp->frozen = 1;
293318
#elif defined(MRB_64BIT) && defined(MRB_USE_FLOAT32)
294-
v.w = 0;
295-
v.f = f;
296-
v.w = (v.w<<2) | 2;
319+
if (f != f) {
320+
/* a NaN is the object it is, and a word holding the whole float has
321+
nothing left to tell one from another with */
322+
v.p = mrb_obj_alloc(mrb, MRB_TT_FLOAT, mrb->float_class);
323+
mrb_rfloat_set(v.fp, f);
324+
v.bp->frozen = 1;
325+
}
326+
else {
327+
v.w = 0;
328+
v.f = f;
329+
v.w = (v.w<<2) | 2;
330+
}
297331
#elif defined(MRB_64BIT)
298332
{
299333
uint64_t bits = wordbox_float64_to_u64((double)f);
@@ -313,7 +347,7 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f)
313347
else if (bits == UINT64_C(0xFFF0000000000000))
314348
v.w = WORDBOX_FLOAT_NINF;
315349
else
316-
v.w = WORDBOX_FLOAT_NAN;
350+
goto float_heap; /* a NaN is the object it is; see the sentinels */
317351
}
318352
else if (exp >= WORDBOX_FLOAT_EXP_MIN && exp <= WORDBOX_FLOAT_EXP_MAX) {
319353
uintptr_t w = (uintptr_t)wordbox_rotl64(bits - WORDBOX_FLOAT_ADDEND, WORDBOX_FLOAT_ROTATE);
@@ -347,7 +381,7 @@ mrb_word_boxing_float_value(mrb_state *mrb, mrb_float f)
347381
else if (bits == 0xFF800000u)
348382
v.w = WORDBOX_FLOAT_NINF;
349383
else
350-
v.w = WORDBOX_FLOAT_NAN;
384+
goto float_heap; /* a NaN is the object it is; see the sentinels */
351385
}
352386
else if (exp >= WORDBOX_FLOAT32_EXP_MIN && exp <= WORDBOX_FLOAT32_EXP_MAX) {
353387
uintptr_t w = (uintptr_t)wordbox_rotl32(bits - WORDBOX_FLOAT32_ADDEND, WORDBOX_FLOAT_ROTATE);
@@ -379,8 +413,11 @@ mrb_word_boxing_value_float(mrb_value v)
379413
#if defined(MRB_64BIT) && defined(MRB_USE_FLOAT32)
380414
union mrb_value_ u;
381415
u.value = v;
382-
u.w >>= 2;
383-
return u.f;
416+
if ((v.w & WORDBOX_FLOAT_MASK) == WORDBOX_FLOAT_FLAG) {
417+
u.w >>= 2;
418+
return u.f;
419+
}
420+
return mrb_rfloat_value(u.fp);
384421
#elif defined(MRB_64BIT)
385422
if ((v.w & WORDBOX_FLOAT_MASK) == WORDBOX_FLOAT_FLAG) {
386423
if (v.w <= WORDBOX_FLOAT_SENTINEL_MAX) {
@@ -389,7 +426,6 @@ mrb_word_boxing_value_float(mrb_value v)
389426
case WORDBOX_FLOAT_NZERO: return (mrb_float)(-0.0);
390427
case WORDBOX_FLOAT_PINF: return (mrb_float)( INFINITY);
391428
case WORDBOX_FLOAT_NINF: return (mrb_float)(-INFINITY);
392-
case WORDBOX_FLOAT_NAN: return (mrb_float) NAN;
393429
default: break; /* not reached */
394430
}
395431
}
@@ -410,7 +446,6 @@ mrb_word_boxing_value_float(mrb_value v)
410446
case WORDBOX_FLOAT_NZERO: return (mrb_float)(-0.0f);
411447
case WORDBOX_FLOAT_PINF: return (mrb_float)( INFINITY);
412448
case WORDBOX_FLOAT_NINF: return (mrb_float)(-INFINITY);
413-
case WORDBOX_FLOAT_NAN: return (mrb_float) NAN;
414449
default: break; /* not reached */
415450
}
416451
}

‎src/hash.c‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,9 +410,17 @@ obj_eql(mrb_state *mrb, mrb_value a, mrb_value b, struct RHash *h)
410410
return mrb_integer(a) == mrb_integer(b);
411411

412412
#ifndef MRB_NO_FLOAT
413-
case MRB_TT_FLOAT:
413+
case MRB_TT_FLOAT: {
414414
if (!mrb_float_p(b)) return FALSE;
415-
return mrb_float(a) == mrb_float(b);
415+
mrb_float fa = mrb_float(a);
416+
if (fa == mrb_float(b)) return TRUE;
417+
/* A NaN is equal to no value, its own key included, so a Hash handed the
418+
very key it stored would not find it again. The key it holds is that
419+
object, and an object is the same key as itself. Only a key that is not
420+
equal to itself asks, so what an ordinary key pays is the comparison
421+
against the value already in hand. */
422+
return fa != fa && mrb_obj_eq(mrb, a, b);
423+
}
416424
#endif
417425

418426
default:

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL