* bug#36370: 27.0.50; XFIXNAT called on negative numbers
@ 2019-06-25 5:36 Pip Cet
2019-06-27 1:10 ` Paul Eggert
0 siblings, 1 reply; 36+ messages in thread
From: Pip Cet @ 2019-06-25 5:36 UTC (permalink / raw)
To: 36370
[-- Attachment #1: Type: text/plain, Size: 1643 bytes --]
I just spent some time reviewing the use of XFIXNAT, after being
bitten by this code in fileio.c:
if (FIXNUMP (tem))
nextpos = XFIXNAT (tem);
In the following cases, negative numbers passed to APIs which aren't
strictly internal or low-level cause us to call XFIXNAT on a negative
number:
(indent-to -100 -100)
(set-text-properties -1 -1 nil "")
(make-network-process :name "name" :family 'ipv6 :remote [-1 0 0 0 0 0 0 0 0])
(end-kbd-macro -1 nil)
(previous-single-property-change (point) 'dummy nil -1)
I think those six cases are worth fixing, but more generally, I think
it would help avoid such errors if XFIXNAT were "always" paired with
FIXNATP, XFIXNUM with FIXNUMP, and XCHARACTER (which would be new)
with CHARACTERP.
So I'm going to be brave here and admit that in many cases, while
XFIXNUM was correct, it took me a long time to see that because the
check was performed somewhere else.
I understand there is a very slight performance hit on some platforms,
but is that really still an issue? Also, consistent pairing would
catch a few cases in which we call XFIXNUM but could get away with an
XFIXNAT, such as this code in self-insert-command:
if (!CHARACTERP (c))
bitch_at_user ();
else {
int character = translate_char (Vtranslation_table_for_input,
XFIXNUM (c));
I also think it would make sense to eassume (FIXNUMP (a)) in both
XFIXNAT and XFIXNUM (but not XHASH, or XUFIXNUM if that's what XHASH
uses). It might catch bugs like
(set-text-properties nil nil nil "")
currently not throwing an error.
The patch I'm attaching fixes only things I'm reasonably sure are bugs.
[-- Attachment #2: xfixnat.diff --]
[-- Type: text/x-patch, Size: 8925 bytes --]
diff --git a/src/ccl.c b/src/ccl.c
index ec108e30d8..c853e61982 100644
--- a/src/ccl.c
+++ b/src/ccl.c
@@ -2062,7 +2062,7 @@ #define CCL_EXECUTE_BUF_SIZE 1024
if (TYPE_RANGED_FIXNUMP (int, AREF (status, i)))
ccl.reg[i] = XFIXNUM (AREF (status, i));
}
- if (FIXNUMP (AREF (status, i)))
+ if (FIXNATP (AREF (status, i)))
{
i = XFIXNAT (AREF (status, 8));
if (ccl.ic < i && i < ccl.size)
diff --git a/src/dosfns.c b/src/dosfns.c
index 47c545007a..fb5bcc9ad3 100644
--- a/src/dosfns.c
+++ b/src/dosfns.c
@@ -72,7 +72,7 @@ DEFUN ("int86", Fint86, Sint86, 2, 2, 0,
if (no < 0 || no > 0xff || ASIZE (registers) != 8)
return Qnil;
for (i = 0; i < 8; i++)
- CHECK_FIXNUM (AREF (registers, i));
+ CHECK_FIXNAT (AREF (registers, i));
inregs.x.ax = (unsigned long) XFIXNAT (AREF (registers, 0));
inregs.x.bx = (unsigned long) XFIXNAT (AREF (registers, 1));
@@ -139,7 +139,7 @@ DEFUN ("msdos-memput", Fdos_memput, Sdos_memput, 2, 2, 0,
for (i = 0; i < len; i++)
{
- CHECK_FIXNUM (AREF (vector, i));
+ CHECK_FIXNAT (AREF (vector, i));
buf[i] = (unsigned char) XFIXNAT (AREF (vector, i)) & 0xFF;
}
diff --git a/src/fileio.c b/src/fileio.c
index 5dd14daacb..b2db6efd54 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -4719,7 +4719,7 @@ because (1) it preserves some marker positions and (2) it puts less data
the format conversion. */
Lisp_Object tem = XCAR (old_undo);
if (CONSP (tem) && FIXNUMP (XCAR (tem))
- && FIXNUMP (XCDR (tem))
+ && FIXNATP (XCDR (tem))
&& XFIXNAT (XCDR (tem)) == PT + old_inserted)
XSETCDR (tem, make_fixnum (PT + inserted));
}
@@ -5391,7 +5391,7 @@ a_write (int desc, Lisp_Object string, ptrdiff_t pos,
{
tem = Fcar_safe (Fcar (*annot));
nextpos = pos - 1;
- if (FIXNUMP (tem))
+ if (FIXNATP (tem))
nextpos = XFIXNAT (tem);
/* If there are no more annotations in this range,
@@ -5847,7 +5847,7 @@ DEFUN ("do-auto-save", Fdo_auto_save, Sdo_auto_save, 0, 2, "",
set_buffer_internal (b);
if (NILP (Vauto_save_include_big_deletions)
- && FIXNUMP (BVAR (b, save_length))
+ && FIXNATP (BVAR (b, save_length))
/* A short file is likely to change a large fraction;
spare the user annoying messages. */
&& XFIXNAT (BVAR (b, save_length)) > 5000
diff --git a/src/image.c b/src/image.c
index 7b648c46ae..2ecea0893f 100644
--- a/src/image.c
+++ b/src/image.c
@@ -2385,7 +2385,7 @@ lookup_image (struct frame *f, Lisp_Object spec)
#endif
ascent = image_spec_value (spec, QCascent, NULL);
- if (FIXNUMP (ascent))
+ if (FIXNATP (ascent))
img->ascent = XFIXNAT (ascent);
else if (EQ (ascent, Qcenter))
img->ascent = CENTERED_IMAGE_ASCENT;
diff --git a/src/lisp.h b/src/lisp.h
index 77fc22d118..a1bd6d8f6b 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -2968,6 +2968,14 @@ #define CHECK_FIXNUM_COERCE_MARKER(x) \
CHECK_TYPE (FIXNUMP (x), Qinteger_or_marker_p, x); \
} while (false)
+#define CHECK_FIXNAT_COERCE_MARKER(x) \
+ do { \
+ if (MARKERP ((x))) \
+ XSETFASTINT (x, marker_position (x)); \
+ else \
+ CHECK_TYPE (FIXNATP (x), Qinteger_or_marker_p, x); \
+ } while (false)
+
INLINE double
XFLOATINT (Lisp_Object n)
{
diff --git a/src/process.c b/src/process.c
index 6717ccb418..6bc3c75190 100644
--- a/src/process.c
+++ b/src/process.c
@@ -2673,7 +2673,7 @@ conv_lisp_to_sockaddr (int family, Lisp_Object address, struct sockaddr *sa, int
hostport = XFIXNUM (p->contents[--len]);
sin6->sin6_port = htons (hostport);
for (i = 0; i < len; i++)
- if (FIXNUMP (p->contents[i]))
+ if (FIXNATP (p->contents[i]))
{
int j = XFIXNAT (p->contents[i]) & 0xffff;
ip6[i] = ntohs (j);
diff --git a/src/textprop.c b/src/textprop.c
index ae42c44185..6b20a34820 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -134,8 +134,8 @@ validate_interval_range (Lisp_Object object, Lisp_Object *begin,
ptrdiff_t searchpos;
CHECK_STRING_OR_BUFFER (object);
- CHECK_FIXNUM_COERCE_MARKER (*begin);
- CHECK_FIXNUM_COERCE_MARKER (*end);
+ CHECK_FIXNAT_COERCE_MARKER (*begin);
+ CHECK_FIXNAT_COERCE_MARKER (*end);
/* If we are asked for a point, but from a subr which operates
on a range, then return nothing. */
@@ -790,14 +790,14 @@ DEFUN ("next-single-char-property-change", Fnext_single_char_property_change,
Fset_buffer (object);
}
- CHECK_FIXNUM_COERCE_MARKER (position);
+ CHECK_FIXNAT_COERCE_MARKER (position);
initial_value = Fget_char_property (position, prop, object);
if (NILP (limit))
XSETFASTINT (limit, ZV);
else
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
if (XFIXNAT (position) >= XFIXNAT (limit))
{
@@ -874,12 +874,12 @@ DEFUN ("previous-single-char-property-change",
Fset_buffer (object);
}
- CHECK_FIXNUM_COERCE_MARKER (position);
+ CHECK_FIXNAT_COERCE_MARKER (position);
if (NILP (limit))
XSETFASTINT (limit, BEGV);
else
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
if (XFIXNAT (position) <= XFIXNAT (limit))
{
@@ -942,7 +942,7 @@ DEFUN ("next-property-change", Fnext_property_change,
XSETBUFFER (object, current_buffer);
if (!NILP (limit) && !EQ (limit, Qt))
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
i = validate_interval_range (object, &position, &position, soft);
@@ -975,7 +975,7 @@ DEFUN ("next-property-change", Fnext_property_change,
if (!next
|| (next->position
- >= (FIXNUMP (limit)
+ >= (FIXNATP (limit)
? XFIXNAT (limit)
: (STRINGP (object)
? SCHARS (object)
@@ -1009,7 +1009,7 @@ DEFUN ("next-single-property-change", Fnext_single_property_change,
XSETBUFFER (object, current_buffer);
if (!NILP (limit))
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
i = validate_interval_range (object, &position, &position, soft);
if (!i)
@@ -1024,7 +1024,7 @@ DEFUN ("next-single-property-change", Fnext_single_property_change,
if (!next
|| (next->position
- >= (FIXNUMP (limit)
+ >= (FIXNATP (limit)
? XFIXNAT (limit)
: (STRINGP (object)
? SCHARS (object)
@@ -1056,7 +1056,7 @@ DEFUN ("previous-property-change", Fprevious_property_change,
XSETBUFFER (object, current_buffer);
if (!NILP (limit))
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
i = validate_interval_range (object, &position, &position, soft);
if (!i)
@@ -1074,7 +1074,7 @@ DEFUN ("previous-property-change", Fprevious_property_change,
if (!previous
|| (previous->position + LENGTH (previous)
- <= (FIXNUMP (limit)
+ <= (FIXNATP (limit)
? XFIXNAT (limit)
: (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
return limit;
@@ -1106,7 +1106,7 @@ DEFUN ("previous-single-property-change", Fprevious_single_property_change,
XSETBUFFER (object, current_buffer);
if (!NILP (limit))
- CHECK_FIXNUM_COERCE_MARKER (limit);
+ CHECK_FIXNAT_COERCE_MARKER (limit);
i = validate_interval_range (object, &position, &position, soft);
@@ -1127,7 +1127,7 @@ DEFUN ("previous-single-property-change", Fprevious_single_property_change,
if (!previous
|| (previous->position + LENGTH (previous)
- <= (FIXNUMP (limit)
+ <= (FIXNATP (limit)
? XFIXNAT (limit)
: (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
return limit;
@@ -1353,8 +1353,8 @@ set_text_properties (Lisp_Object start, Lisp_Object end, Lisp_Object properties,
/* If we want no properties for a whole string,
get rid of its intervals. */
if (NILP (properties) && STRINGP (object)
- && XFIXNAT (start) == 0
- && XFIXNAT (end) == SCHARS (object))
+ && FIXNATP (start) && XFIXNAT (start) == 0
+ && FIXNATP (end) && XFIXNAT (end) == SCHARS (object))
{
if (!string_intervals (object))
return Qnil;
diff --git a/src/w32term.c b/src/w32term.c
index 5726124b0e..886fc6c751 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -2463,7 +2463,7 @@ w32_draw_glyph_string (struct glyph_string *s)
Lisp_Object val
= buffer_local_value (Qunderline_minimum_offset,
s->w->contents);
- if (FIXNUMP (val))
+ if (FIXNATP (val))
minimum_offset = XFIXNAT (val);
else
minimum_offset = 1;
diff --git a/src/xterm.c b/src/xterm.c
index 1acff2af0d..64467e0d92 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -3806,7 +3806,7 @@ x_draw_glyph_string (struct glyph_string *s)
Lisp_Object val
= buffer_local_value (Qunderline_minimum_offset,
s->w->contents);
- if (FIXNUMP (val))
+ if (FIXNATP (val))
minimum_offset = XFIXNAT (val);
else
minimum_offset = 1;
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-25 5:36 bug#36370: 27.0.50; XFIXNAT called on negative numbers Pip Cet
@ 2019-06-27 1:10 ` Paul Eggert
2019-06-27 6:16 ` Pip Cet
0 siblings, 1 reply; 36+ messages in thread
From: Paul Eggert @ 2019-06-27 1:10 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370
[-- Attachment #1: Type: text/plain, Size: 1521 bytes --]
Thanks for looking into this. Cleaning this up has been on my to-do list
for a while, and something along these lines has been discussed on the
mailing list. Some comments on your fixes:
* The ccl.c, fileio.c, image.c, and process.c fixes should use XFIXNUM,
not XFIXNAT, since the code is range-checking after extraction anyway
and the latter range-checking should do the right thing. Come to think
of it, the ccl.c and image.c code is buggy regardless of the
XFIXNUM/XFIXNAT business since the Emacs integer might not fit into int
range, and that should be fixed too.
* Instead of adding a new macro CHECK_FIXNAT_COERCE_MARKER, the code
should stick with CHECK_FIXNUM_COERCE_MARKER and do the usual
range-checking on the results. This should yield more-precise
diagnostics. Most of the range-checking is there already.
* The set_text_properties fix can be done more efficiently via EQ.
* I'm mildly inclined to think that w32term.c and xterm.c should treate
negative minimum values as 0 when the actual value must be nonnegative.
That is, a negative minimum should act like a minimum of 0, not as a
minimum of 1 as in your proposed patch.
To help prevent similar problems in the future, XFIXNUM, XFIXNAT etc.
should check that their arguments are of the proper type when
ENABLE_CHECKING is nonzero.
A proposed patch is attached; it should do all the above.
PS. At some point we should also check that make_fixnum is invoked only
on values in fixnum range, but that's a matter for a later patch.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Clean-up-use-of-XFIXNUM-etc.patch --]
[-- Type: text/x-patch; name="0001-Clean-up-use-of-XFIXNUM-etc.patch", Size: 15900 bytes --]
From bdec4f8e8d7278d68d3534b87c29362ffcbe23d2 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 26 Jun 2019 17:52:06 -0700
Subject: [PATCH] Clean up use of XFIXNUM etc.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A few bits of the code were relying on the fact that XFIXNUM,
XFIXNAT, and XUFIXNUM do something even with arguments that
are not fixnums/fixnats. Separate these rare uses out into
XFIXNUM_RAW and XUFIXNUM_RAW.
Problem and original patch reported by Pip Cet (Bug#36370).
* src/ccl.c (Fccl_execute_on_string):
* src/fileio.c (Finsert_file_contents, a_write)
(Fdo_auto_save):
* src/process.c (conv_lisp_to_sockaddr):
* src/textprop.c (Fnext_single_char_property_change)
(Fprevious_single_char_property_change)
(Fnext_property_change, Fnext_single_property_change)
(Fprevious_property_change)
(Fprevious_single_property_change):
Don’t assume fixnums are nonnegative.
* src/ccl.c (Fccl_execute_on_string):
Fix range-checking bug if AREF (status, i) is out of int range.
* src/data.c (arith_driver): Use XFIXNUM_RAW as we want
efficient garbage if the value is not a fixnum.
* src/dosfns.c (Fint86, Fdos_memput):
Check that args are nonnegative.
* src/image.c (lookup_image): Check that args are in range.
* src/lisp.h (lisp_h_XHASH): Use XUFIXNUM_RAW, since this
is for hashing.
(lisp_h_XFIXNAT, XFIXNAT) [USE_LSB_TAG]: Remove macros.
(lisp_h_XFIXNUM_RAW, XFIXNUM_RAW) [USE_LSB_TAG]: New macros, with
the semantics of the old macros without _RAW.
(XFIXNUM_RAW, XUFIXNUM_RAW): New inline functions, with the
semantics of the old functions without _RAW.
(FIXNUMP): Move definition up to avoid forward use.
(XFIXNUM, XFIXNAT, XUFIXNUM): Use eassume to add a runtime
check (when debugging) that the argument has the proper form.
(XFIXNUM, XFIXNAT): Now inline functions only, since they
refer to their arguments more than once now that they use eassume.
* src/textprop.c (Fprevious_single_char_property_change):
Avoid fixnum overflow with invalid input.
(set_text_properties): Fix unlikely failure
to validate arguments, by using EQ instead of XFIXNAT.
* src/w32term.c (w32_draw_glyph_string):
* src/xterm.c (x_draw_glyph_string):
Treat negative minimums as 0 rather than as garbage patterns.
---
src/ccl.c | 6 ++--
src/data.c | 2 +-
src/dosfns.c | 4 +--
src/fileio.c | 6 ++--
src/image.c | 4 +--
src/lisp.h | 75 +++++++++++++++++++++++++-------------------------
src/process.c | 2 +-
src/textprop.c | 29 +++++++++----------
src/w32term.c | 2 +-
src/xterm.c | 2 +-
10 files changed, 67 insertions(+), 65 deletions(-)
diff --git a/src/ccl.c b/src/ccl.c
index ec108e30d8..f1d4c28df1 100644
--- a/src/ccl.c
+++ b/src/ccl.c
@@ -2064,9 +2064,9 @@ #define CCL_EXECUTE_BUF_SIZE 1024
}
if (FIXNUMP (AREF (status, i)))
{
- i = XFIXNAT (AREF (status, 8));
- if (ccl.ic < i && i < ccl.size)
- ccl.ic = i;
+ EMACS_INT ic = XFIXNUM (AREF (status, i));
+ if (ccl.ic < ic && ic < ccl.size)
+ ccl.ic = ic;
}
buf_magnification = ccl.buf_magnification ? ccl.buf_magnification : 1;
diff --git a/src/data.c b/src/data.c
index c1699aeae7..46bd7e0e25 100644
--- a/src/data.c
+++ b/src/data.c
@@ -2928,7 +2928,7 @@ arith_driver (enum arithop code, ptrdiff_t nargs, Lisp_Object *args,
ptrdiff_t argnum = 0;
/* Set ACCUM to VAL's value if it is a fixnum, otherwise to some
ignored value to avoid using an uninitialized variable later. */
- intmax_t accum = XFIXNUM (val);
+ intmax_t accum = XFIXNUM_RAW (val);
if (FIXNUMP (val))
while (true)
diff --git a/src/dosfns.c b/src/dosfns.c
index 47c545007a..fb5bcc9ad3 100644
--- a/src/dosfns.c
+++ b/src/dosfns.c
@@ -72,7 +72,7 @@ DEFUN ("int86", Fint86, Sint86, 2, 2, 0,
if (no < 0 || no > 0xff || ASIZE (registers) != 8)
return Qnil;
for (i = 0; i < 8; i++)
- CHECK_FIXNUM (AREF (registers, i));
+ CHECK_FIXNAT (AREF (registers, i));
inregs.x.ax = (unsigned long) XFIXNAT (AREF (registers, 0));
inregs.x.bx = (unsigned long) XFIXNAT (AREF (registers, 1));
@@ -139,7 +139,7 @@ DEFUN ("msdos-memput", Fdos_memput, Sdos_memput, 2, 2, 0,
for (i = 0; i < len; i++)
{
- CHECK_FIXNUM (AREF (vector, i));
+ CHECK_FIXNAT (AREF (vector, i));
buf[i] = (unsigned char) XFIXNAT (AREF (vector, i)) & 0xFF;
}
diff --git a/src/fileio.c b/src/fileio.c
index 0da9894a73..61e10dac47 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -4720,7 +4720,7 @@ because (1) it preserves some marker positions and (2) it puts less data
Lisp_Object tem = XCAR (old_undo);
if (CONSP (tem) && FIXNUMP (XCAR (tem))
&& FIXNUMP (XCDR (tem))
- && XFIXNAT (XCDR (tem)) == PT + old_inserted)
+ && XFIXNUM (XCDR (tem)) == PT + old_inserted)
XSETCDR (tem, make_fixnum (PT + inserted));
}
}
@@ -5392,7 +5392,7 @@ a_write (int desc, Lisp_Object string, ptrdiff_t pos,
tem = Fcar_safe (Fcar (*annot));
nextpos = pos - 1;
if (FIXNUMP (tem))
- nextpos = XFIXNAT (tem);
+ nextpos = XFIXNUM (tem);
/* If there are no more annotations in this range,
output the rest of the range all at once. */
@@ -5850,7 +5850,7 @@ DEFUN ("do-auto-save", Fdo_auto_save, Sdo_auto_save, 0, 2, "",
&& FIXNUMP (BVAR (b, save_length))
/* A short file is likely to change a large fraction;
spare the user annoying messages. */
- && XFIXNAT (BVAR (b, save_length)) > 5000
+ && XFIXNUM (BVAR (b, save_length)) > 5000
&& (growth_factor * (BUF_Z (b) - BUF_BEG (b))
< (growth_factor - 1) * XFIXNAT (BVAR (b, save_length)))
/* These messages are frequent and annoying for `*mail*'. */
diff --git a/src/image.c b/src/image.c
index 7b648c46ae..d204749631 100644
--- a/src/image.c
+++ b/src/image.c
@@ -2385,13 +2385,13 @@ lookup_image (struct frame *f, Lisp_Object spec)
#endif
ascent = image_spec_value (spec, QCascent, NULL);
- if (FIXNUMP (ascent))
+ if (RANGED_FIXNUMP (0, ascent, INT_MAX))
img->ascent = XFIXNAT (ascent);
else if (EQ (ascent, Qcenter))
img->ascent = CENTERED_IMAGE_ASCENT;
margin = image_spec_value (spec, QCmargin, NULL);
- if (FIXNUMP (margin))
+ if (RANGED_FIXNUMP (0, margin, INT_MAX))
img->vmargin = img->hmargin = XFIXNAT (margin);
else if (CONSP (margin))
{
diff --git a/src/lisp.h b/src/lisp.h
index 77fc22d118..077d236065 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -414,12 +414,11 @@ #define lisp_h_XCAR(c) XCONS (c)->u.s.car
#define lisp_h_XCDR(c) XCONS (c)->u.s.u.cdr
#define lisp_h_XCONS(a) \
(eassert (CONSP (a)), XUNTAG (a, Lisp_Cons, struct Lisp_Cons))
-#define lisp_h_XHASH(a) XUFIXNUM (a)
+#define lisp_h_XHASH(a) XUFIXNUM_RAW (a)
#if USE_LSB_TAG
# define lisp_h_make_fixnum(n) \
XIL ((EMACS_INT) (((EMACS_UINT) (n) << INTTYPEBITS) + Lisp_Int0))
-# define lisp_h_XFIXNAT(a) XFIXNUM (a)
-# define lisp_h_XFIXNUM(a) (XLI (a) >> INTTYPEBITS)
+# define lisp_h_XFIXNUM_RAW(a) (XLI (a) >> INTTYPEBITS)
# define lisp_h_XTYPE(a) ((enum Lisp_Type) (XLI (a) & ~VALMASK))
#endif
@@ -460,8 +459,7 @@ #define lisp_h_XHASH(a) XUFIXNUM (a)
# define XHASH(a) lisp_h_XHASH (a)
# if USE_LSB_TAG
# define make_fixnum(n) lisp_h_make_fixnum (n)
-# define XFIXNAT(a) lisp_h_XFIXNAT (a)
-# define XFIXNUM(a) lisp_h_XFIXNUM (a)
+# define XFIXNUM_RAW(a) lisp_h_XFIXNUM_RAW (a)
# define XTYPE(a) lisp_h_XTYPE (a)
# endif
#endif
@@ -1141,17 +1139,9 @@ #define MOST_NEGATIVE_FIXNUM (-1 - MOST_POSITIVE_FIXNUM)
}
INLINE EMACS_INT
-(XFIXNUM) (Lisp_Object a)
+(XFIXNUM_RAW) (Lisp_Object a)
{
- return lisp_h_XFIXNUM (a);
-}
-
-INLINE EMACS_INT
-(XFIXNAT) (Lisp_Object a)
-{
- EMACS_INT n = lisp_h_XFIXNAT (a);
- eassume (0 <= n);
- return n;
+ return lisp_h_XFIXNUM_RAW (a);
}
#else /* ! USE_LSB_TAG */
@@ -1179,9 +1169,11 @@ make_fixnum (EMACS_INT n)
return XIL (n);
}
-/* Extract A's value as a signed integer. */
+/* Extract A's value as a signed integer. Unlike XFIXNUM, this works
+ on any Lisp object, although the resulting integer is useful only
+ for things like hashing when A is not a fixnum. */
INLINE EMACS_INT
-XFIXNUM (Lisp_Object a)
+XFIXNUM_RAW (Lisp_Object a)
{
EMACS_INT i = XLI (a);
if (! USE_LSB_TAG)
@@ -1192,31 +1184,36 @@ XFIXNUM (Lisp_Object a)
return i >> INTTYPEBITS;
}
-/* Like XFIXNUM (A), but may be faster. A must be nonnegative.
- If ! USE_LSB_TAG, this takes advantage of the fact that Lisp
- integers have zero-bits in their tags. */
-INLINE EMACS_INT
-XFIXNAT (Lisp_Object a)
+#endif /* ! USE_LSB_TAG */
+
+INLINE bool
+(FIXNUMP) (Lisp_Object x)
{
- EMACS_INT int0 = Lisp_Int0;
- EMACS_INT n = USE_LSB_TAG ? XFIXNUM (a) : XLI (a) - (int0 << VALBITS);
- eassume (0 <= n);
- return n;
+ return lisp_h_FIXNUMP (x);
}
-#endif /* ! USE_LSB_TAG */
+INLINE EMACS_INT
+XFIXNUM (Lisp_Object a)
+{
+ eassume (FIXNUMP (a));
+ return XFIXNUM_RAW (a);
+}
/* Extract A's value as an unsigned integer in the range 0..INTMASK. */
INLINE EMACS_UINT
-XUFIXNUM (Lisp_Object a)
+XUFIXNUM_RAW (Lisp_Object a)
{
EMACS_UINT i = XLI (a);
return USE_LSB_TAG ? i >> INTTYPEBITS : i & INTMASK;
}
+INLINE EMACS_UINT
+XUFIXNUM (Lisp_Object a)
+{
+ eassume (FIXNUMP (a));
+ return XUFIXNUM_RAW (a);
+}
-/* Return A's hash, which is in the range 0..INTMASK. Although XHASH (A) ==
- XUFIXNUM (A) currently, XUFIXNUM should be applied only to fixnums. */
-
+/* Return A's hash, which is in the range 0..INTMASK. */
INLINE EMACS_INT
(XHASH) (Lisp_Object a)
{
@@ -1261,12 +1258,6 @@ make_lisp_ptr (void *ptr, enum Lisp_Type type)
return a;
}
-INLINE bool
-(FIXNUMP) (Lisp_Object x)
-{
- return lisp_h_FIXNUMP (x);
-}
-
#define XSETINT(a, b) ((a) = make_fixnum (b))
#define XSETFASTINT(a, b) ((a) = make_fixed_natnum (b))
#define XSETCONS(a, b) ((a) = make_lisp_ptr (b, Lisp_Cons))
@@ -2832,6 +2823,16 @@ FIXNATP (Lisp_Object x)
{
return FIXNUMP (x) && 0 <= XFIXNUM (x);
}
+
+/* Like XFIXNUM (A), but may be faster. A must be nonnegative. */
+INLINE EMACS_INT
+XFIXNAT (Lisp_Object a)
+{
+ eassume (FIXNATP (a));
+ EMACS_INT int0 = Lisp_Int0;
+ return USE_LSB_TAG ? XFIXNUM (a) : XLI (a) - (int0 << VALBITS);
+}
+
INLINE bool
NUMBERP (Lisp_Object x)
{
diff --git a/src/process.c b/src/process.c
index 15d87cf601..cab390c10c 100644
--- a/src/process.c
+++ b/src/process.c
@@ -2675,7 +2675,7 @@ conv_lisp_to_sockaddr (int family, Lisp_Object address, struct sockaddr *sa, int
for (i = 0; i < len; i++)
if (FIXNUMP (p->contents[i]))
{
- int j = XFIXNAT (p->contents[i]) & 0xffff;
+ int j = XFIXNUM (p->contents[i]) & 0xffff;
ip6[i] = ntohs (j);
}
sa->sa_family = family;
diff --git a/src/textprop.c b/src/textprop.c
index ae42c44185..3026ec7e99 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -799,7 +799,7 @@ DEFUN ("next-single-char-property-change", Fnext_single_char_property_change,
else
CHECK_FIXNUM_COERCE_MARKER (limit);
- if (XFIXNAT (position) >= XFIXNAT (limit))
+ if (XFIXNAT (position) >= XFIXNUM (limit))
{
position = limit;
if (XFIXNAT (position) > ZV)
@@ -881,16 +881,17 @@ DEFUN ("previous-single-char-property-change",
else
CHECK_FIXNUM_COERCE_MARKER (limit);
- if (XFIXNAT (position) <= XFIXNAT (limit))
+ if (XFIXNUM (position) <= XFIXNUM (limit))
{
position = limit;
- if (XFIXNAT (position) < BEGV)
+ if (XFIXNUM (position) < BEGV)
XSETFASTINT (position, BEGV);
}
else
{
Lisp_Object initial_value
- = Fget_char_property (make_fixnum (XFIXNAT (position) - 1),
+ = Fget_char_property (make_fixnum (XFIXNUM (position)
+ - (0 <= XFIXNUM (position))),
prop, object);
while (true)
@@ -970,13 +971,13 @@ DEFUN ("next-property-change", Fnext_property_change,
next = next_interval (i);
while (next && intervals_equal (i, next)
- && (NILP (limit) || next->position < XFIXNAT (limit)))
+ && (NILP (limit) || next->position < XFIXNUM (limit)))
next = next_interval (next);
if (!next
|| (next->position
>= (FIXNUMP (limit)
- ? XFIXNAT (limit)
+ ? XFIXNUM (limit)
: (STRINGP (object)
? SCHARS (object)
: BUF_ZV (XBUFFER (object))))))
@@ -1019,13 +1020,13 @@ DEFUN ("next-single-property-change", Fnext_single_property_change,
next = next_interval (i);
while (next
&& EQ (here_val, textget (next->plist, prop))
- && (NILP (limit) || next->position < XFIXNAT (limit)))
+ && (NILP (limit) || next->position < XFIXNUM (limit)))
next = next_interval (next);
if (!next
|| (next->position
>= (FIXNUMP (limit)
- ? XFIXNAT (limit)
+ ? XFIXNUM (limit)
: (STRINGP (object)
? SCHARS (object)
: BUF_ZV (XBUFFER (object))))))
@@ -1069,13 +1070,13 @@ DEFUN ("previous-property-change", Fprevious_property_change,
previous = previous_interval (i);
while (previous && intervals_equal (previous, i)
&& (NILP (limit)
- || (previous->position + LENGTH (previous) > XFIXNAT (limit))))
+ || (previous->position + LENGTH (previous) > XFIXNUM (limit))))
previous = previous_interval (previous);
if (!previous
|| (previous->position + LENGTH (previous)
<= (FIXNUMP (limit)
- ? XFIXNAT (limit)
+ ? XFIXNUM (limit)
: (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
return limit;
else
@@ -1122,13 +1123,13 @@ DEFUN ("previous-single-property-change", Fprevious_single_property_change,
while (previous
&& EQ (here_val, textget (previous->plist, prop))
&& (NILP (limit)
- || (previous->position + LENGTH (previous) > XFIXNAT (limit))))
+ || (previous->position + LENGTH (previous) > XFIXNUM (limit))))
previous = previous_interval (previous);
if (!previous
|| (previous->position + LENGTH (previous)
<= (FIXNUMP (limit)
- ? XFIXNAT (limit)
+ ? XFIXNUM (limit)
: (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
return limit;
else
@@ -1353,8 +1354,8 @@ set_text_properties (Lisp_Object start, Lisp_Object end, Lisp_Object properties,
/* If we want no properties for a whole string,
get rid of its intervals. */
if (NILP (properties) && STRINGP (object)
- && XFIXNAT (start) == 0
- && XFIXNAT (end) == SCHARS (object))
+ && EQ (start, make_fixnum (0))
+ && EQ (end, make_fixnum (SCHARS (object))))
{
if (!string_intervals (object))
return Qnil;
diff --git a/src/w32term.c b/src/w32term.c
index 5726124b0e..97a5fc6389 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -2464,7 +2464,7 @@ w32_draw_glyph_string (struct glyph_string *s)
= buffer_local_value (Qunderline_minimum_offset,
s->w->contents);
if (FIXNUMP (val))
- minimum_offset = XFIXNAT (val);
+ minimum_offset = max (0, XFIXNUM (val));
else
minimum_offset = 1;
val = buffer_local_value (Qx_underline_at_descent_line,
diff --git a/src/xterm.c b/src/xterm.c
index 1acff2af0d..38bc17de97 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -3807,7 +3807,7 @@ x_draw_glyph_string (struct glyph_string *s)
= buffer_local_value (Qunderline_minimum_offset,
s->w->contents);
if (FIXNUMP (val))
- minimum_offset = XFIXNAT (val);
+ minimum_offset = max (0, XFIXNUM (val));
else
minimum_offset = 1;
val = buffer_local_value (Qx_underline_at_descent_line,
--
2.21.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 1:10 ` Paul Eggert
@ 2019-06-27 6:16 ` Pip Cet
2019-06-27 8:28 ` Paul Eggert
0 siblings, 1 reply; 36+ messages in thread
From: Pip Cet @ 2019-06-27 6:16 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370
On Thu, Jun 27, 2019 at 1:11 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> Thanks for looking into this. Cleaning this up has been on my to-do list
> for a while, and something along these lines has been discussed on the
> mailing list. Some comments on your fixes:
I really think I should reiterate that readability is much much more
important than whatever performance tweaks XFIXNAT might allow. I'd
prefer if the code were written as though FIXNATs weren't FIXNUMs at
all: pair FIXNATP with XFIXNAT, and provide FIXNAT versions of
CHECK_RANGED_FIXNUM and CHECK_FIXNUM_COERCE_MARKER.
Hmm. I'm not sure it's a problem for you, but I'm finding it rather
hard to check in all cases whether XFIXNAT is safe. For example,
ascent = image_spec_value (spec, QCascent, NULL);
if (FIXNUMP (ascent))
img->ascent = XFIXNAT (ascent);
in image.c is safe, because lookup_image is called only after valid_image_p.
if (FIXNUMP (AREF (status, i)))
{
- i = XFIXNAT (AREF (status, 8));
- if (ccl.ic < i && i < ccl.size)
- ccl.ic = i;
+ EMACS_INT ic = XFIXNUM (AREF (status, i));
+ if (ccl.ic < ic && ic < ccl.size)
+ ccl.ic = ic;
}
I'd prefer AREF (status, 8) here, for what readability the code gives us.
# define XHASH(a) lisp_h_XHASH (a)
# if USE_LSB_TAG
# define make_fixnum(n) lisp_h_make_fixnum (n)
-# define XFIXNAT(a) lisp_h_XFIXNAT (a)
-# define XFIXNUM(a) lisp_h_XFIXNUM (a)
+# define XFIXNUM_RAW(a) lisp_h_XFIXNUM_RAW (a)
# define XTYPE(a) lisp_h_XTYPE (a)
# endif
#endif
I'd prefer leaving the macros for now; eassume (inline_function (x))
doesn't work very well on current GCC, while eassume (MACRO (x)) is
fine, so I'm sometimes building with DEFINE_KEY_OPS_AS_MACROS. (The
eassume/eassert situation is something else I've been planning to look
at in more detail, though debugging it will be easier when
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91005 is fixed).
> * The ccl.c, fileio.c, image.c, and process.c fixes should use XFIXNUM,
> not XFIXNAT, since the code is range-checking after extraction anyway
> and the latter range-checking should do the right thing. Come to think
That's an argument that either XFIXNUM or XFIXNAT are fine, and in
this case XFIXNAT seems, to me, much more readable.
> of it, the ccl.c and image.c code is buggy regardless of the
> XFIXNUM/XFIXNAT business since the Emacs integer might not fit into int
> range, and that should be fixed too.
I don't see how either ccl.c or image.c are buggy for out-of-range
integer arguments.
> * Instead of adding a new macro CHECK_FIXNAT_COERCE_MARKER, the code
> should stick with CHECK_FIXNUM_COERCE_MARKER and do the usual
> range-checking on the results. This should yield more-precise
> diagnostics. Most of the range-checking is there already.
I disagree, obviously. When you want a position, you want a FIXNAT,
not a negative number, so CHECK_FIXNAT_COERCE_MARKER everywhere would
catch those cases where someone passes an obviously invalid argument.
> * The set_text_properties fix can be done more efficiently via EQ.
I suspect your change results in a few more insns being emitted, but
I'll have to check.
> * I'm mildly inclined to think that w32term.c and xterm.c should treate
> negative minimum values as 0 when the actual value must be nonnegative.
> That is, a negative minimum should act like a minimum of 0, not as a
> minimum of 1 as in your proposed patch.
Doesn't matter, so I decided not to fiddle with this code.
> To help prevent similar problems in the future, XFIXNUM, XFIXNAT etc.
> should check that their arguments are of the proper type when
> ENABLE_CHECKING is nonzero.
>
> A proposed patch is attached; it should do all the above.
I'd prefer not touching the dosfns.c code, for the simple reason that
if anyone still uses it, they might rely on the broken behavior.
> PS. At some point we should also check that make_fixnum is invoked only
> on values in fixnum range, but that's a matter for a later patch.
Definitely.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 6:16 ` Pip Cet
@ 2019-06-27 8:28 ` Paul Eggert
2019-06-27 13:17 ` Pip Cet
0 siblings, 1 reply; 36+ messages in thread
From: Paul Eggert @ 2019-06-27 8:28 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370
[-- Attachment #1: Type: text/plain, Size: 4402 bytes --]
Pip Cet wrote:
> I'd prefer if the code were written as though FIXNATs weren't FIXNUMs at all.
I'm more of the opposite opinion. The original reason for XFIXNAT (under its old
name) was performance, because long ago Emacs put the type tag in the most
significant bits, and it was faster to mask it out than to smear the sign bit
when you knew the fixnum was nonnegative. Nowadays that original reason is
obsolete because the tags are in the least significant bits and XFIXNAT is just
an alias for XFIXNUM, and if it were up to me we'd get rid of XFIXNAT entirely,
and just use XFIXNUM. But old habits die hard....
> ascent = image_spec_value (spec, QCascent, NULL);
> if (FIXNUMP (ascent))
> img->ascent = XFIXNAT (ascent);
>
> in image.c is safe, because lookup_image is called only after valid_image_p.
Sorry, you've lost me. How does valid_image_p guarantee that 'ascent' (if a
fixnum) is in the range 0..INT_MAX? Because if it's not in that range, the above
code could trap.
> I'd prefer AREF (status, 8) here, for what readability the code gives us.
Sure, sounds good. See attached further patch.
> I'd prefer leaving the macros for now
I actually did it that way at first, but that failed miserably as the macros
evaluated their arguments more than once when debugging , and lots of code
expects them to behave like functions and evaluate their arguments exactly once.
So functions it is.
> eassume (inline_function (x))
> doesn't work very well on current GCC, while eassume (MACRO (x)) is
> fine, so I'm sometimes building with DEFINE_KEY_OPS_AS_MACROS.
Hmm, I see I should have helped GCC more there. First, the two new eassumes
should just be easserts as the assumptions are not helpful to the compiler.
Second, I should restore the eassume that tells GCC that XFIXNAT returns a
nonnegative integer.
> (The
> eassume/eassert situation is something else I've been planning to look
> at in more detail, though debugging it will be easier when
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91005 is fixed).
Sorry, I don't follow. That bug report seems to be about nested C functions; why
is it relevant to Emacs, which doesn't use nested C functions?
>> * The ccl.c, fileio.c, image.c, and process.c fixes should use XFIXNUM,
>> not XFIXNAT, since the code is range-checking after extraction anyway
>> and the latter range-checking should do the right thing. Come to think
>
> That's an argument that either XFIXNUM or XFIXNAT are fine, and in
> this case XFIXNAT seems, to me, much more readable.
No, as the range-checking is done after the XFIXNAT, which means the XFIXNAT
could have an assertion failure.
>> of it, the ccl.c and image.c code is buggy regardless of the
>> XFIXNUM/XFIXNAT business since the Emacs integer might not fit into int
>> range, and that should be fixed too.
>
> I don't see how either ccl.c or image.c are buggy for out-of-range
> integer arguments.
For example, ccl.c in master has this:
if (FIXNUMP (AREF (status, i)))
{
i = XFIXNAT (AREF (status, 8));
if (ccl.ic < i && i < ccl.size)
ccl.ic = i;
}
where i is a 32-bit int. If AREF (status, 8) is the 62-bit Lisp fixnum with
value '2 * (EMACS_INT) INT_MAX + ccl.ic + 3', the assignment to i overflows,
which can trap. Even if the assignment doesn't trap but merely wraps around, the
next line won't do range checking correctly.
> When you want a position, you want a FIXNAT,
> not a negative number, so CHECK_FIXNAT_COERCE_MARKER everywhere would
> catch those cases where someone passes an obviously invalid argument.
But why stop there? 0 is an obviously invalid argument for buffers. :-) I think
this is just an area where we disagree - I want a range check where you want a
type check. It's safe either way and the range check is a tad faster (as the
range check needs to be done anyway) and results in a crisper diagnostic when it
fails.
> I'd prefer not touching the dosfns.c code, for the simple reason that
> if anyone still uses it, they might rely on the broken behavior.
We can't leave it alone, as XFIXNAT is now checking that its result is
nonnegative when debugging is enabled. However, we could instead change the
XFIXNATs to XFIXNUMs: this will generate exactly the same machine code on that
platform when debugging is disabled, and will not trap when debugging is
enabled. See attached patch.
[-- Attachment #2: 0001-Improve-XFIXNUM-cleanup-a-bit.txt --]
[-- Type: text/plain, Size: 4162 bytes --]
From 3f8090e91125de98798d4bfb5f91934bcac9626b Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 27 Jun 2019 01:14:53 -0700
Subject: [PATCH] Improve XFIXNUM cleanup a bit
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Based on Pip Cet’s review (Bug#36370#13).
* src/ccl.c (Fccl_execute_on_string): Use clearer indexing.
* src/dosfns.c (Fint86, Fdos_memput):
Avoid runtime checks for negative fixnums when debugging.
This restores the earlier machine code.
* src/lisp.h (XFIXNUM, XUFIXNUM): Use eassert, not eassume.
(XFIXNAT): At the start, merely eassert FIXNUMP rather
than eassuming FIXNATP. At the end, eassume that the
result is nonnegative. This restores help to the compiler
that the previous patch mistakenly removed.
---
src/ccl.c | 4 ++--
src/dosfns.c | 22 +++++++++++-----------
src/lisp.h | 10 ++++++----
3 files changed, 19 insertions(+), 17 deletions(-)
diff --git a/src/ccl.c b/src/ccl.c
index f1d4c28df1..ff42c6f25f 100644
--- a/src/ccl.c
+++ b/src/ccl.c
@@ -2062,9 +2062,9 @@ #define CCL_EXECUTE_BUF_SIZE 1024
if (TYPE_RANGED_FIXNUMP (int, AREF (status, i)))
ccl.reg[i] = XFIXNUM (AREF (status, i));
}
- if (FIXNUMP (AREF (status, i)))
+ if (FIXNUMP (AREF (status, 8)))
{
- EMACS_INT ic = XFIXNUM (AREF (status, i));
+ EMACS_INT ic = XFIXNUM (AREF (status, 8));
if (ccl.ic < ic && ic < ccl.size)
ccl.ic = ic;
}
diff --git a/src/dosfns.c b/src/dosfns.c
index fb5bcc9ad3..635f29bd65 100644
--- a/src/dosfns.c
+++ b/src/dosfns.c
@@ -72,16 +72,16 @@ DEFUN ("int86", Fint86, Sint86, 2, 2, 0,
if (no < 0 || no > 0xff || ASIZE (registers) != 8)
return Qnil;
for (i = 0; i < 8; i++)
- CHECK_FIXNAT (AREF (registers, i));
+ CHECK_FIXNUM (AREF (registers, i));
- inregs.x.ax = (unsigned long) XFIXNAT (AREF (registers, 0));
- inregs.x.bx = (unsigned long) XFIXNAT (AREF (registers, 1));
- inregs.x.cx = (unsigned long) XFIXNAT (AREF (registers, 2));
- inregs.x.dx = (unsigned long) XFIXNAT (AREF (registers, 3));
- inregs.x.si = (unsigned long) XFIXNAT (AREF (registers, 4));
- inregs.x.di = (unsigned long) XFIXNAT (AREF (registers, 5));
- inregs.x.cflag = (unsigned long) XFIXNAT (AREF (registers, 6));
- inregs.x.flags = (unsigned long) XFIXNAT (AREF (registers, 7));
+ inregs.x.ax = (unsigned long) XFIXNUM (AREF (registers, 0));
+ inregs.x.bx = (unsigned long) XFIXNUM (AREF (registers, 1));
+ inregs.x.cx = (unsigned long) XFIXNUM (AREF (registers, 2));
+ inregs.x.dx = (unsigned long) XFIXNUM (AREF (registers, 3));
+ inregs.x.si = (unsigned long) XFIXNUM (AREF (registers, 4));
+ inregs.x.di = (unsigned long) XFIXNUM (AREF (registers, 5));
+ inregs.x.cflag = (unsigned long) XFIXNUM (AREF (registers, 6));
+ inregs.x.flags = (unsigned long) XFIXNUM (AREF (registers, 7));
int86 (no, &inregs, &outregs);
@@ -139,8 +139,8 @@ DEFUN ("msdos-memput", Fdos_memput, Sdos_memput, 2, 2, 0,
for (i = 0; i < len; i++)
{
- CHECK_FIXNAT (AREF (vector, i));
- buf[i] = (unsigned char) XFIXNAT (AREF (vector, i)) & 0xFF;
+ CHECK_FIXNUM (AREF (vector, i));
+ buf[i] = (unsigned char) XFIXNUM (AREF (vector, i)) & 0xFF;
}
dosmemput (buf, len, offs);
diff --git a/src/lisp.h b/src/lisp.h
index 077d236065..a0619e64f2 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1195,7 +1195,7 @@ XFIXNUM_RAW (Lisp_Object a)
INLINE EMACS_INT
XFIXNUM (Lisp_Object a)
{
- eassume (FIXNUMP (a));
+ eassert (FIXNUMP (a));
return XFIXNUM_RAW (a);
}
@@ -1209,7 +1209,7 @@ XUFIXNUM_RAW (Lisp_Object a)
INLINE EMACS_UINT
XUFIXNUM (Lisp_Object a)
{
- eassume (FIXNUMP (a));
+ eassert (FIXNUMP (a));
return XUFIXNUM_RAW (a);
}
@@ -2828,9 +2828,11 @@ FIXNATP (Lisp_Object x)
INLINE EMACS_INT
XFIXNAT (Lisp_Object a)
{
- eassume (FIXNATP (a));
+ eassert (FIXNUMP (a));
EMACS_INT int0 = Lisp_Int0;
- return USE_LSB_TAG ? XFIXNUM (a) : XLI (a) - (int0 << VALBITS);
+ EMACS_INT result = USE_LSB_TAG ? XFIXNUM (a) : XLI (a) - (int0 << VALBITS);
+ eassume (0 <= result);
+ return result;
}
INLINE bool
--
2.21.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 8:28 ` Paul Eggert
@ 2019-06-27 13:17 ` Pip Cet
2019-06-27 13:37 ` Eli Zaretskii
2019-06-27 19:38 ` Paul Eggert
0 siblings, 2 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-27 13:17 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370
On Thu, Jun 27, 2019 at 8:28 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> Pip Cet wrote:
> > I'd prefer if the code were written as though FIXNATs weren't FIXNUMs at all.
>
> I'm more of the opposite opinion. The original reason for XFIXNAT (under its old
> name) was performance, because long ago Emacs put the type tag in the most
> significant bits, and it was faster to mask it out than to smear the sign bit
> when you knew the fixnum was nonnegative. Nowadays that original reason is
> obsolete because the tags are in the least significant bits and XFIXNAT is just
> an alias for XFIXNUM, and if it were up to me we'd get rid of XFIXNAT entirely,
> and just use XFIXNUM. But old habits die hard....
I actually think that would be best, so we're only disagreeing about
what the second-best solution is :-)
> > ascent = image_spec_value (spec, QCascent, NULL);
> > if (FIXNUMP (ascent))
> > img->ascent = XFIXNAT (ascent);
> >
> > in image.c is safe, because lookup_image is called only after valid_image_p.
>
> Sorry, you've lost me. How does valid_image_p guarantee that 'ascent' (if a
> fixnum) is in the range 0..INT_MAX? Because if it's not in that range, the above
> code could trap.
valid_image_p only returns true if ->valid_p returns true, which only
happens when parse_image_spec returns true, which only happens if
:ascent is not present, Qcenter, or a fixnum in the 0..100 range.
> > I'd prefer leaving the macros for now
>
> I actually did it that way at first, but that failed miserably as the macros
> evaluated their arguments more than once when debugging , and lots of code
> expects them to behave like functions and evaluate their arguments exactly once.
> So functions it is.
>
> > eassume (inline_function (x))
> > doesn't work very well on current GCC, while eassume (MACRO (x)) is
> > fine, so I'm sometimes building with DEFINE_KEY_OPS_AS_MACROS.
>
> Hmm, I see I should have helped GCC more there. First, the two new eassumes
> should just be easserts as the assumptions are not helpful to the compiler.
> Second, I should restore the eassume that tells GCC that XFIXNAT returns a
> nonnegative integer.
>
> > (The
> > eassume/eassert situation is something else I've been planning to look
> > at in more detail, though debugging it will be easier when
> > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91005 is fixed).
>
> Sorry, I don't follow. That bug report seems to be about nested C functions; why
> is it relevant to Emacs, which doesn't use nested C functions?
I don't think it's really relevant to this discussion, though there
are similarities:
----
Just like having both XFIXNUM and XFIXNAT is confusing, so is having
eassume and eassert. My idea is to define eassume as follows:
#define eassume(cond) (__builtin_constant_p (!(cond) == !(cond)) ?
((cond) ? 0 : __builtin_unreachable ()) : 0)
That would catch most (not all) cases in which eassume() causes code
to be emitted. I wanted to test this, and in particular to catch
false negatives like this eassume in lisp.h:
eassume (0 <= i && i < bool_vector_size (a));
(GCC isn't smart enough to derive any information from the RHS of the
&&, so __builtin_constant_p (!(cond) == !(cond)) is false, but we only
actually use the LHS of the &&...)
So I resorted to this old trick to see whether code was being emitted:
int c;
asm(".pushsection .text.bad");
c = (cond);
asm(".popsection");
but that hack no longer works with -g3. Instead, we can define an
inner function with the relevant code, put it into a magic section,
then confirm that only the trivial function actually ended up in the
magic section.
----
> > I don't see how either ccl.c or image.c are buggy for out-of-range
> > integer arguments.
>
> For example, ccl.c in master has this:
>
> if (FIXNUMP (AREF (status, i)))
> {
> i = XFIXNAT (AREF (status, 8));
> if (ccl.ic < i && i < ccl.size)
> ccl.ic = i;
> }
>
> where i is a 32-bit int. If AREF (status, 8) is the 62-bit Lisp fixnum with
> value '2 * (EMACS_INT) INT_MAX + ccl.ic + 3', the assignment to i overflows,
> which can trap. Even if the assignment doesn't trap but merely wraps around, the
> next line won't do range checking correctly.
Ah. Right. Thanks.
> > When you want a position, you want a FIXNAT,
> > not a negative number, so CHECK_FIXNAT_COERCE_MARKER everywhere would
> > catch those cases where someone passes an obviously invalid argument.
>
> But why stop there?
Because it's as far as we get only replacing characters in our code,
not typing in new ones.
> 0 is an obviously invalid argument for buffers. :-)
True, but then we'd have to add extra characters to distinguish the
buffer and string cases.
> I think
> this is just an area where we disagree - I want a range check where you want a
> type check. It's safe either way and the range check is a tad faster (as the
> range check needs to be done anyway) and results in a crisper diagnostic when it
> fails.
Okay, let's agree to disagree on what the second-best solution is here.
> > I'd prefer not touching the dosfns.c code, for the simple reason that
> > if anyone still uses it, they might rely on the broken behavior.
>
> We can't leave it alone, as XFIXNAT is now checking that its result is
> nonnegative when debugging is enabled.
Do DOS machines have enough memory to run Emacs with debugging
enabled? :-) You're absolutely right, of course.
Thanks for the changes! I think this code is a bit hard to read:
margin = image_spec_value (spec, QCmargin, NULL);
if (RANGED_FIXNUMP (0, margin, INT_MAX))
img->vmargin = img->hmargin = XFIXNAT (margin);
else if (CONSP (margin))
{
img->hmargin = XFIXNAT (XCAR (margin));
img->vmargin = XFIXNAT (XCDR (margin));
}
because range is checked for the :margin 3 case but not the :margin
'(3 4) case; I'm perfectly okay with taking a list and checking it
twice.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 13:17 ` Pip Cet
@ 2019-06-27 13:37 ` Eli Zaretskii
2019-06-27 19:38 ` Paul Eggert
1 sibling, 0 replies; 36+ messages in thread
From: Eli Zaretskii @ 2019-06-27 13:37 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, eggert
> From: Pip Cet <pipcet@gmail.com>
> Date: Thu, 27 Jun 2019 13:17:46 +0000
> Cc: 36370@debbugs.gnu.org
>
> Do DOS machines have enough memory to run Emacs with debugging
> enabled? :-)
Yes.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 13:17 ` Pip Cet
2019-06-27 13:37 ` Eli Zaretskii
@ 2019-06-27 19:38 ` Paul Eggert
2019-06-27 19:56 ` Pip Cet
1 sibling, 1 reply; 36+ messages in thread
From: Paul Eggert @ 2019-06-27 19:38 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370-done
[-- Attachment #1: Type: text/plain, Size: 1522 bytes --]
>> if it were up to me we'd get rid of XFIXNAT entirely,
>> and just use XFIXNUM. But old habits die hard....
>
> I actually think that would be best, so we're only disagreeing about
> what the second-best solution is :-)
Removing XFIXNAT would be outside the scope of this patch. However, if
we're already fixing the code for some other reason and if the XFIXNATs
are confusing that code, we might as well replace them with XFIXNUMs.
The attached patch does that.
> valid_image_p only returns true if ->valid_p returns true, which only
> happens when parse_image_spec returns true, which only happens if
> :ascent is not present, Qcenter, or a fixnum in the 0..100 range.
Thanks for explaining. The attached patch removes the unnecessary range
checks that I proposed.
> My idea is to define eassume as follows:
>
> #define eassume(cond) (__builtin_constant_p (!(cond) == !(cond)) ?
> ((cond) ? 0 : __builtin_unreachable ()) : 0)
That would generate worse code in some cases, since after (say) "eassume
(i >= 0); return i/2;" where i is a variable, GCC would not be able to
optimize i/2 into i>>1 because GCC would not know that i is nonnegative.
The main point of eassume (as opposed to eassert) is to enable
optimizations like that.
> think this code is a bit hard to read:
The attached patch changes that to use XFIXNUM instead of XFIXNAT, along
the lines discussed above.
Thanks for the review. In addition to the already-mentioned patches, I
installed the attached and am closing the bug report.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Omit-a-few-minor-unnecessary-range-checks.patch --]
[-- Type: text/x-patch; name="0001-Omit-a-few-minor-unnecessary-range-checks.patch", Size: 2976 bytes --]
From 7219f991a94015e06851d54b77acb4b1161749a5 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 27 Jun 2019 12:31:27 -0700
Subject: [PATCH] Omit a few minor unnecessary range checks
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Based on Pip Cet’s review (Bug#36370#19).
* src/fileio.c (Fdo_auto_save):
* src/image.c (lookup_image):
* src/textprop.c (Fnext_single_char_property_change):
Prefer XFIXNUM to XFIXNAT for clarity and consistency with
neighboring code, and to avoid unnecessary range checks.
* src/image.c (lookup_image): Omit unnecessary range checks.
---
src/fileio.c | 2 +-
src/image.c | 12 ++++++------
src/textprop.c | 4 ++--
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/fileio.c b/src/fileio.c
index 61e10dac47..ed1d2aedf3 100644
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -5852,7 +5852,7 @@ DEFUN ("do-auto-save", Fdo_auto_save, Sdo_auto_save, 0, 2, "",
spare the user annoying messages. */
&& XFIXNUM (BVAR (b, save_length)) > 5000
&& (growth_factor * (BUF_Z (b) - BUF_BEG (b))
- < (growth_factor - 1) * XFIXNAT (BVAR (b, save_length)))
+ < (growth_factor - 1) * XFIXNUM (BVAR (b, save_length)))
/* These messages are frequent and annoying for `*mail*'. */
&& !NILP (BVAR (b, filename))
&& NILP (no_message))
diff --git a/src/image.c b/src/image.c
index bbf25b4d58..f3d6508f46 100644
--- a/src/image.c
+++ b/src/image.c
@@ -2385,18 +2385,18 @@ lookup_image (struct frame *f, Lisp_Object spec)
#endif
ascent = image_spec_value (spec, QCascent, NULL);
- if (RANGED_FIXNUMP (0, ascent, INT_MAX))
- img->ascent = XFIXNAT (ascent);
+ if (FIXNUMP (ascent))
+ img->ascent = XFIXNUM (ascent);
else if (EQ (ascent, Qcenter))
img->ascent = CENTERED_IMAGE_ASCENT;
margin = image_spec_value (spec, QCmargin, NULL);
- if (RANGED_FIXNUMP (0, margin, INT_MAX))
- img->vmargin = img->hmargin = XFIXNAT (margin);
+ if (FIXNUMP (margin))
+ img->vmargin = img->hmargin = XFIXNUM (margin);
else if (CONSP (margin))
{
- img->hmargin = XFIXNAT (XCAR (margin));
- img->vmargin = XFIXNAT (XCDR (margin));
+ img->hmargin = XFIXNUM (XCAR (margin));
+ img->vmargin = XFIXNUM (XCDR (margin));
}
relief = image_spec_value (spec, QCrelief, NULL);
diff --git a/src/textprop.c b/src/textprop.c
index 3026ec7e99..9023f4efa0 100644
--- a/src/textprop.c
+++ b/src/textprop.c
@@ -799,10 +799,10 @@ DEFUN ("next-single-char-property-change", Fnext_single_char_property_change,
else
CHECK_FIXNUM_COERCE_MARKER (limit);
- if (XFIXNAT (position) >= XFIXNUM (limit))
+ if (XFIXNUM (position) >= XFIXNUM (limit))
{
position = limit;
- if (XFIXNAT (position) > ZV)
+ if (XFIXNUM (position) > ZV)
XSETFASTINT (position, ZV);
}
else
--
2.21.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 19:38 ` Paul Eggert
@ 2019-06-27 19:56 ` Pip Cet
2019-06-27 21:13 ` Paul Eggert
[not found] ` <5284eb58-3560-da42-d1d1-3bdb930eae49@cs.ucla.edu>
0 siblings, 2 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-27 19:56 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370-done
On Thu, Jun 27, 2019 at 7:38 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> >> if it were up to me we'd get rid of XFIXNAT entirely,
> >> and just use XFIXNUM. But old habits die hard....
> >
> > I actually think that would be best, so we're only disagreeing about
> > what the second-best solution is :-)
>
> Removing XFIXNAT would be outside the scope of this patch. However, if
> we're already fixing the code for some other reason and if the XFIXNATs
> are confusing that code, we might as well replace them with XFIXNUMs.
> The attached patch does that.
Cool!
> > My idea is to define eassume as follows:
> >
> > #define eassume(cond) (__builtin_constant_p (!(cond) == !(cond)) ?
> > ((cond) ? 0 : __builtin_unreachable ()) : 0)
>
> That would generate worse code in some cases, since after (say) "eassume
> (i >= 0); return i/2;" where i is a variable, GCC would not be able to
> optimize i/2 into i>>1 because GCC would not know that i is nonnegative.
I'm confused, and I'm not sure what you're saying.
int f (int i)
{
eassume (i >= 0);
printf("%d\n", i & 0x80000000);
return 0;
}
The eassume tells GCC i is nonnegative, since (!(i >= 0) == !(i >= 0))
is indeed a constant. So the code generated is:
xorl %esi, %esi
movl $.LC0, %edi
xorl %eax, %eax
call printf
which is as it should be. For your example, it is:
movl %edi, %eax
sarl %eax
ret
with the eassume and
movl %edi, %eax
shrl $31, %eax
addl %edi, %eax
sarl %eax
ret
without.
> The main point of eassume (as opposed to eassert) is to enable
> optimizations like that.
Yes, indeed. I don't see how they are disabled by my proposed definition.
> Thanks for the review. In addition to the already-mentioned patches, I
> installed the attached and am closing the bug report.
Thanks!
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-27 19:56 ` Pip Cet
@ 2019-06-27 21:13 ` Paul Eggert
[not found] ` <5284eb58-3560-da42-d1d1-3bdb930eae49@cs.ucla.edu>
1 sibling, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-27 21:13 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Gnulib bugs
[-- Attachment #1: Type: text/plain, Size: 770 bytes --]
On 6/27/19 12:56 PM, Pip Cet wrote:
> The eassume tells GCC i is nonnegative, since (!(i >= 0) == !(i >= 0))
> is indeed a constant.
Ah! Thanks, I didn't catch that subtle point. Would the attached patch
to verify.h address that problem? This patch is for Gnulib, but would
propagate into Emacs.
I tried this out with Emacs master and although it did change the
machine code subtly I didn't have the patience to see whether the
changes were likely to improve performance. The changes did grow the
Emacs text segment from 2556193 to 2557657 bytes (a 0.06% growth), which
is not a good sign. This was on Fedora 30 x86-64 with a default Emacs build.
I'll CC: this to bug-gnulib since it's a Gnulib issue. I have not
installed this patch into Gnulib on savannah.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-verify-tweak-assume-performance.patch --]
[-- Type: text/x-patch; name="0001-verify-tweak-assume-performance.patch", Size: 1822 bytes --]
From 9a5a83937544e7c127026fcf32030f7dbaa5766c Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Thu, 27 Jun 2019 14:01:53 -0700
Subject: [PATCH] =?UTF-8?q?verify:=20tweak=20=E2=80=98assume=E2=80=99=20pe?=
=?UTF-8?q?rformance?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Suggested by Pip Cet (Bug#36370#30).
* lib/verify.h (assume): Use __builtin_constant_p to generate
better code in recent GCC.
---
ChangeLog | 7 +++++++
lib/verify.h | 6 ++++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 5ae108e25..7059f4f2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2019-06-27 Paul Eggert <eggert@cs.ucla.edu>
+
+ verify: tweak ‘assume’ performance
+ Suggested by Pip Cet (Bug#36370#30).
+ * lib/verify.h (assume): Use __builtin_constant_p to generate
+ better code in recent GCC.
+
2019-06-26 Paul Eggert <eggert@cs.ucla.edu>
strverscmp: sync from glibc
diff --git a/lib/verify.h b/lib/verify.h
index f8e4eff02..9b015c693 100644
--- a/lib/verify.h
+++ b/lib/verify.h
@@ -263,9 +263,11 @@ template <int w>
accordingly. R should not have side-effects; it may or may not be
evaluated. Behavior is undefined if R is false. */
-#if (__has_builtin (__builtin_unreachable) \
+#if ((__has_builtin (__builtin_constant_p) \
+ && __has_builtin (__builtin_unreachable)) \
|| 4 < __GNUC__ + (5 <= __GNUC_MINOR__))
-# define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
+# define assume(R) (!__builtin_constant_p (!(R) == !(R)) || (R) \
+ ? (void) 0 : __builtin_unreachable ())
#elif 1200 <= _MSC_VER
# define assume(R) __assume (R)
#elif ((defined GCC_LINT || defined lint) \
--
2.21.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <5284eb58-3560-da42-d1d1-3bdb930eae49@cs.ucla.edu>
@ 2019-06-27 21:37 ` Pip Cet
2019-06-27 23:45 ` Bruno Haible
[not found] ` <2715311.ceefYqj39C@omega>
2 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-27 21:37 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370, Gnulib bugs
On Thu, Jun 27, 2019 at 9:13 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> On 6/27/19 12:56 PM, Pip Cet wrote:
> > The eassume tells GCC i is nonnegative, since (!(i >= 0) == !(i >= 0))
> > is indeed a constant.
>
> Ah! Thanks, I didn't catch that subtle point. Would the attached patch
> to verify.h address that problem? This patch is for Gnulib, but would
> propagate into Emacs.
Eventually, I think that would be a good idea. But right now, I would
like to figure out why GCC isn't generating code that's as good as it
should be.
> I tried this out with Emacs master and although it did change the
> machine code subtly I didn't have the patience to see whether the
> changes were likely to improve performance. The changes did grow the
> Emacs text segment from 2556193 to 2557657 bytes (a 0.06% growth), which
> is not a good sign. This was on Fedora 30 x86-64 with a default Emacs build.
So far, what I've found is that
eassume (0 <= i && i < bool_vector_size (a));
doesn't work with my patch. Tweaking that to
eassume (0 <= i);
eassume (i < bool_vector_size (a));
appears to help a little, but it's a good idea to avoid generating a
call to a function that might be out-lined by the compiler, in any
case.
One thing I've neglected to look at in detail, because Emacs isn't
affected by it, is how this works with pure functions.
> I'll CC: this to bug-gnulib since it's a Gnulib issue. I have not
> installed this patch into Gnulib on savannah.
I'd be extremely interested in any comments people might have!
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <5284eb58-3560-da42-d1d1-3bdb930eae49@cs.ucla.edu>
2019-06-27 21:37 ` Pip Cet
@ 2019-06-27 23:45 ` Bruno Haible
[not found] ` <2715311.ceefYqj39C@omega>
2 siblings, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-27 23:45 UTC (permalink / raw)
To: bug-gnulib; +Cc: 36370, Paul Eggert, Pip Cet
Hi Paul,
> I'll CC: this to bug-gnulib since it's a Gnulib issue. I have not
> installed this patch into Gnulib on savannah.
Can you please show an example code on which the change makes a difference?
I used this test program
==================================== foo.c ====================================
#include <stdio.h>
//#define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
#define assume(R) (!__builtin_constant_p (!(R) == !(R)) || (R) ? (void) 0 : __builtin_unreachable ())
int f_generic (int i)
{
printf("%d\n", i & 0x80000000);
return 0;
}
int f_condition (int i)
{
if (i >= 0)
printf("%d\n", i & 0x80000000);
return 0;
}
int f_assume (int i)
{
assume (i >= 0);
printf("%d\n", i & 0x80000000);
return 0;
}
===============================================================================
$ gcc -O2 -m32 -S foo.c && fgrep -v .cfi foo.s
and the code is the same, regardless of which definition of 'assume' I enable.
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2715311.ceefYqj39C@omega>
@ 2019-06-28 0:04 ` Paul Eggert
2019-06-28 11:06 ` Pip Cet
1 sibling, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-28 0:04 UTC (permalink / raw)
To: Bruno Haible, bug-gnulib; +Cc: 36370, Pip Cet
[-- Attachment #1: Type: text/plain, Size: 449 bytes --]
On 6/27/19 4:45 PM, Bruno Haible wrote:
> Can you please show an example code on which the change makes a difference?
Not easily, I'm afraid. I grabbed the latest Emacs master
<https://git.savannah.gnu.org/git/emacs.git>, ran './configure; make
CFLAGS=-O2', edited the emacs lib/verify.h, and ran 'make CFLAGS=-O2'
again. The biggest percentage change in text size was in
src/regex-emacs.o. I'll attach the two respective .s files, compressed.
[-- Attachment #2: regex-emacs-old.s.gz --]
[-- Type: application/gzip, Size: 41374 bytes --]
[-- Attachment #3: regex-emacs-new.s.gz --]
[-- Type: application/gzip, Size: 41750 bytes --]
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2715311.ceefYqj39C@omega>
2019-06-28 0:04 ` Paul Eggert
@ 2019-06-28 11:06 ` Pip Cet
2019-06-28 12:14 ` Bruno Haible
[not found] ` <8979488.cRkkfcT1mV@omega>
1 sibling, 2 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-28 11:06 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
On Thu, Jun 27, 2019 at 11:45 PM Bruno Haible <bruno@clisp.org> wrote:
> Can you please show an example code on which the change makes a difference?
int main(void)
{
eassume (printf("hi\n"));
return 0;
}
Or, more realistically:
extern int potentially_inlined_function(int i);
int main(void)
{
...
eassume(potentially_inlined_function(i));
return i >= 0;
}
With the old gnulib eassume, the programmer has to know whether
potentially_inlined_function is inlined (in which case the eassume
appears to be treated as a nop) or not (in which case a potentially
expensive external function call is generated). With the new eassume,
these cases are distinguished by the compiler.
This makes it safe to use function expressions in eassume, whether the
function is inlined or not.
(That GCC doesn't actually do very much with this information is a
separate issue).
This approach does fail for certain compound expressions passed as
arguments to eassume:
eassume(i >= 0 && i < complicated_function ());
will not "split" the && expression, so it'll behave differently from
eassume(i >= 0);
eassume(i < complicated_function ());
But even in those cases, this approach is better than the old approach
of actually evaluating complicated_function.
At first, I thought it would be better to have a __builtin_assume
expression at the GCC level, but even that would have to have "either
evaluate the entire condition expression, or evaluate none of it"
semantics. We'll just have to get used to splitting our eassumes, I
think.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-28 11:06 ` Pip Cet
@ 2019-06-28 12:14 ` Bruno Haible
[not found] ` <8979488.cRkkfcT1mV@omega>
1 sibling, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-28 12:14 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Pip Cet wrote:
> Or, more realistically:
>
> extern int potentially_inlined_function(int i);
>
> int main(void)
> {
> ...
> eassume(potentially_inlined_function(i));
> return i >= 0;
> }
OK, I see...
> This makes it safe to use function expressions in eassume, whether the
> function is inlined or not.
By "safe" you mean that you want the function call to not be evaluated.
You are mentioning a limitation:
> eassume(i >= 0 && i < complicated_function ());
>
> will not "split" the && expression, so it'll behave differently from
>
> eassume(i >= 0);
> eassume(i < complicated_function ());
And I would mention a regression: When -flto is in use and the expression
invokes an external potentially-inlined function, the old 'assume' would
work fine, i.e. do optimizations across compilation-unit boundaries.
Whereas the new 'assume' does not.
Test case:
================================ foo.c =================================
#include <stdio.h>
#define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
//#define assume(R) (!__builtin_constant_p (!(R) == !(R)) || (R) ? (void) 0 : __builtin_unreachable ())
extern int complicated (int i);
extern int nonnegative (int i);
int f_generic (int i)
{
printf("%d\n", i & 0x80000000);
return 0;
}
int f_condition (int i)
{
if (complicated (i) && i >= 0)
printf("%d\n", i & 0x80000000);
return 0;
}
int f_assume (int i)
{
assume (complicated (i) && i >= 0);
printf("%d\n", i & 0x80000000);
return 0;
}
================================= bar.c ================================
int complicated (int i) { return (i & 7) == 3; }
int nonnegative (int i) { return i >= 0; }
========================================================================
$ gcc -O2 -m32 -flto foo.c bar.c -shared -o libfoo.so && objdump --disassemble libfoo.so
With the old 'assume':
000005f0 <f_assume>:
5f0: 83 ec 10 sub $0x10,%esp
5f3: 6a 00 push $0x0
5f5: 68 74 06 00 00 push $0x674
5fa: 6a 01 push $0x1
5fc: e8 fc ff ff ff call 5fd <f_assume+0xd>
601: 31 c0 xor %eax,%eax
603: 83 c4 1c add $0x1c,%esp
606: c3 ret
607: 89 f6 mov %esi,%esi
609: 8d bc 27 00 00 00 00 lea 0x0(%edi,%eiz,1),%edi
With the new 'assume':
00000610 <f_generic>:
610: 83 ec 10 sub $0x10,%esp
613: 8b 44 24 14 mov 0x14(%esp),%eax
617: 25 00 00 00 80 and $0x80000000,%eax
61c: 50 push %eax
61d: 68 48 06 00 00 push $0x648
622: 6a 01 push $0x1
624: e8 fc ff ff ff call 625 <f_generic+0x15>
629: 31 c0 xor %eax,%eax
62b: 83 c4 1c add $0x1c,%esp
62e: c3 ret
62f: 90 nop
00000630 <f_assume>:
630: eb de jmp 610 <f_generic>
> But even in those cases, this approach is better than the old approach
> of actually evaluating complicated_function.
I disagree that it is better:
1. The new 'assume' is worse when -flto is in use.
2. You recommend to users to split assume(A && B) into assume(A); assume(B);
which is unnatural.
> At first, I thought it would be better to have a __builtin_assume
> expression at the GCC level, but even that would have to have "either
> evaluate the entire condition expression, or evaluate none of it"
> semantics.
No. At GCC level, it could have a "make the maximum of inferences - across
all optimization phases -, but evaluate none of it" semantics.
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <8979488.cRkkfcT1mV@omega>
@ 2019-06-28 12:29 ` Bruno Haible
2019-06-28 13:51 ` Pip Cet
[not found] ` <CAOqdjBfS99UpLZ-qLe4=FMXMsr+T3LUvJEsf_gfmF6wwLbqgOw@mail.gmail.com>
2 siblings, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-28 12:29 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Oops, the test case that I meant to show is this one:
================================ foo.c =================================
#include <stdio.h>
#define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
//#define assume(R) (!__builtin_constant_p (!(R) == !(R)) || (R) ? (void) 0 : __builtin_unreachable ())
extern int complicated (int i);
extern int nonnegative (int i);
int f_generic (int i)
{
printf("%d\n", i & 0x80000000);
return 0;
}
int f_condition (int i)
{
if (complicated (i) && nonnegative (i))
printf("%d\n", i & 0x80000000);
return 0;
}
int f_assume (int i)
{
assume (complicated (i) && nonnegative (i));
printf("%d\n", i & 0x80000000);
return 0;
}
================================ bar.c =================================
int complicated (int i) { return (i & 7) == 3; }
int nonnegative (int i) { return i >= 0; }
========================================================================
The results are as shown: the optimization in f_assume is performed
with the old 'assume' definition, but not with the new one.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <8979488.cRkkfcT1mV@omega>
2019-06-28 12:29 ` Bruno Haible
@ 2019-06-28 13:51 ` Pip Cet
[not found] ` <CAOqdjBfS99UpLZ-qLe4=FMXMsr+T3LUvJEsf_gfmF6wwLbqgOw@mail.gmail.com>
2 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-28 13:51 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
On Fri, Jun 28, 2019 at 12:14 PM Bruno Haible <bruno@clisp.org> wrote:
> Pip Cet wrote:
> > This makes it safe to use function expressions in eassume, whether the
> > function is inlined or not.
>
> By "safe" you mean that you want the function call to not be evaluated.
Sorry, sloppy wording there. You're right.
> You are mentioning a limitation:
>
> > eassume(i >= 0 && i < complicated_function ());
> >
> > will not "split" the && expression, so it'll behave differently from
> >
> > eassume(i >= 0);
> > eassume(i < complicated_function ());
>
> And I would mention a regression: When -flto is in use and the expression
> invokes an external potentially-inlined function, the old 'assume' would
> work fine, i.e. do optimizations across compilation-unit boundaries.
Sorry, can't reproduce that here. I'm sure the changes I need to make
are obvious once I've found them, but can you let me know your gcc
version?
> > But even in those cases, this approach is better than the old approach
> > of actually evaluating complicated_function.
>
> I disagree that it is better:
Sorry to be pedantic, but do you disagree that it is better in these
cases, or in general? The latter is a question that I'm trying to find
the answer to, but in these specific cases, it clearly is better.
(Just in the interest of full disclosure, I described the idea in a
different context; I think it's a neat hack, and I'm trying to figure
out whether it has practical applications, but if it doesn't then I
won't feel there's continuing disagreement).
> 1. The new 'assume' is worse when -flto is in use.
Maybe. Even if it is, though, that's a GCC limitation which I consider
likely to be fixable; your estimation of that may vary, of course.
> 2. You recommend to users to split assume(A && B) into assume(A); assume(B);
> which is unnatural.
I make that recommendation independently of which assume is in use.
In practice, combining a complicated expression with a simple one in
an eassume is almost always not what you want to do. It's way too easy
to do something like
eassume(ptr->field >= 0 && f(ptr));
when what you mean is
eassume(ptr->field >= 0);
eassume(f(ptr));
(As an unusual special case, consider:
{
printf("%d\n", i & 0x80000000);
assume(i >= 0 && complicated_function());
}
which would generate different code from
{
printf("%d\n", i & 0x80000000);
assume(i >= 0);
assume(complicated_function());
})
Combining two simple expressions and not getting the right result
appears, at this point, to run into a GCC limitation, but I'm not sure
where.
> > At first, I thought it would be better to have a __builtin_assume
> > expression at the GCC level, but even that would have to have "either
> > evaluate the entire condition expression, or evaluate none of it"
> > semantics.
>
> No. At GCC level, it could have a "make the maximum of inferences - across
> all optimization phases -, but evaluate none of it" semantics.
There's no contradiction there: I'm saying that the programmer is
allowed to assume that the expression passed to assume either has been
evaluated, or hasn't been, with no in-between interpretations allowed
to the compiler. That means assume (A && B) isn't equivalent, in
general, to assume (A); assume (B); My suspicion is that the latter is
almost always what is intended.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBfS99UpLZ-qLe4=FMXMsr+T3LUvJEsf_gfmF6wwLbqgOw@mail.gmail.com>
@ 2019-06-28 17:46 ` Paul Eggert
2019-06-28 19:11 ` Bruno Haible
` (2 subsequent siblings)
3 siblings, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-28 17:46 UTC (permalink / raw)
To: Pip Cet, Bruno Haible; +Cc: 36370, bug-gnulib
Pip Cet wrote:
> It's way too easy
> to do something like
>
> eassume(ptr->field >= 0 && f(ptr));
>
> when what you mean is
>
> eassume(ptr->field >= 0);
> eassume(f(ptr));
These mean the same thing. Both tell the compiler that a certain condition (A &&
B) is known to be true, and that behavior is undefined if (A && B) is false. The
fact that Gnulib+GCC implements them differently is a quality-of-implementation
issue, not a semantics issue.
> I'm saying that the programmer is
> allowed to assume that the expression passed to assume either has been
> evaluated, or hasn't been, with no in-between interpretations allowed
> to the compiler.
I don't see why that assumption is valid. It's OK if GCC partially evaluates the
expression. As a silly example, eassume (0 * dump_core () + getchar ()) is not
required to call dump_core, even if the compiler generates a call to getchar.
Perhaps we should change the comments in verify.h to make this point clearer.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBfS99UpLZ-qLe4=FMXMsr+T3LUvJEsf_gfmF6wwLbqgOw@mail.gmail.com>
2019-06-28 17:46 ` Paul Eggert
@ 2019-06-28 19:11 ` Bruno Haible
[not found] ` <a293f2fe-99b3-3776-f27b-35e3a93d1d34@cs.ucla.edu>
[not found] ` <11002295.LrvMqknVDZ@omega>
3 siblings, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-28 19:11 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Pip Cet wrote:
> Sorry, can't reproduce that here. I'm sure the changes I need to make
> are obvious once I've found them, but can you let me know your gcc
> version?
I reproduce this with GCC versions 5.4.0, 6.5.0, 7.4.0, 8.3.0, and 9.1.0.
1. Take the files foo.c and bar.c from
<https://lists.gnu.org/archive/html/bug-gnulib/2019-06/msg00096.html>
2. Compile and disassemble:
gcc -O2 -m32 -flto foo.c bar.c -shared -o libfoo.so && objdump --disassemble
libfoo.so
Observe that f_assume pushes an immediate $0 argument on the stack for the
function call.
3. Enable the second 'assume' definition instead of the first one.
4. Compile and disassemble:
gcc -O2 -m32 -flto foo.c bar.c -shared -o libfoo.so && objdump --disassemble
libfoo.so
Observe that f_assume is now an alias of f_generic, that pushes a
computed value as argument on the stack for the function call (push %eax).
> Sorry to be pedantic, but do you disagree that it is better in these
> cases, or in general?
I disagree that it is better in general.
You're apparently setting out a high goal for the 'assume' macro:
(1) that the programmer may call it with an expression that involves
function calls,
(2) that the generated code will never include these function calls,
because the generated code with the 'assume' invocation should be
optimized at least as well as the generated code without the
'assume' invocation.
I'm adding certain quality criteria:
- It is not "good" if a construct behaves unpredictably, that is,
if it hard to document precisely how it will behave. (*)
- It is not "good" if the behaviour with no LTO is different from
the behaviour with LTO.
The implementation with the __builtin_constant, while attaining the
goals (1) and (2), does not satisfy the two quality criteria.
I believe the only way to attain the goals and the quality criteria
is, as you suggested, to ask the GCC people to add a __builtin_assume
built-in.
> > 1. The new 'assume' is worse when -flto is in use.
>
> Maybe. Even if it is, though, that's a GCC limitation which I consider
> likely to be fixable
Yes, *maybe* the GCC people can change the semantics of __builtin_constant_p
so that it is effectively computed at link-time, rather than when a single
compilation unit gets compiled. Or maybe not. I don't know...
> It's way too easy to do something like
>
> eassume(ptr->field >= 0 && f(ptr));
>
> when what you mean is
>
> eassume(ptr->field >= 0);
> eassume(f(ptr));
I argue that it's unnatural if the two don't behave exactly the same.
Like everyone expects that
x = foo ? yes : no;
is equivalent to
if (foo) x = yes; else x = no;
And everyone expects that
if (A && B) { ... }
is equivalent to
if (A) if (B) { ... }
Bruno
(*) My favourite example of this principle is tail-recursion elimination.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <a293f2fe-99b3-3776-f27b-35e3a93d1d34@cs.ucla.edu>
@ 2019-06-28 19:15 ` Pip Cet
2019-06-28 19:56 ` Bruno Haible
` (2 more replies)
0 siblings, 3 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-28 19:15 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370, Bruno Haible, bug-gnulib
On Fri, Jun 28, 2019 at 5:46 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> Pip Cet wrote:
> > It's way too easy
> > to do something like
> >
> > eassume(ptr->field >= 0 && f(ptr));
> >
> > when what you mean is
> >
> > eassume(ptr->field >= 0);
> > eassume(f(ptr));
>
> These mean the same thing.
I'm really convinced they don't. Can you humor me and explain why
they're equivalent?
I'm considering this test case:
====
int global;
extern int f(void);
#define eassume0(cond) ((cond) ? (void) 0 : __builtin_unreachable ())
#ifdef ASSUME_GNULIB
#define eassume eassume0
#else
#define eassume(cond) (__builtin_constant_p (!(cond) == !(cond)) ?
eassume0(cond) : (void) 0)
#endif
int main(void)
{
#ifdef TWO_ASSUMES
eassume (global == 0);
eassume (f ());
#else
eassume (global == 0 && f ());
#endif
global++;
}
====
with this external function:
====
extern int global;
int f(void)
{
return ++global;
}
====
I believe, and that is what my patch is based on, that the compiler
should be free to "use" the first eassume and ignore the second one,
resulting in this machine code:
movl $1, global(%rip)
xorl %eax, %eax
ret
No call to f, it just sets global.
Without -DTWO_ASSUMES, the compiler cannot split the assumption.
> Both tell the compiler that a certain condition (A &&
> B) is known to be true, and that behavior is undefined if (A && B) is false.
Again, no. The split eassumes tell the compiler that A, B, and A && B
would all evaluate to true or fail to evaluate. The single eassume()
only covers the last of those three cases.
> The
> fact that Gnulib+GCC implements them differently is a quality-of-implementation
> issue, not a semantics issue.
Are you really saying that the single-assume case is equivalent to the
single-instruction program?
> > I'm saying that the programmer is
> > allowed to assume that the expression passed to assume either has been
> > evaluated, or hasn't been, with no in-between interpretations allowed
> > to the compiler.
>
> I don't see why that assumption is valid. It's OK if GCC partially evaluates the
> expression. As a silly example, eassume (0 * dump_core () + getchar ()) is not
> required to call dump_core, even if the compiler generates a call to getchar.
That's because && implies a sequence point, and * doesn't.
> Perhaps we should change the comments in verify.h to make this point clearer.
I'm sorry to be selfish, but I'd really rather understand where I've
gone wrong, first.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-28 19:15 ` Pip Cet
@ 2019-06-28 19:56 ` Bruno Haible
2019-06-28 21:08 ` Pip Cet
2019-06-29 5:41 ` Paul Eggert
[not found] ` <87168b28-192b-6666-e9b6-9cdc2ed3917a@cs.ucla.edu>
2 siblings, 1 reply; 36+ messages in thread
From: Bruno Haible @ 2019-06-28 19:56 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
On Freitag, 28. Juni 2019 19:15:06 CEST Pip Cet wrote:
> On Fri, Jun 28, 2019 at 5:46 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> > Pip Cet wrote:
> > > It's way too easy
> > > to do something like
> > >
> > > eassume(ptr->field >= 0 && f(ptr));
> > >
> > > when what you mean is
> > >
> > > eassume(ptr->field >= 0);
> > > eassume(f(ptr));
> >
> > These mean the same thing.
>
> I'm really convinced they don't. Can you humor me and explain why
> they're equivalent?
>
> I'm considering this test case:
> ====
> int global;
>
> extern int f(void);
>
> #define eassume0(cond) ((cond) ? (void) 0 : __builtin_unreachable ())
> #ifdef ASSUME_GNULIB
> #define eassume eassume0
> #else
> #define eassume(cond) (__builtin_constant_p (!(cond) == !(cond)) ?
> eassume0(cond) : (void) 0)
> #endif
>
> int main(void)
> {
> #ifdef TWO_ASSUMES
> eassume (global == 0);
> eassume (f ());
> #else
> eassume (global == 0 && f ());
> #endif
>
> global++;
> }
> ====
> with this external function:
> ====
> extern int global;
>
> int f(void)
> {
> return ++global;
> }
> ====
>
> I believe, and that is what my patch is based on, that the compiler
> should be free to "use" the first eassume and ignore the second one,
> resulting in this machine code:
>
> movl $1, global(%rip)
> xorl %eax, %eax
> ret
For reference: This test case produces:
Options Result
none increment
-DASSUME_GNULIB increment
-DTWO_ASSUMES single-store
-DASSUME_GNULIB -DTWO_ASSUMES increment
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <11002295.LrvMqknVDZ@omega>
@ 2019-06-28 21:07 ` Pip Cet
2019-06-28 23:30 ` Bruno Haible
[not found] ` <2067160.1HRgjLhtDS@omega>
0 siblings, 2 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-28 21:07 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
On Fri, Jun 28, 2019 at 7:11 PM Bruno Haible <bruno@clisp.org> wrote:
> Pip Cet wrote:
> > Sorry, can't reproduce that here. I'm sure the changes I need to make
> > are obvious once I've found them, but can you let me know your gcc
> > version?
>
> I reproduce this with GCC versions 5.4.0, 6.5.0, 7.4.0, 8.3.0, and 9.1.0.
> 1. Take the files foo.c and bar.c from
> <https://lists.gnu.org/archive/html/bug-gnulib/2019-06/msg00096.html>
> 2. Compile and disassemble:
> gcc -O2 -m32 -flto foo.c bar.c -shared -o libfoo.so && objdump --disassemble
> libfoo.so
> Observe that f_assume pushes an immediate $0 argument on the stack for the
> function call.
> 3. Enable the second 'assume' definition instead of the first one.
> 4. Compile and disassemble:
> gcc -O2 -m32 -flto foo.c bar.c -shared -o libfoo.so && objdump --disassemble
> libfoo.so
> Observe that f_assume is now an alias of f_generic, that pushes a
> computed value as argument on the stack for the function call (push %eax).
Thanks. I have no idea what I was doing wrong, but I can confirm that
this case indeed demonstrates a GCC weakness when there is a non-split
eassume (A && B), where A and B are both assumable (in the sense that
__builtin_constant_p(!A == !A).
> > Sorry to be pedantic, but do you disagree that it is better in these
> > cases, or in general?
>
> I disagree that it is better in general.
> You're apparently setting out a high goal for the 'assume' macro:
> (1) that the programmer may call it with an expression that involves
> function calls,
That's the status quo in Emacs and many other projects that have
started believing the "an inline function is as fast as a macro"
mantra*, assuming you include inline functions with "function calls".
(* - as have I)
> (2) that the generated code will never include these function calls,
> because the generated code with the 'assume' invocation should be
> optimized at least as well as the generated code without the
> 'assume' invocation.
I think it should be the rarest of exceptions for an assume() to
result in slower code, yes. I believe that includes the case where
functions marked inline aren't inlined, because of compiler options,
for example.
> I'm adding certain quality criteria:
> - It is not "good" if a construct behaves unpredictably, that is,
> if it hard to document precisely how it will behave. (*)
Uhm, isn't the whole point of assume() to make the program behavior
undefined in a certain case?
int i = 1;
assume(i == 0);
will behave unpredictably. That's why we use the assume(), to speed up
execution by providing the compiler with an expression which "may or
may not be evaluated", and allowing it to do just anything if the
expression evaluates to false. That's the documented API.
If you want your program to behave predictably, in the strict sense,
you cannot ever use the current assume() API. If you want it to behave
predictably in some looser sense, the sense I suggest is that
assume(C) may or may not evaluate C. Again, that's what the
documentation says.
> - It is not "good" if the behaviour with no LTO is different from
> the behaviour with LTO.
I agree I should investigate the LTO example further, but please let's
keep in mind that I observed problematic behavior of assume(A && B) in
other contexts, so I'm not convinced LTO is the decisive factor here.
However, optimization can and will influence program behavior in many
ways. One of these ways is whether the argument to assume, which the
programmer knows "may or may not be evaluated", is evaluated or not.
> The implementation with the __builtin_constant, while attaining the
> goals (1) and (2), does not satisfy the two quality criteria.
For the record, my quality criteria are:
(1) implement the documented API, and don't change it
(2) when optimizing for speed, do not produce slower code with
eassume() than we would without it. Even when the programmer wrongly
guessed that a function would be inlined.
(3) when optimizing for size, do not produce larger code with
eassume() than we would without it. Even when inline functions are not
inlined.
(4) be at least as fast as gnulib assume()
> I believe the only way to attain the goals and the quality criteria
> is, as you suggested, to ask the GCC people to add a __builtin_assume
> built-in.
I think there's a significant probability that the GCC people would
agree to add such a built-in, but insist on its having "may or may not
evaluate its argument" semantics.
> > > 1. The new 'assume' is worse when -flto is in use.
> >
> > Maybe. Even if it is, though, that's a GCC limitation which I consider
> > likely to be fixable
>
> Yes, *maybe* the GCC people can change the semantics of __builtin_constant_p
> so that it is effectively computed at link-time, rather than when a single
> compilation unit gets compiled. Or maybe not. I don't know...
Hmm. My observations suggest that __builtin_constant_p is effectively
computed at link-time; all I have to do is to split the assume() in
your example.
> > It's way too easy to do something like
> >
> > eassume(ptr->field >= 0 && f(ptr));
> >
> > when what you mean is
> >
> > eassume(ptr->field >= 0);
> > eassume(f(ptr));
>
> I argue that it's unnatural if the two don't behave exactly the same.
But you're arguing for a __builtin_assume which doesn't evaluate its
argument, still? Because
{
i++;
__builtin_assume(i == 0);
__builtin_assume(infloop());
i++;
}
could validly be optimized to i = 1, while
{
i++;
__builtin_assume(i == 0 && infloop());
i++;
}
could not be.
Or did I misunderstand the semantics you suggested for __builtin_assume?
> Like everyone expects that
> x = foo ? yes : no;
> is equivalent to
> if (foo) x = yes; else x = no;
It is.
> And everyone expects that
> if (A && B) { ... }
> is equivalent to
> if (A) if (B) { ... }
It is.
Sorry if I'm being a bit dense here.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-28 19:56 ` Bruno Haible
@ 2019-06-28 21:08 ` Pip Cet
0 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-28 21:08 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
> Options Result
> none increment
> -DASSUME_GNULIB increment
> -DTWO_ASSUMES single-store
> -DASSUME_GNULIB -DTWO_ASSUMES increment
Thanks, I should have included that. However, please note that in the
-DASSUME_GNULIB cases, global will have a final value of 2, as it's
incremented by f as well as by main; in the "none" case, it will have
a final value of 1.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-28 21:07 ` Pip Cet
@ 2019-06-28 23:30 ` Bruno Haible
[not found] ` <2067160.1HRgjLhtDS@omega>
1 sibling, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-28 23:30 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Pip Cet wrote:
> have started believing the "an inline function is as fast as a macro"
> mantra*, assuming you include inline functions with "function calls".
Ah, that's where the entire topic with the function calls inside assume()
comes from! I agree it's an important case (more important than the
functions defined in other compilation units). So let's test this:
==================================== foo.c ====================================
#include <stdio.h>
#define assume(R) ((R) ? (void) 0 : __builtin_unreachable ())
//#define assume(R) (!__builtin_constant_p (!(R) == !(R)) || (R) ? (void) 0 : __builtin_unreachable ())
#if USE_MACROS
# define complicated(i) (((i) & 7) == 3)
# define nonnegative(i) ((i) >= 0)
#else
static inline int complicated (int i) { return (i & 7) == 3; }
static inline int nonnegative (int i) { return i >= 0; }
#endif
#if COMPLEX_CONDITION
# define CONDITION complicated (i) && nonnegative (i)
#else
# define CONDITION nonnegative (i)
#endif
int f_generic (int i)
{
printf("%d\n", i & 0x80000000);
return 0;
}
int f_condition (int i)
{
if (CONDITION)
printf("%d\n", i & 0x80000000);
return 0;
}
int f_assume (int i)
{
assume (CONDITION);
printf("%d\n", i & 0x80000000);
return 0;
}
===============================================================================
$ gcc -O2 -m32 -S foo.c && fgrep -v .cfi foo.s
Results:
// old 'assume', !COMPLEX_CONDITION, USE_MACROS -> f_assume optimized
// old 'assume', COMPLEX_CONDITION, USE_MACROS -> f_assume optimized
// old 'assume', !COMPLEX_CONDITION, !USE_MACROS -> f_assume optimized
// old 'assume', COMPLEX_CONDITION, !USE_MACROS -> f_assume optimized
// new 'assume', !COMPLEX_CONDITION, USE_MACROS -> f_assume optimized
// new 'assume', COMPLEX_CONDITION, USE_MACROS -> f_assume optimized
// new 'assume', !COMPLEX_CONDITION, !USE_MACROS -> f_assume not optimized
// new 'assume', COMPLEX_CONDITION, !USE_MACROS -> f_assume not optimized
So, the main effect of the proposed new 'assume' is that it de-optimizes
the case where the CONDITION is defined using inline functions!
The other case - that the CONDITION calls functions defined in other
compilation units - is a fringe case. And the topic regarding the
COMPLEX_CONDITION versus simple condition is also less important.
Based on these results, I formally object against the proposed patch.
> > (2) that the generated code will never include these function calls,
> > because the generated code with the 'assume' invocation should be
> > optimized at least as well as the generated code without the
> > 'assume' invocation.
>
> I think it should be the rarest of exceptions for an assume() to
> result in slower code, yes. I believe that includes the case where
> functions marked inline aren't inlined, because of compiler options,
> for example.
Then, I think we should change the documentation of 'assume' to say
that when it invokes functions, these functions should be marked
'__attribute__ ((__always_inline__))', otherwise performance will
be worse than without the 'assume', not better.
> (1) implement the documented API, and don't change it
> (2) when optimizing for speed, do not produce slower code with
> eassume() than we would without it. Even when the programmer wrongly
> guessed that a function would be inlined.
> (3) when optimizing for size, do not produce larger code with
> eassume() than we would without it. Even when inline functions are not
> inlined.
> (4) be at least as fast as gnulib assume()
You evidently have slightly different quality criteria than I do. :)
> > I believe the only way to attain the goals and the quality criteria
> > is, as you suggested, to ask the GCC people to add a __builtin_assume
> > built-in.
>
> I think there's a significant probability that the GCC people would
> agree to add such a built-in, but insist on its having "may or may not
> evaluate its argument" semantics.
We can tell them that it would be important for us that is does not
evaluate its argument. Like sizeof (EXPRESSION) does not evaluate EXPRESSION.
> Sorry if I'm being a bit dense here.
No problem. I'm also often being dense.
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2067160.1HRgjLhtDS@omega>
@ 2019-06-29 5:40 ` Paul Eggert
2019-06-29 5:44 ` Pip Cet
[not found] ` <CAOqdjBcNA4mDiwsd_jbeePGMdUwPvkFCNdgtZvmiQnYmJNR3pA@mail.gmail.com>
2 siblings, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-29 5:40 UTC (permalink / raw)
To: Bruno Haible, Pip Cet; +Cc: 36370, bug-gnulib
Bruno Haible wrote:
> I think we should change the documentation of 'assume' to say
> that when it invokes functions, these functions should be marked
> '__attribute__ ((__always_inline__))', otherwise performance will
> be worse than without the 'assume', not better.
I suggest something simpler and a little more general. The Emacs documentation
for 'eassume' says this:
This can improve performance in some cases,
though it can degrade performance in others. It's often suboptimal
for COND to call external functions or access volatile storage.
and we could migrate that into the documentation for 'assume'.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-28 19:15 ` Pip Cet
2019-06-28 19:56 ` Bruno Haible
@ 2019-06-29 5:41 ` Paul Eggert
[not found] ` <87168b28-192b-6666-e9b6-9cdc2ed3917a@cs.ucla.edu>
2 siblings, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-29 5:41 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Bruno Haible, bug-gnulib
Pip Cet wrote:
> eassume (global == 0);
> eassume (f ());
> #else
> eassume (global == 0 && f ());
> ...
> extern int global;
>
> int f(void)
> {
> return ++global;
> }
This is not a valid use of 'assume'. It's documented that assume's argument
should be free of side effects.
It would be nice if 'assume (R)' reported an error if R has side effects, and
generated a warning unless the compiler can verify that R is free of side
effects. However, these niceties would require better support from the compiler.
> If you want your program to behave predictably, in the strict sense,
> you cannot ever use the current assume() API.
I'm not sure what you mean by "in the strict sense". It's true that programs can
misuse 'assume' and get undefined behavior, but that's kind of the point....
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2067160.1HRgjLhtDS@omega>
2019-06-29 5:40 ` Paul Eggert
@ 2019-06-29 5:44 ` Pip Cet
[not found] ` <CAOqdjBcNA4mDiwsd_jbeePGMdUwPvkFCNdgtZvmiQnYmJNR3pA@mail.gmail.com>
2 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-29 5:44 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
On Fri, Jun 28, 2019 at 11:30 PM Bruno Haible <bruno@clisp.org> wrote:
> Pip Cet wrote:
> > have started believing the "an inline function is as fast as a macro"
> > mantra*, assuming you include inline functions with "function calls".
>
> Ah, that's where the entire topic with the function calls inside assume()
> comes from! I agree it's an important case (more important than the
> functions defined in other compilation units).
As I said earlier:
----
This makes it safe to use function expressions in eassume, whether the
function is inlined or not.
(That GCC doesn't actually do very much with this information is a
separate issue).
----
So we're talking about this separate issue now? I ask not for
rhetorical points, but because I genuinely think it's interesting if
the objection is based on GCC limitations rather than fundamentally
unfixable reasons.
> So, the main effect of the proposed new 'assume' is that it de-optimizes
> the case where the CONDITION is defined using inline functions!
(I don't think it's the "main" effect).
That certainly is something that appears to be happening in some
situations. It's a GCC limitation, and let's be clear: as long as this
limitation isn't lifted, an inline function is not as fast as a macro.
However, passing an inline function to assume() is problematic anyway,
unless it's marked as __attribute__((always_inline)), and marking it
as __attribute__((always_inline)) is problematic because it might
directly contradict what the programmer was trying to achieve by
passing -fno-inline.
> Based on these results, I formally object against the proposed patch.
I won't argue against that as long as I haven't found a way around the
GCC issue, but I would like to state that the current assume does
behave very badly when combined with -fno-inline-small-functions
-fno-inline.
> > > (2) that the generated code will never include these function calls,
> > > because the generated code with the 'assume' invocation should be
> > > optimized at least as well as the generated code without the
> > > 'assume' invocation.
> >
> > I think it should be the rarest of exceptions for an assume() to
> > result in slower code, yes. I believe that includes the case where
> > functions marked inline aren't inlined, because of compiler options,
> > for example.
>
> Then, I think we should change the documentation of 'assume' to say
> that when it invokes functions, these functions should be marked
> '__attribute__ ((__always_inline__))', otherwise performance will
> be worse than without the 'assume', not better.
I disagree. It's tedious, and people might just change their INLINE
macros (or whatever) to specify __attribute__((always_inline)), making
-fno-inline worthless...
> > I think there's a significant probability that the GCC people would
> > agree to add such a built-in, but insist on its having "may or may not
> > evaluate its argument" semantics.
>
> We can tell them that it would be important for us that is does not
> evaluate its argument. Like sizeof (EXPRESSION) does not evaluate EXPRESSION.
We can tell them that, but my suspicion is it'll be much, much harder
to implement that way.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <87168b28-192b-6666-e9b6-9cdc2ed3917a@cs.ucla.edu>
@ 2019-06-29 6:48 ` Pip Cet
[not found] ` <CAOqdjBfcNbXFw3Fb0wgRR10PNbkJQ+88ObE9KEghLSb-ptdrbA@mail.gmail.com>
1 sibling, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-29 6:48 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370, Bruno Haible, bug-gnulib
On Sat, Jun 29, 2019 at 5:41 AM Paul Eggert <eggert@cs.ucla.edu> wrote:
> Pip Cet wrote:
> > eassume (global == 0);
> > eassume (f ());
> > #else
> > eassume (global == 0 && f ());
> > ...
> > extern int global;
> >
> > int f(void)
> > {
> > return ++global;
> > }
>
>
> This is not a valid use of 'assume'. It's documented that assume's argument
> should be free of side effects.
But the compiler makes no such assumption, so it cannot optimize
assume(i >= 0 && f()), (where i is a global or a non-const pointer to
i has potentially leaked) unless f happens to be available at
optimization time.
I think this is an interesting point: if GCC decided to add a
__builtin_assume() builtin, we could give it slightly different
semantics: that the expression passed to it evaluates to true, but
doesn't evaluate to false or fail to evaluate. Something like
__attribute__((does_return)) might do on a function.
However, if I'm not too confused, we're discussing whether
assume(SIMPLE_CONDITION && COMPLICATED_CONDITION) is ever a good idea.
With the old assume, it's harmful. With the new assume, it's
pointless. Is assume(SIMPLE_CONDITION); assume(COMPLICATED_CONDITION);
a good idea? With the old assume, it's harmful. With the new assume,
it's a more verbose way of simply assuming SIMPLE_CONDITION, so it
might be a good idea.
Also, "should" doesn't mean "must", does it? I'd prefer rewording that
sentence as "R may or may not be evaluated: it should not normally
have side-effects".
> It would be nice if 'assume (R)' reported an error if R has side effects, and
> generated a warning unless the compiler can verify that R is free of side
> effects. However, these niceties would require better support from the compiler.
But if we had that support from the compiler, wouldn't it be even
nicer to give up (most of) the distinction between assert and assume
and just tell people to use assume? That idea was my starting point,
and I'm still convinced it would result in better code overall. Except
someone would have to grep a little once in a while and replace most
eassume (A && B) expressions with eassume (A); eassume (B);
However, there are a few tricks we can use to verify this in special
debug builds.
------
Putting inner functions into sections:
#define assume(C) ({ auto void inner (void)
__attribute__((section("only_trivial_functions_go_here"), used)); void
inner(void) { (void)(C); } (void) 0; })
Then verify that the section contains only trivial function
definitions. (For the record, for Emacs, it does).
Another approach for detecting when (C) has "global" side effects
(such as calling an external function) is to do this:
-------
#include <stdio.h>
int global;
extern void dummy(void (*)(void)) __attribute__((warning ("assume
might have side effects")));
#define C printf("3")
int main(void)
{
auto void inner(void) __attribute__((used));
void inner(void)
{
if (global)
__builtin_unreachable ();
(void)(C);
if (global)
dummy(inner);
}
}
-----
A third possibility is to use __builtin_constant_p(!(C)!=!(C)), as the
patch does. That doesn't state precisely that C has no side effects,
but it does come fairly close in practice ... except for the inline
function problem.
> > If you want your program to behave predictably, in the strict sense,
> > you cannot ever use the current assume() API.
>
> I'm not sure what you mean by "in the strict sense". It's true that programs can
> misuse 'assume' and get undefined behavior, but that's kind of the point....
Precisely what I meant.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBcNA4mDiwsd_jbeePGMdUwPvkFCNdgtZvmiQnYmJNR3pA@mail.gmail.com>
@ 2019-06-29 10:31 ` Bruno Haible
[not found] ` <2515002.Q0mBYvUW8C@omega>
1 sibling, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-29 10:31 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Pip Cet wrote:
> I would like to state that the current assume does
> behave very badly when combined with -fno-inline-small-functions
> -fno-inline.
Since we can't address this limitation through an acceptable change
to the 'assume' macro, we need to address it through documentation.
> marking it
> as __attribute__((always_inline)) is problematic because it might
> directly contradict what the programmer was trying to achieve by
> passing -fno-inline.
__attribute__((always_inline)) exists precisely to make a distinction
between functions where inlining is usually desirable vs. functions
where inlining is essential. We don't need to warn against the uses
of __attribute__((always_inline)) -- confusing behaviour in the debugger
etc. -- becauses these drawbacks are already well-known.
Paul Eggert wrote:
> I suggest something simpler and a little more general. The Emacs documentation
> for 'eassume' says this:
>
> This can improve performance in some cases,
> though it can degrade performance in others. It's often suboptimal
> for COND to call external functions or access volatile storage.
>
> and we could migrate that into the documentation for 'assume'.
As a user of the 'assume' macro, I want a definitive statement about what
I need to provide so that the macro works in the sense of improved performance.
A statement about "often" is too vague.
How about this proposed patch?
diff --git a/lib/verify.h b/lib/verify.h
index f8e4eff..ed1ba19 100644
--- a/lib/verify.h
+++ b/lib/verify.h
@@ -261,7 +261,10 @@ template <int w>
/* Assume that R always holds. This lets the compiler optimize
accordingly. R should not have side-effects; it may or may not be
- evaluated. Behavior is undefined if R is false. */
+ evaluated. The behavior is undefined if R is false.
+ If you want the use of this macro to improve, not deteriorate,
+ performance, R should not contain function calls except to functions
+ that are declared 'inline __attribute__((__always_inline__))'. */
#if (__has_builtin (__builtin_unreachable) \
|| 4 < __GNUC__ + (5 <= __GNUC_MINOR__))
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2515002.Q0mBYvUW8C@omega>
@ 2019-06-29 17:11 ` Paul Eggert
[not found] ` <99bacb9f-1192-1315-85d7-5ab4924dfef8@cs.ucla.edu>
` (2 subsequent siblings)
3 siblings, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-29 17:11 UTC (permalink / raw)
To: Bruno Haible, Pip Cet; +Cc: 36370, bug-gnulib
[-- Attachment #1: Type: text/plain, Size: 1008 bytes --]
Bruno Haible wrote:
> + If you want the use of this macro to improve, not deteriorate,
> + performance, R should not contain function calls except to functions
> + that are declared 'inline __attribute__((__always_inline__))'. */
A reader of that might incorrectly conclude that using such functions will
always improve performance, compared to using functions not declared that way.
And I'm leery of putting such GCC-specific info into the documentation of a
generic macro.
Also, given Pip Cet's misunderstanding it'd be helpful to add a word or two
about the intent of 'assume (R)'.
How about the attached patch?
> - evaluated. Behavior is undefined if R is false. */
> + evaluated. The behavior is undefined if R is false.
This is a nit, but to my ears the shorter version is better, and has a
more-accurate connotation as "the behavior" connotes "the behavior of 'assume
(R)'", whereas behavior is also undefined for code before or after a call to an
incorrect 'assume (R)'.
[-- Attachment #2: verify.diff --]
[-- Type: text/x-patch, Size: 800 bytes --]
diff --git a/lib/verify.h b/lib/verify.h
index f8e4eff02..168a7afdb 100644
--- a/lib/verify.h
+++ b/lib/verify.h
@@ -259,9 +259,11 @@ template <int w>
# define __has_builtin(x) 0
#endif
-/* Assume that R always holds. This lets the compiler optimize
- accordingly. R should not have side-effects; it may or may not be
- evaluated. Behavior is undefined if R is false. */
+/* Assume that R always holds. Behavior is undefined if R is false.
+ R should lack side effects, as it may be evaluated only partially.
+ Although 'assume (R)' is typically intended to help performance,
+ performance may degrade if R uses hard-to-optimize features
+ such as calls to non-inlined functions. */
#if (__has_builtin (__builtin_unreachable) \
|| 4 < __GNUC__ + (5 <= __GNUC_MINOR__))
^ permalink raw reply related [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBfcNbXFw3Fb0wgRR10PNbkJQ+88ObE9KEghLSb-ptdrbA@mail.gmail.com>
@ 2019-06-29 17:31 ` Paul Eggert
[not found] ` <791ae316-3a6f-605a-0da5-874fe3d224c5@cs.ucla.edu>
1 sibling, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-06-29 17:31 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Bruno Haible, bug-gnulib
Pip Cet wrote:
>> This is not a valid use of 'assume'. It's documented that assume's argument
>> should be free of side effects.
>
> But the compiler makes no such assumption
Sure, but if the caller uses 'assume' contrary to its documentation, that's a
problem with the caller's code, not with 'assume'. It's merely an implementation
detail as to which pothole the problematic code runs into.
> if GCC decided to add a
> __builtin_assume() builtin, we could give it slightly different
> semantics: that the expression passed to it evaluates to true, but
> doesn't evaluate to false or fail to evaluate. Something like
> __attribute__((does_return)) might do on a function.
Yes, the expression should return true without side effects or looping. I don't
see this as an significant difference in semantics. One should also not call
Gnulib assume (R) with an expression that loops forever, as this defeats the
intent of 'assume' which is to make code more efficient. If there's any real
confusion about this issue, we can add it to the 'assume' documentation as well.
> Also, "should" doesn't mean "must", does it?
It's not the "should" of an Internet RFC. It's more the "should" of "you should
do this, and if you don't you're on your own".
> I'd prefer rewording that
> sentence as "R may or may not be evaluated: it should not normally
> have side-effects".
Better to say that it should not have side effects at all. There's no "normally"
about that. Side effects are trouble.
> wouldn't it be even
> nicer to give up (most of) the distinction between assert and assume
> and just tell people to use assume?
No, because 'assert (false)' has well-defined behavior, whereas behavior is
undefined for programs that do 'assume (false)' . This is a fundamental
difference between the two.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <99bacb9f-1192-1315-85d7-5ab4924dfef8@cs.ucla.edu>
@ 2019-06-29 17:48 ` Bruno Haible
0 siblings, 0 replies; 36+ messages in thread
From: Bruno Haible @ 2019-06-29 17:48 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370, bug-gnulib, Pip Cet
Hi Paul,
> > + If you want the use of this macro to improve, not deteriorate,
> > + performance, R should not contain function calls except to functions
> > + that are declared 'inline __attribute__((__always_inline__))'. */
>
> A reader of that might incorrectly conclude that using such functions will
> always improve performance, compared to using functions not declared that way.
For functions not declared 'inline __attribute__((__always_inline__))', it
depends on the inlining heuristics of the compiler whether the 'assume (R)'
will be a performance improvement or the opposite.
> Also, given Pip Cet's misunderstanding it'd be helpful to add a word or two
> about the intent of 'assume (R)'.
>
> How about the attached patch?
That's a good compromise.
> > - evaluated. Behavior is undefined if R is false. */
> > + evaluated. The behavior is undefined if R is false.
>
> This is a nit, but to my ears the shorter version is better
OK, fine.
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <791ae316-3a6f-605a-0da5-874fe3d224c5@cs.ucla.edu>
@ 2019-06-30 9:21 ` Pip Cet
0 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-30 9:21 UTC (permalink / raw)
To: Paul Eggert; +Cc: 36370, Bruno Haible, bug-gnulib
On Sat, Jun 29, 2019 at 5:31 PM Paul Eggert <eggert@cs.ucla.edu> wrote:
> >> This is not a valid use of 'assume'. It's documented that assume's argument
> >> should be free of side effects.
> >
> > But the compiler makes no such assumption
>
> Sure, but if the caller uses 'assume' contrary to its documentation, that's a
> problem with the caller's code, not with 'assume'. It's merely an implementation
> detail as to which pothole the problematic code runs into.
I agree, for what it's worth, that the new documentation is clear on
what is and isn't allowed with assume(). It's just that it's a
significant API change from the previous state, and I dislike the API
change.
Again, this isn't about using `assume' contrary to its documentation,
it's about using it in a way that the compiler cannot prove obeys the
documented API, and thus handles suboptimally.
> > if GCC decided to add a
> > __builtin_assume() builtin, we could give it slightly different
> > semantics: that the expression passed to it evaluates to true, but
> > doesn't evaluate to false or fail to evaluate. Something like
> > __attribute__((does_return)) might do on a function.
>
> Yes, the expression should return true without side effects or looping. I don't
> see this as an significant difference in semantics.
It's significant to GCC. It optimizes function calls that are known to
return differently from ordinary function calls.
> One should also not call
> Gnulib assume (R) with an expression that loops forever, as this defeats the
> intent of 'assume' which is to make code more efficient.
No disagreement there. However, that doesn't mean the compiler is free
to assume that assume()'s argument doesn't loop; it needs to prove
that, whereas with the new __builtin_assume() builtin, it wouldn't
have to go to that trouble.
> If there's any real
> confusion about this issue, we can add it to the 'assume' documentation as well.
I think the new documentation is fine, in this regard.
> > Also, "should" doesn't mean "must", does it?
>
> It's not the "should" of an Internet RFC. It's more the "should" of "you should
> do this, and if you don't you're on your own".
So if I add a debug print statement to an inline function that happens
to be called in an eassume, I'm on my own? That's a significant API
change.
> > I'd prefer rewording that
> > sentence as "R may or may not be evaluated: it should not normally
> > have side-effects".
>
> Better to say that it should not have side effects at all. There's no "normally"
> about that. Side effects are trouble.
Except for debugging statements, other assert()s, abort()s, other
assume()s, divisions by zero that happen in the eassume() code rather
than after it...
Again, I think there are two issues here: you want to change the API
to be much more restrictive (my original patch intended to make it
more lenient), and you want to document the new API. Obviously, the
second point is fine.
If the new requirement is that R has no side effects, we don't need
the "R may or may not be evaluated". In fact, it might confuse others
in the same way I was confused.
I read the old documentation as:
As far as side effects go, each assume(R) may decide, independently
and unpredictably, whether side effects of R are visible or not: R may
or may not be evaluated. This unpredictable behavior can be confusing;
to avoid confusion, R RFC-SHOULD not have side effects.
the new documentation, I read as:
If R has any side effects, assume(R) is undefined. It can cause
compilation, linking, or run-time errors.
> > wouldn't it be even
> > nicer to give up (most of) the distinction between assert and assume
> > and just tell people to use assume?
>
> No, because 'assert (false)' has well-defined behavior, whereas behavior is
> undefined for programs that do 'assume (false)' . This is a fundamental
> difference between the two.
Thus the "most of", yes.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <2515002.Q0mBYvUW8C@omega>
2019-06-29 17:11 ` Paul Eggert
[not found] ` <99bacb9f-1192-1315-85d7-5ab4924dfef8@cs.ucla.edu>
@ 2019-06-30 15:30 ` Pip Cet
[not found] ` <CAOqdjBeiMno7nGKwk7SSZQob+CTyG39KRTM9EEebq7NQavLR-Q@mail.gmail.com>
3 siblings, 0 replies; 36+ messages in thread
From: Pip Cet @ 2019-06-30 15:30 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, Paul Eggert, bug-gnulib
On Sat, Jun 29, 2019 at 10:31 AM Bruno Haible <bruno@clisp.org> wrote:
> Pip Cet wrote:
> > I would like to state that the current assume does
> > behave very badly when combined with -fno-inline-small-functions
> > -fno-inline.
>
> Since we can't address this limitation through an acceptable change
> to the 'assume' macro, we need to address it through documentation.
I agree. There's a limitation bad enough for me to consider it a bug
in current GCC, and even if that's lifted tomorrow, it wouldn't be
backported so it would be quite a while until I'd ask you to
reconsider the matter.
> > marking it
> > as __attribute__((always_inline)) is problematic because it might
> > directly contradict what the programmer was trying to achieve by
> > passing -fno-inline.
>
> __attribute__((always_inline)) exists precisely to make a distinction
> between functions where inlining is usually desirable vs. functions
> where inlining is essential.
Indeed. In this case, it's usually desirable but not essential for the
correctness of the program, IMHO. I understand you disagree.
> We don't need to warn against the uses
> of __attribute__((always_inline)) -- confusing behaviour in the debugger
> etc. -- becauses these drawbacks are already well-known.
I disagree completely. There's no warning in the GCC documentation for
the attribute.
> As a user of the 'assume' macro, I want a definitive statement about what
> I need to provide so that the macro works in the sense of improved performance.
> A statement about "often" is too vague.
I agree.
> How about this proposed patch?
>
> diff --git a/lib/verify.h b/lib/verify.h
> index f8e4eff..ed1ba19 100644
> --- a/lib/verify.h
> +++ b/lib/verify.h
> @@ -261,7 +261,10 @@ template <int w>
>
> /* Assume that R always holds. This lets the compiler optimize
> accordingly. R should not have side-effects; it may or may not be
> - evaluated. Behavior is undefined if R is false. */
> + evaluated. The behavior is undefined if R is false.
> + If you want the use of this macro to improve, not deteriorate,
> + performance, R should not contain function calls except to functions
> + that are declared 'inline __attribute__((__always_inline__))'. */
My suggestion would be "Assume that R always holds. This lets the
compiler optimize accordingly. Behavior is undefined if R is false,
fails to evaluate, or has side effects. Performance will suffer if R
contains function calls that are not inlined at compile time."
That would describe the API as I understand you and Paul think it
always has been; I think it describes a drastically stricter API than
what the old comment did. As a reminder, my starting point was that I
wanted to make the API more lenient. So, obviously, I disagree with
the API change but it is more important that there's consensus on what
the API actually is than it is to have a good API.
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBeiMno7nGKwk7SSZQob+CTyG39KRTM9EEebq7NQavLR-Q@mail.gmail.com>
@ 2019-06-30 15:45 ` Bruno Haible
2019-07-02 23:39 ` Paul Eggert
2019-07-01 1:46 ` Richard Stallman
1 sibling, 1 reply; 36+ messages in thread
From: Bruno Haible @ 2019-06-30 15:45 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, Paul Eggert, bug-gnulib
Pip Cet wrote:
> My suggestion would be "Assume that R always holds. This lets the
> compiler optimize accordingly. Behavior is undefined if R is false,
> fails to evaluate, or has side effects. Performance will suffer if R
> contains function calls that are not inlined at compile time."
I like this wording.
Bruno
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
[not found] ` <CAOqdjBeiMno7nGKwk7SSZQob+CTyG39KRTM9EEebq7NQavLR-Q@mail.gmail.com>
2019-06-30 15:45 ` Bruno Haible
@ 2019-07-01 1:46 ` Richard Stallman
1 sibling, 0 replies; 36+ messages in thread
From: Richard Stallman @ 2019-07-01 1:46 UTC (permalink / raw)
To: Pip Cet; +Cc: 36370, bruno, eggert, bug-gnulib
[[[ To any NSA and FBI agents reading my email: please consider ]]]
[[[ whether defending the US Constitution against all enemies, ]]]
[[[ foreign or domestic, requires you to follow Snowden's example. ]]]
> I agree. There's a limitation bad enough for me to consider it a bug
> in current GCC,
If you have encountered an apparent GCC bug, please report it.
The GCC developers need our help.
--
Dr Richard Stallman
President, Free Software Foundation (https://gnu.org, https://fsf.org)
Internet Hall-of-Famer (https://internethalloffame.org)
^ permalink raw reply [flat|nested] 36+ messages in thread
* bug#36370: 27.0.50; XFIXNAT called on negative numbers
2019-06-30 15:45 ` Bruno Haible
@ 2019-07-02 23:39 ` Paul Eggert
0 siblings, 0 replies; 36+ messages in thread
From: Paul Eggert @ 2019-07-02 23:39 UTC (permalink / raw)
To: Bruno Haible; +Cc: 36370, bug-gnulib, Pip Cet
[-- Attachment #1: Type: text/plain, Size: 339 bytes --]
Bruno Haible wrote:
> I like this wording.
OK, I merged that into the wording I proposed and installed the attached. One
new point that I added is that I sometimes use 'assume (X)' to pacify GCC so
that it does not issue a bogus warning in nearby code; this benefit of 'assume'
is independent of the efficiency of the generated code.
[-- Attachment #2: 0001-verify-document-assume-better.txt --]
[-- Type: text/plain, Size: 1661 bytes --]
From c882996026931d9b11b0878a9bb032ca6b3941dd Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Tue, 2 Jul 2019 16:33:04 -0700
Subject: [PATCH] =?UTF-8?q?verify:=20document=20=E2=80=98assume=E2=80=99?=
=?UTF-8?q?=20better?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* lib/verify.h: Reword doc (Bug#36370).
---
ChangeLog | 5 +++++
lib/verify.h | 8 +++++---
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/ChangeLog b/ChangeLog
index 77d8e9e6a..b89fd5d9e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2019-07-02 Paul Eggert <eggert@cs.ucla.edu>
+
+ verify: document ‘assume’ better
+ * lib/verify.h: Reword doc (Bug#36370).
+
2019-07-02 Bruno Haible <bruno@clisp.org>
localcharset, nl_langinfo: Fix return value for UTF-8 locales on MSVC.
diff --git a/lib/verify.h b/lib/verify.h
index f8e4eff02..9b8e1ed20 100644
--- a/lib/verify.h
+++ b/lib/verify.h
@@ -259,9 +259,11 @@ template <int w>
# define __has_builtin(x) 0
#endif
-/* Assume that R always holds. This lets the compiler optimize
- accordingly. R should not have side-effects; it may or may not be
- evaluated. Behavior is undefined if R is false. */
+/* Assume that R always holds. Behavior is undefined if R is false,
+ fails to evaluate, or has side effects. Although assuming R can
+ help a compiler generate better code or diagnostics, performance
+ can suffer if R uses hard-to-optimize features such as function
+ calls not inlined by the compiler. */
#if (__has_builtin (__builtin_unreachable) \
|| 4 < __GNUC__ + (5 <= __GNUC_MINOR__))
--
2.17.1
^ permalink raw reply related [flat|nested] 36+ messages in thread
end of thread, other threads:[~2019-07-02 23:39 UTC | newest]
Thread overview: 36+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-06-25 5:36 bug#36370: 27.0.50; XFIXNAT called on negative numbers Pip Cet
2019-06-27 1:10 ` Paul Eggert
2019-06-27 6:16 ` Pip Cet
2019-06-27 8:28 ` Paul Eggert
2019-06-27 13:17 ` Pip Cet
2019-06-27 13:37 ` Eli Zaretskii
2019-06-27 19:38 ` Paul Eggert
2019-06-27 19:56 ` Pip Cet
2019-06-27 21:13 ` Paul Eggert
[not found] ` <5284eb58-3560-da42-d1d1-3bdb930eae49@cs.ucla.edu>
2019-06-27 21:37 ` Pip Cet
2019-06-27 23:45 ` Bruno Haible
[not found] ` <2715311.ceefYqj39C@omega>
2019-06-28 0:04 ` Paul Eggert
2019-06-28 11:06 ` Pip Cet
2019-06-28 12:14 ` Bruno Haible
[not found] ` <8979488.cRkkfcT1mV@omega>
2019-06-28 12:29 ` Bruno Haible
2019-06-28 13:51 ` Pip Cet
[not found] ` <CAOqdjBfS99UpLZ-qLe4=FMXMsr+T3LUvJEsf_gfmF6wwLbqgOw@mail.gmail.com>
2019-06-28 17:46 ` Paul Eggert
2019-06-28 19:11 ` Bruno Haible
[not found] ` <a293f2fe-99b3-3776-f27b-35e3a93d1d34@cs.ucla.edu>
2019-06-28 19:15 ` Pip Cet
2019-06-28 19:56 ` Bruno Haible
2019-06-28 21:08 ` Pip Cet
2019-06-29 5:41 ` Paul Eggert
[not found] ` <87168b28-192b-6666-e9b6-9cdc2ed3917a@cs.ucla.edu>
2019-06-29 6:48 ` Pip Cet
[not found] ` <CAOqdjBfcNbXFw3Fb0wgRR10PNbkJQ+88ObE9KEghLSb-ptdrbA@mail.gmail.com>
2019-06-29 17:31 ` Paul Eggert
[not found] ` <791ae316-3a6f-605a-0da5-874fe3d224c5@cs.ucla.edu>
2019-06-30 9:21 ` Pip Cet
[not found] ` <11002295.LrvMqknVDZ@omega>
2019-06-28 21:07 ` Pip Cet
2019-06-28 23:30 ` Bruno Haible
[not found] ` <2067160.1HRgjLhtDS@omega>
2019-06-29 5:40 ` Paul Eggert
2019-06-29 5:44 ` Pip Cet
[not found] ` <CAOqdjBcNA4mDiwsd_jbeePGMdUwPvkFCNdgtZvmiQnYmJNR3pA@mail.gmail.com>
2019-06-29 10:31 ` Bruno Haible
[not found] ` <2515002.Q0mBYvUW8C@omega>
2019-06-29 17:11 ` Paul Eggert
[not found] ` <99bacb9f-1192-1315-85d7-5ab4924dfef8@cs.ucla.edu>
2019-06-29 17:48 ` Bruno Haible
2019-06-30 15:30 ` Pip Cet
[not found] ` <CAOqdjBeiMno7nGKwk7SSZQob+CTyG39KRTM9EEebq7NQavLR-Q@mail.gmail.com>
2019-06-30 15:45 ` Bruno Haible
2019-07-02 23:39 ` Paul Eggert
2019-07-01 1:46 ` Richard Stallman
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.