changeset 5769:ffc0c5a66ab1

Be lazy converting markers to integers, bytecode_{arithcompare,arithop}(). src/ChangeLog addition: 2013-12-15 Aidan Kehoe <kehoea@parhasard.net> * bytecode.c (bytecode_arithcompare): * bytecode.c (bytecode_arithop): Call promote_args_lazy () in these two functions, only converting markers to fixnums if absolutely necessary (since that is ON with large, mule buffers). * data.c (BIGNUM_CASE): * data.c (RATIO_CASE): * data.c (BIGFLOAT_CASE): * data.c (ARITHCOMPARE_MANY): Call promote_args_lazy () here too if WITH_NUMBER_TYPES is defined. We're not doing the equivalent with the non-NUMBER_TYPES code, but that's mostly fine, we are doing it in the bytecode. * number.h: * number.h (NUMBER_TYPES): * number.h (promote_args_lazy): Add this, returning LAZY_MARKER_T if both arguments are markers that point to the same buffer. tests/ChangeLog addition: 2013-12-15 Aidan Kehoe <kehoea@parhasard.net> * automated/lisp-tests.el: Test arithmetic comparisons with markers, check the type of the returned values for #'min and #'max.
author Aidan Kehoe <kehoea@parhasard.net>
date Sun, 15 Dec 2013 10:26:31 +0000
parents 3bfcdeb65578
children f22989bb7632
files src/ChangeLog src/bytecode.c src/data.c src/number.h tests/ChangeLog tests/automated/lisp-tests.el
diffstat 6 files changed, 205 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/src/ChangeLog	Sun Dec 15 09:57:28 2013 +0000
+++ b/src/ChangeLog	Sun Dec 15 10:26:31 2013 +0000
@@ -1,3 +1,25 @@
+2013-12-15  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* bytecode.c (bytecode_arithcompare):
+	* bytecode.c (bytecode_arithop):
+	Call promote_args_lazy () in these two functions, only converting
+	markers to fixnums if absolutely necessary (since that is ON with
+	large, mule buffers).
+
+	* data.c (BIGNUM_CASE):
+	* data.c (RATIO_CASE):
+	* data.c (BIGFLOAT_CASE):
+	* data.c (ARITHCOMPARE_MANY):
+	Call promote_args_lazy () here too if WITH_NUMBER_TYPES is defined.
+	We're not doing the equivalent with the non-NUMBER_TYPES code, but
+	that's mostly fine, we are doing it in the bytecode.
+	
+	* number.h:
+	* number.h (NUMBER_TYPES):
+	* number.h (promote_args_lazy):
+	Add this, returning LAZY_MARKER_T if both arguments are markers
+	that point to the same buffer.
+
 2013-12-15  Aidan Kehoe  <kehoea@parhasard.net>
 
 	* data.c (Fmax):
