summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2009-11-09 22:03:12 +0000
committerPaul Phillips <paulp@improving.org>2009-11-09 22:03:12 +0000
commita0159da70da18f65863947aa4d016ca52fd7bb4f (patch)
tree916acfd275d3c0bf3c59aa9d599fed37162c2c9a
parent26a13165f4a00ce8c0336058fbde937ddb59e718 (diff)
downloadscala-a0159da70da18f65863947aa4d016ca52fd7bb4f.tar.gz
scala-a0159da70da18f65863947aa4d016ca52fd7bb4f.tar.bz2
scala-a0159da70da18f65863947aa4d016ca52fd7bb4f.zip
Breaks down the hash function in BoxesRunTime b...
Breaks down the hash function in BoxesRunTime by type so we can do as much as possible at compile time. Documents various trouble points I've identified with trying to get the hashCodes aligned. Test case exercises the hashCode functions. Deleted all the code associated with previous equality adventures. Don't worry, I can put anything back if it turns out we have to change course again, but for now it's noise. Also, gives return types to the box and unbox methods which are added to the primitive companions, so e.g. Int.box(5) now returns a j.l.Integer instead of an Object.
-rw-r--r--src/compiler/scala/tools/nsc/Settings.scala10
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala25
-rw-r--r--src/compiler/scala/tools/nsc/symtab/Definitions.scala13
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala6
-rw-r--r--src/library/scala/Predef.scala7
-rw-r--r--src/library/scala/runtime/BoxesRunTime.java129
-rw-r--r--src/library/scala/runtime/Equality.java89
-rw-r--r--test/files/run/hashCodeBoxesRunTime.scala28
8 files changed, 98 insertions, 209 deletions
diff --git a/src/compiler/scala/tools/nsc/Settings.scala b/src/compiler/scala/tools/nsc/Settings.scala
index 95c3a56767..0f59d9a3d9 100644
--- a/src/compiler/scala/tools/nsc/Settings.scala
+++ b/src/compiler/scala/tools/nsc/Settings.scala
@@ -839,16 +839,6 @@ trait ScalacSettings {
val YhigherKindedRaw = BooleanSetting ("-Yhigher-kinded-raw", "(temporary!) Treat raw Java types as higher-kinded types.")
val Yjenkins = BooleanSetting ("-Yjenkins-hashCodes", "Use jenkins hash algorithm for case class generated hashCodes.")
- // Equality specific
- val logEqEq = BooleanSetting ("-Ylog-eqeq", "Log all noteworthy equality tests") .
- withPostSetHook(() => scala.runtime.Equality.logEverything = true)
- val YfutureEqEq = BooleanSetting ("-Yfuture-eqeq", "Use proposed overloading-based numeric equality semantics.") .
- withPostSetHook(() => scala.runtime.Equality.use28Semantics = true)
- val YwarnEqEq = BooleanSetting ("-Ywarn-eqeq", "Warn when boxed primitives of different types are compared.") .
- withPostSetHook(() => scala.runtime.Equality.warnOnBoxedCompare = true)
- val YdieEqEq = BooleanSetting ("-Ydie-changed-eqeq", "Throw an exception if a comparison would have come back differently in scala 2.7.") .
- withPostSetHook(() => scala.runtime.Equality.dieOnBoxedCompareIfValuesAreEqual = true)
-
// Warnings
val Xwarninit = BooleanSetting ("-Xwarninit", "Warn about possible changes in initialization semantics")
val Xchecknull = BooleanSetting ("-Xcheck-null", "Emit warning on selection of nullable reference")
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
index d12bf0c2c5..e1ddd260cb 100644
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
@@ -1292,9 +1292,6 @@ abstract class GenICode extends SubComponent {
elseCtx: Context): Unit =
{
def genComparisonOp(l: Tree, r: Tree, code: Int) {
- if (settings.logEqEq.value && isUniversalEqualityOp(code))
- logEqEq(tree, l, r, code)
-
val op: TestOp = code match {
case scalaPrimitives.LT => LT
case scalaPrimitives.LE => LE
@@ -1423,13 +1420,8 @@ abstract class GenICode extends SubComponent {
(rsym == ObjectClass) ||
(lsym != rsym) && (isBoxed(lsym) || isBoxed(rsym))
}
- def cannotAvoidBoxesRuntime =
- settings.logEqEq.value || settings.YwarnEqEq.value || settings.YdieEqEq.value
- /** We can avoid generating calls to BoxesRuntime only if -Yfuture-eqeq
- * is enabled AND none of the eqeq logging options are enabled.
- */
- if (mustUseAnyComparator && (!settings.YfutureEqEq.value || cannotAvoidBoxesRuntime)) {
+ if (mustUseAnyComparator) {
var equalsMethod = BoxesRunTime_equals
// when -optimise is on we call the @inline-version of equals, found in ScalaRunTime
if (settings.XO.value) {
@@ -2161,19 +2153,4 @@ abstract class GenICode extends SubComponent {
override def toString() = "[]"
override def varsInScope: Buffer[Local] = new ListBuffer
}
-
- /** Log equality tests between different primitives. */
- def logEqEq(tree: Tree, l: Tree, r: Tree, code: Int) {
- import definitions._
- val op = if (code == scalaPrimitives.EQ) "==" else if (code == scalaPrimitives.NE) "!=" else "??"
- val tkl = toTypeKind(l.tpe)
- val tkr = toTypeKind(r.tpe)
-
- if (tkl.isNumericType && tkr.isNumericType && tkl != tkr)
- runtime.Equality.logComparison(
- "Comparing actual primitives",
- "%s %s %s".format(l.tpe, op, r.tpe),
- tree.pos.source + ":" + tree.pos.line
- )
- }
}
diff --git a/src/compiler/scala/tools/nsc/symtab/Definitions.scala b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
index 365bff38e6..cb9c681075 100644
--- a/src/compiler/scala/tools/nsc/symtab/Definitions.scala
+++ b/src/compiler/scala/tools/nsc/symtab/Definitions.scala
@@ -557,11 +557,9 @@ trait Definitions {
mclass.setInfo(ClassInfoType(List(), new Scope, mclass))
module.setInfo(mclass.tpe)
- val box = newMethod(mclass, nme.box, List(clazz.typeConstructor),
- ObjectClass.typeConstructor)
+ val box = newMethod(mclass, nme.box, List(clazz.typeConstructor), boxedClass(clazz).tpe)
boxMethod(clazz) = box
- val unbox = newMethod(mclass, nme.unbox, List(ObjectClass.typeConstructor),
- clazz.typeConstructor)
+ val unbox = newMethod(mclass, nme.unbox, List(ObjectClass.typeConstructor), clazz.typeConstructor)
unboxMethod(clazz) = unbox
clazz
@@ -624,10 +622,11 @@ trait Definitions {
// def +(s: String): String
newMethod(clazz, nme.ADD, List(stringtype), stringtype)
- val restype = clazz match {
- case LongClass | FloatClass | DoubleClass => clazz.typeConstructor
- case _ => inttype
+ def isLongFloatOrDouble = clazz match {
+ case LongClass | FloatClass | DoubleClass => true
+ case _ => false
}
+ val restype = if (isLongFloatOrDouble) clazz.typeConstructor else inttype
// shift operations
if (isCardinal)
diff --git a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
index e8bb4581f1..ba328b9f48 100644
--- a/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala
@@ -152,12 +152,6 @@ abstract class ConstantFolder {
}
private def foldBinop(op: Name, x: Constant, y: Constant): Constant = {
- // temporarily logging folded ==/!= so the log doesn't have unexplained absences
- // Careful, these four lines added 3 minutes to the time to compile this file under -optimise
- // if ((op == nme.EQ || op == nme.NE) && x.tag != y.tag && settings.logEqEq.value) {
- // val opstr = if (op == nme.EQ) "==" else "!="
- // scala.runtime.Equality.log("Folding constant expression (%s %s %s)".format(x.value, opstr, y.value))
- // }
val optag =
if (x.tag == y.tag) x.tag
else if (isNumeric(x.tag) && isNumeric(y.tag)) Math.max(x.tag, y.tag)
diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala
index c3e0548183..c44ec72299 100644
--- a/src/library/scala/Predef.scala
+++ b/src/library/scala/Predef.scala
@@ -96,12 +96,13 @@ object Predef extends LowPriorityImplicits {
// hashcode -----------------------------------------------------------
@inline def hash(x: Any): Int =
- if (x.isInstanceOf[Number]) runtime.BoxesRunTime.numHash(x.asInstanceOf[Number]) else x.hashCode
+ if (x.isInstanceOf[Number]) runtime.BoxesRunTime.hashFromNumber(x.asInstanceOf[Number])
+ else x.hashCode
@inline def hash(x: Number): Int =
- runtime.BoxesRunTime.numHash(x.asInstanceOf[Number])
+ runtime.BoxesRunTime.hashFromNumber(x)
- @inline def hash(x: Long): Int = {
+ @inline def hash(x: java.lang.Long): Int = {
val iv = x.intValue
if (iv == x.longValue) iv else x.hashCode
}
diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java
index c965bc2e95..b4252ed647 100644
--- a/src/library/scala/runtime/BoxesRunTime.java
+++ b/src/library/scala/runtime/BoxesRunTime.java
@@ -178,76 +178,65 @@ public class BoxesRunTime
return x.intValue() == ch;
}
- public static int numHash(Number n) {
- if (n instanceof Long) {
- int iv = n.intValue();
- long lv = n.longValue();
- if (lv == iv) return iv;
- } else if (n instanceof Float) {
- int iv = n.intValue();
- float fv = n.floatValue();
- if (fv == iv) return iv;
- long lv = n.longValue();
- if (fv == lv) return new Long(lv).hashCode();
- } else if (n instanceof Double) {
- int iv = n.intValue();
- double dv = n.doubleValue();
- if (dv == iv) return iv;
- float fv = n.floatValue();
- if (dv == fv) return new Float(fv).hashCode();
- long lv = n.longValue();
- if (dv == lv) return new Long(lv).hashCode();
- }
- return n.hashCode();
- }
-
- private static boolean equalsBonusLogicFromScala27(Object a, Object b) {
- if (a instanceof Number || a instanceof Character || b instanceof Number || b instanceof Character) {
- int acode = typeCode(a);
- int bcode = typeCode(b);
- int maxcode = (acode < bcode) ? bcode : acode;
- boolean res = false;
- if (maxcode <= INT) {
- int aa = (acode == CHAR) ? ((Character) a).charValue() : ((Number) a).intValue();
- int bb = (bcode == CHAR) ? ((Character) b).charValue() : ((Number) b).intValue();
- res = (aa == bb);
- }
- if (maxcode <= LONG) {
- long aa = (acode == CHAR) ? ((Character) a).charValue() : ((Number) a).longValue();
- long bb = (bcode == CHAR) ? ((Character) b).charValue() : ((Number) b).longValue();
- res = (aa == bb);
- }
- if (maxcode <= FLOAT) {
- float aa = (acode == CHAR) ? ((Character) a).charValue() : ((Number) a).floatValue();
- float bb = (bcode == CHAR) ? ((Character) b).charValue() : ((Number) b).floatValue();
- res = (aa == bb);
- }
- if (maxcode <= DOUBLE) {
- double aa = (acode == CHAR) ? ((Character) a).charValue() : ((Number) a).doubleValue();
- double bb = (bcode == CHAR) ? ((Character) b).charValue() : ((Number) b).doubleValue();
- res = (aa == bb);
- }
-
- if (res || b.equals(a)) return true;
- else return false;
- }
- return false;
- }
-
- private static String logMessage(Object a, Object b, String where) {
- return "Compared boxed primitives (" + boxDescription(a) + " == " + boxDescription(b) + ") @ " + where;
- }
-
- private static void verifyEqEq(Object a, Object b, boolean isFatal, boolean onlyIfActuallyEqual) {
- int code1 = typeCode(a);
- int code2 = typeCode(b);
-
- if (code1 < OTHER && code2 < OTHER && code1 != code2) {
- if (!onlyIfActuallyEqual || equalsBonusLogicFromScala27(a, b)) {
- String msg = logMessage(a, b, Equality.whereAreWe(5));
- Equality.warnOrDie(msg, isFatal);
- }
- }
+ /** Hashcode algorithm is driven by the requirements imposed
+ * by primitive equality semantics, namely that equal objects
+ * have equal hashCodes. The first priority are the integral/char
+ * types, which already have the same hashCodes for the same
+ * values except for Long. So Long's hashCode is altered to
+ * conform to Int's for all values in Int's range.
+ *
+ * Float is problematic because it's far too small to hold
+ * all the Ints, so for instance Int.MaxValue.toFloat claims
+ * to be == to each of the largest 64 Ints. There is no way
+ * to preserve equals/hashCode alignment without compromising
+ * the hashCode distribution, so Floats are only guaranteed
+ * to have the same hashCode for whole Floats in the range
+ * Short.MinValue to Short.MaxValue (2^16 total.)
+ *
+ * Double has its hashCode altered to match the entire Int range,
+ * but is not guaranteed beyond that. (But could/should it be?
+ * The hashCode is only 32 bits so this is a more tractable
+ * issue than Float's, but it might be better simply to exclude it.)
+ *
+ * Note: BigInt and BigDecimal, being arbitrary precision, could
+ * be made consistent with all other types for the Int range, but
+ * as yet have not.
+ *
+ * Note: Among primitives, Float.NaN != Float.NaN, but the boxed
+ * verisons are equal. This still needs reconciliation.
+ */
+ public static int hashFromLong(Long n) {
+ int iv = n.intValue();
+ if (iv == n.longValue()) return iv;
+ else return n.hashCode();
+ }
+ public static int hashFromDouble(Double n) {
+ int iv = n.intValue();
+ double dv = n.doubleValue();
+ if (iv == dv) return iv;
+
+ long lv = n.longValue();
+ if (lv == dv) return Long.valueOf(lv).hashCode();
+ else return n.hashCode();
+ }
+ public static int hashFromFloat(Float n) {
+ int iv = n.intValue();
+ float fv = n.floatValue();
+ if (iv == fv) return iv;
+
+ long lv = n.longValue();
+ if (lv == fv) return Long.valueOf(lv).hashCode();
+ else return n.hashCode();
+ }
+ public static int hashFromNumber(Number n) {
+ if (n instanceof Long) return hashFromLong((Long)n);
+ else if (n instanceof Double) return hashFromDouble((Double)n);
+ else if (n instanceof Float) return hashFromFloat((Float)n);
+ else return n.hashCode();
+ }
+ public static int hashFromObject(Object a) {
+ if (a instanceof Number) return hashFromNumber((Number)a);
+ else return a.hashCode();
}
/* OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS ... OPERATORS */
diff --git a/src/library/scala/runtime/Equality.java b/src/library/scala/runtime/Equality.java
deleted file mode 100644
index 52ed7005cd..0000000000
--- a/src/library/scala/runtime/Equality.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* __ *\
-** ________ ___ / / ___ Scala API **
-** / __/ __// _ | / / / _ | (c) 2006-2009, LAMP/EPFL **
-** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
-** /____/\___/_/ |_/____/_/ | | **
-** |/ **
-\* */
-
-// $Id$
-
-
-package scala.runtime;
-
-import java.io.*;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.logging.Handler;
-import java.util.logging.StreamHandler;
-import java.util.logging.SimpleFormatter;
-
-/** An object (static class) encapsulating the variations on equality
- * presently under consideration. It's written in java so it can easily
- * be made available to BoxesRunTime as well as scala code.
- */
-public class Equality
-{
- public static boolean logEverything = false;
- public static boolean use28Semantics = false;
-
- public static boolean warnOnBoxedCompare = false;
- public static boolean warnOnBoxedCompareIfValuesAreEqual = false;
- public static boolean dieOnBoxedCompare = false;
- public static boolean dieOnBoxedCompareIfValuesAreEqual = false;
-
- private static Handler handler;
- public static Logger logger;
- public static final Logger defaultLogger;
-
- static {
- class EqualityLogger extends Logger {
- EqualityLogger() {
- super("EqualityLogger", null);
- }
- }
- defaultLogger = new EqualityLogger();
-
- handler = new StreamHandler(System.out, new SimpleFormatter());
- handler.setLevel(Level.INFO);
- defaultLogger.addHandler(handler);
-
- logger = defaultLogger;
- }
-
- public static void warnOrDie(String msg, boolean isFatal) {
- if (isFatal) throw new RuntimeException(msg);
- else log(msg);
- // else System.out.println(msg);
- }
-
- public static String obToString(Object o) {
- String s = o.toString() + " (" + o.getClass().getSimpleName() + ")";
- return s.replaceAll("\\n", " ");
- }
-
- public static void logComparison(String msg, String cmp, String where) {
- log(String.format("%s (%s) at %s", msg, cmp, where));
- }
-
- public static String whereAreWe() { return whereAreWe(4); }
- public static String whereAreWe(int depth) {
- StackTraceElement[] es = Thread.currentThread().getStackTrace();
- if (es.length < depth)
- return "<unknown>";
-
- StackTraceElement e = es[depth - 1];
- String clazz = e.getClassName().replaceAll("\\$.*$", "\\$...");
- return String.format("%s.%s(%s.%d)", clazz, e.getMethodName(), e.getFileName(), e.getLineNumber());
- }
-
- public static void log(String msg) {
- if (logEverything) {
- if (logger != null) {
- logger.warning(msg);
- handler.flush();
- }
- else System.out.println(msg);
- }
- }
-}
diff --git a/test/files/run/hashCodeBoxesRunTime.scala b/test/files/run/hashCodeBoxesRunTime.scala
new file mode 100644
index 0000000000..3eacacb663
--- /dev/null
+++ b/test/files/run/hashCodeBoxesRunTime.scala
@@ -0,0 +1,28 @@
+// This only tests direct access to the methods in BoxesRunTime,
+// not the whole scheme.
+object Test
+{
+ import java.{ lang => jl }
+ import scala.runtime.BoxesRunTime.{ hashFromNumber, hashFromObject }
+
+ def allSame[T](xs: List[T]) = assert(xs.removeDuplicates.size == 1, "failed: " + xs)
+
+ def mkNumbers(x: Int): List[Number] =
+ List(x.toByte, x.toShort, x, x.toLong, x.toFloat, x.toDouble)
+
+ def testLDF(x: Long) = allSame(List[Number](x, x.toDouble, x.toFloat) map hashFromNumber)
+
+ def main(args: Array[String]): Unit = {
+ List(Byte.MinValue, -1, 0, 1, Byte.MaxValue) foreach { n =>
+ val hashes = mkNumbers(n) map hashFromNumber
+ allSame(hashes)
+ if (n >= 0) {
+ val charCode = hashFromObject(n.toChar: Character)
+ assert(charCode == hashes.head)
+ }
+ }
+
+ testLDF(Short.MaxValue.toLong)
+ testLDF(Short.MinValue.toLong)
+ }
+}