diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/Settings.scala | 10 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/GenICode.scala | 25 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/Definitions.scala | 13 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/typechecker/ConstantFolder.scala | 6 | ||||
-rw-r--r-- | src/library/scala/Predef.scala | 7 | ||||
-rw-r--r-- | src/library/scala/runtime/BoxesRunTime.java | 129 | ||||
-rw-r--r-- | src/library/scala/runtime/Equality.java | 89 | ||||
-rw-r--r-- | test/files/run/hashCodeBoxesRunTime.scala | 28 |
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) + } +} |