--- a/src/bytecode.c	Sun Dec 15 09:57:28 2013 +0000
+++ b/src/bytecode.c	Sun Dec 15 10:26:31 2013 +0000
@@ -287,25 +287,32 @@
 bytecode_arithcompare (Lisp_Object obj1, Lisp_Object obj2)
 {
 #ifdef WITH_NUMBER_TYPES
-  switch (promote_args (&obj1, &obj2))
+  switch (promote_args_lazy (&obj1, &obj2))
     {
-    case FIXNUM_T:
+    case LAZY_FIXNUM_T:
       {
 	EMACS_INT ival1 = XREALFIXNUM (obj1), ival2 = XREALFIXNUM (obj2);
 	return ival1 < ival2 ? -1 : ival1 > ival2 ? 1 : 0;
       }
 #ifdef HAVE_BIGNUM
-    case BIGNUM_T:
+    case LAZY_BIGNUM_T:
       return bignum_cmp (XBIGNUM_DATA (obj1), XBIGNUM_DATA (obj2));
 #endif
 #ifdef HAVE_RATIO
-    case RATIO_T:
+    case LAZY_RATIO_T:
       return ratio_cmp (XRATIO_DATA (obj1), XRATIO_DATA (obj2));
 #endif
 #ifdef HAVE_BIGFLOAT
-    case BIGFLOAT_T:
+    case LAZY_BIGFLOAT_T:
       return bigfloat_cmp (XBIGFLOAT_DATA (obj1), XBIGFLOAT_DATA (obj2));
 #endif
+    case LAZY_MARKER_T:
+      {
+	Bytebpos ival1 = byte_marker_position (obj1);
+	Bytebpos ival2 = byte_marker_position (obj2);
+	return ival1 < ival2 ? -1 : ival1 > ival2 ? 1 : 0;
+      }
+
     default: /* FLOAT_T */
       {
 	double dval1 = XFLOAT_DATA (obj1), dval2 = XFLOAT_DATA (obj2);
@@ -320,7 +327,19 @@
 
     if      (FIXNUMP    (obj1)) ival1 = XFIXNUM  (obj1);
     else if (CHARP   (obj1)) ival1 = XCHAR (obj1);
-    else if (MARKERP (obj1)) ival1 = marker_position (obj1);
+    else if (MARKERP (obj1))
+      {
+	/* Handle markers specially, since #'marker-position can be O(N): */
+	if (MARKERP (obj2)
+	    && (XMARKER (obj1)->buffer == XMARKER (obj2)->buffer))
+	  {
+	    Bytebpos ival1 = byte_marker_position (obj1);
+	    Bytebpos ival2 = byte_marker_position (obj2);
+	    return ival1 < ival2 ? -1 : ival1 > ival2 ? 1 : 0;
+	  }
+
+	ival1 = marker_position (obj1);
+      }
     else goto arithcompare_float;
 
     if      (FIXNUMP    (obj2)) ival2 = XFIXNUM  (obj2);
@@ -365,9 +384,29 @@
 bytecode_arithop (Lisp_Object obj1, Lisp_Object obj2, Opcode opcode)
 {
 #ifdef WITH_NUMBER_TYPES
-  switch (promote_args (&obj1, &obj2))
+  switch (promote_args_lazy (&obj1, &obj2))
     {
-    case FIXNUM_T:
+    case LAZY_MARKER_T:
+      {
+	switch (opcode)
+	  {
+	  case Bmax:
+	    return make_fixnum (marker_position
+				((byte_marker_position (obj1)
+				  < byte_marker_position (obj2)) ?
+				 obj2 : obj1));
+	  case Bmin:
+	    return make_fixnum (marker_position
+				((byte_marker_position (obj1)
+				  > byte_marker_position (obj2)) ?
+				 obj2 : obj1));
+	  default:
+	    obj1 = make_fixnum (marker_position (obj1));
+	    obj2 = make_fixnum (marker_position (obj2));
+	    /* FALLTHROUGH */
+	  }
+      }
+    case LAZY_FIXNUM_T:
       {
 	EMACS_INT ival1 = XREALFIXNUM (obj1), ival2 = XREALFIXNUM (obj2);
 	switch (opcode)
@@ -395,7 +434,7 @@
 	return make_integer (ival1);
       }
 #ifdef HAVE_BIGNUM
-    case BIGNUM_T:
+    case LAZY_BIGNUM_T:
       switch (opcode)
 	{
 	case Bplus:
@@ -426,7 +465,7 @@
       return Fcanonicalize_number (make_bignum_bg (scratch_bignum));
 #endif
 #ifdef HAVE_RATIO
-    case RATIO_T:
+    case LAZY_RATIO_T:
       switch (opcode)
 	{
 	case Bplus:
@@ -453,7 +492,7 @@
       return make_ratio_rt (scratch_ratio);
 #endif
 #ifdef HAVE_BIGFLOAT
-    case BIGFLOAT_T:
+    case LAZY_BIGFLOAT_T:
       bigfloat_set_prec (scratch_bigfloat, max (XBIGFLOAT_GET_PREC (obj1),
 						XBIGFLOAT_GET_PREC (obj2)));
       switch (opcode)
--- a/src/data.c	Sun Dec 15 09:57:28 2013 +0000
+++ b/src/data.c	Sun Dec 15 10:26:31 2013 +0000
@@ -899,7 +899,7 @@
 
 #ifdef HAVE_BIGNUM
 #define BIGNUM_CASE(op)							\
-	case BIGNUM_T:							\
+        case LAZY_BIGNUM_T:                                             \
 	  if (!bignum_##op (XBIGNUM_DATA (obj1), XBIGNUM_DATA (obj2)))	\
 	    return Qnil;						\
 	  break;
@@ -909,7 +909,7 @@
 
 #ifdef HAVE_RATIO
 #define RATIO_CASE(op)							\
-	case RATIO_T:							\
+        case LAZY_RATIO_T:                                              \
 	  if (!ratio_##op (XRATIO_DATA (obj1), XRATIO_DATA (obj2)))	\
 	    return Qnil;						\
 	  break;
@@ -919,7 +919,7 @@
 
 #ifdef HAVE_BIGFLOAT
 #define BIGFLOAT_CASE(op)						\
-	case BIGFLOAT_T:						\
+	case LAZY_BIGFLOAT_T:						\
 	  if (!bigfloat_##op (XBIGFLOAT_DATA (obj1), XBIGFLOAT_DATA (obj2))) \
 	    return Qnil;						\
 	  break;
@@ -936,24 +936,33 @@
     {								\
       obj1 = args[i - 1];					\
       obj2 = args[i];						\
-      switch (promote_args (&obj1, &obj2))			\
+      switch (promote_args_lazy (&obj1, &obj2))                 \
 	{							\
-	case FIXNUM_T:						\
-	  if (!(XREALFIXNUM (obj1) c_op XREALFIXNUM (obj2)))		\
+        case LAZY_FIXNUM_T:                                     \
+          if (!(XREALFIXNUM (obj1) c_op XREALFIXNUM (obj2)))    \
 	    return Qnil;					\
 	  break;						\
 	BIGNUM_CASE (op)					\
 	RATIO_CASE (op)						\
-	case FLOAT_T:						\
+        case LAZY_FLOAT_T:                                      \
 	  if (!(XFLOAT_DATA (obj1) c_op XFLOAT_DATA (obj2)))	\
 	    return Qnil;					\
 	  break;						\
 	BIGFLOAT_CASE (op)					\
+        case LAZY_MARKER_T:                                     \
+          if (!(byte_marker_position (obj1) c_op                \
+                byte_marker_position (obj2)))                   \
+            return Qnil;                                        \
+          break;                                                \
 	}							\
     }								\
   return Qt;							\
 }
 #else /* !WITH_NUMBER_TYPES */
+/* We don't convert markers lazily here, although we could. It's more
+   important that we do this lazily in bytecode, which is the case; see
+   bytecode_arithcompare().
+   */
 #define ARITHCOMPARE_MANY(c_op,op)				\
 {								\
   int_or_double iod1, iod2, *p = &iod1, *q = &iod2;		\
--- a/src/number.h	Sun Dec 15 09:57:28 2013 +0000
+++ b/src/number.h	Sun Dec 15 10:26:31 2013 +0000
@@ -373,11 +373,42 @@
 
 EXFUN (Fcanonicalize_number, 1);
 
-enum number_type {FIXNUM_T, BIGNUM_T, RATIO_T, FLOAT_T, BIGFLOAT_T};
+#define NUMBER_TYPES(prefix) prefix##FIXNUM_T, prefix##BIGNUM_T, \
+    prefix##RATIO_T, prefix##FLOAT_T, prefix##BIGFLOAT_T
+
+enum number_type { NUMBER_TYPES() };
+enum lazy_number_type { NUMBER_TYPES(LAZY_), LAZY_MARKER_T };
+
+#undef NUMBER_TYPES
 
 extern enum number_type get_number_type (Lisp_Object);
 extern enum number_type promote_args (Lisp_Object *, Lisp_Object *);
 
+/* promote_args() *always* converts a marker argument to a fixnum.
+
+   Unfortunately, for a marker with byte position N, getting the (character)
+   marker position is O(N). Getting the character position isn't necessary
+   for bytecode_arithcompare() if two markers being compared are in the same
+   buffer, comparing the byte position is enough.
+
+   Similarly, min and max don't necessarily need to have their arguments
+   converted from markers, though we have always promised up to this point
+   that the result is a fixnum rather than a marker, and that's what we're
+   continuing to do. */
+
+DECLARE_INLINE_HEADER (
+enum lazy_number_type
+promote_args_lazy (Lisp_Object *obj1, Lisp_Object *obj2))
+{
+  if (MARKERP (*obj1) && MARKERP (*obj2) &&
+      XMARKER (*obj1)->buffer == XMARKER (*obj2)->buffer)
+    {
+      return LAZY_MARKER_T;
+    }
+
+  return (enum lazy_number_type) promote_args (obj1, obj2);
+}
+
 #ifdef WITH_NUMBER_TYPES
 DECLARE_INLINE_HEADER (
 int
--- a/tests/ChangeLog	Sun Dec 15 09:57:28 2013 +0000
+++ b/tests/ChangeLog	Sun Dec 15 10:26:31 2013 +0000
@@ -1,3 +1,9 @@
+2013-12-15  Aidan Kehoe  <kehoea@parhasard.net>
+
+	* automated/lisp-tests.el:
+	Test arithmetic comparisons with markers, check the type of the
+	returned values for #'min and #'max.
+
 2013-09-15  Mats Lidell  <matsl@xemacs.org>
 
 	* automated/files-tests.el: New file. Test new states in
--- a/tests/automated/lisp-tests.el	Sun Dec 15 09:57:28 2013 +0000
+++ b/tests/automated/lisp-tests.el	Sun Dec 15 10:26:31 2013 +0000
@@ -3041,4 +3041,83 @@
                 (macroexpand '(with-second-arguments)))))
    (with-both-arguments (list))))
 
+;; Test arithmetic comparisons of markers and operations on markers. Most
+;; relevant with Mule, but also worth doing on non-Mule.
+(let ((character (if (featurep 'mule) (decode-char 'ucs #x20ac) ?\xff))
+      (translation (make-char-table 'generic))
+      markers fixnums)
+  (macrolet
+      ((Assert-arith-equivalences (markers context)
+	 `(progn
+	   (Assert (apply #'> markers)
+		   ,(concat "checking #'> correct with long arguments list, "
+		     context))
+	   (Assert 0 ,context)
+	   (Assert (apply #'< (reverse markers))
+		   ,(concat "checking #'< correct with long arguments list, "
+			    context))
+	   (map-plist #'(lambda (object1 object2)
+			  (Assert (> object1 object2)
+				  ,(concat 
+				    "checking markers correctly ordered, >, "
+				    context))
+			  (Assert (< object2 object1)
+				  ,(concat
+				    "checking markers correctly ordered, <, "
+				    context)))
+		      markers)
+	   ;; OK, so up to this point there has been no need for byte-char
+	   ;; conversion. The following requires it, though:
+	   (map-plist #'(lambda (object1 object2)
+			  (Assert
+			   (= (max object1 object2) object1)
+			   ,(concat
+			     "checking max correct, two markers, " context))
+			  (Assert
+			   (= (min object1 object2) object2)
+			   ,(concat
+			     "checking min, correct, two markers, " context))
+			  ;; It is probably reasonable to change this design
+			  ;; decision.
+			  (Assert
+			   (fixnump (max object1 object2))
+			   ,(concat
+			     "checking fixnum conversion as documented, max, "
+			     context))
+			  (Assert
+			   (fixnump (min object1 object2))
+			   ,(concat
+			     "checking fixnum conversion as documented, min, "
+			     context)))
+	              markers))))
+    (with-temp-buffer
+      (princ "hello there, in with-temp-buffer\n" (get-buffer "*scratch*"))
+      (loop for ii from 0 to 100
+	do (progn
+	     (insert " " character " " character " " character " "
+			 character "\n")
+	     (insert character)
+	     (push (copy-marker (1- (point)) t) markers)
+	     (insert ?\x20)
+	     (push (copy-marker (1- (point)) t) markers)))
+      (Assert-arith-equivalences markers "with Euro sign")
+      ;; Save the markers as fixnum character positions:
+      (setq fixnums (mapcar #'marker-position markers))
+      ;; Check that the equivalences work with the fixnums, while we
+      ;; have them:
+      (Assert-arith-equivalences fixnums "fixnums, with Euro sign")
+      ;; Now, transform the characters that may be problematic to ASCII,
+      ;; check our equivalences still hold.
+      (put-char-table character ?\x7f translation)
+      (translate-region (point-min) (point-max) translation)
+      ;; Sigh, restore the markers #### shouldn't the insertion and
+      ;; deletion code do this?!
+      (map nil #'set-marker markers fixnums)
+      (Assert-arith-equivalences markers "without Euro sign")
+      ;; Restore the problematic character.
+      (put-char-table ?\x7f character translation)
+      (translate-region (point-min) (point-max) translation)
+      (map nil #'set-marker markers fixnums)
+      (Assert-arith-equivalences markers "with Euro sign restored"))))
+
 ;;; end of lisp-tests.el