diff options
author | Paul Phillips <paulp@improving.org> | 2010-03-19 21:48:42 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-03-19 21:48:42 +0000 |
commit | 6613b1cdae66ae23edfd0e20eb5d4d066018681d (patch) | |
tree | c033a214a3d7e15ca01cdf778b88ac6e026c0fb7 | |
parent | f2be3e6836014b3e4db9c7eca3bb4da8ab447f89 (diff) | |
download | scala-6613b1cdae66ae23edfd0e20eb5d4d066018681d.tar.gz scala-6613b1cdae66ae23edfd0e20eb5d4d066018681d.tar.bz2 scala-6613b1cdae66ae23edfd0e20eb5d4d066018681d.zip |
Returning to the thrilling world of equality an...
Returning to the thrilling world of equality and hashCodes now
that Any.## is a reality. Moved the hash functions from Predef to
ScalaRunTime, and made what appears to be an optimization to equals by
not losing the result of an instanceof test. Review by community.
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/JavaPlatform.scala | 3 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/GenICode.scala | 30 | ||||
-rw-r--r-- | src/library/scala/Predef.scala | 16 | ||||
-rw-r--r-- | src/library/scala/runtime/BoxesRunTime.java | 103 | ||||
-rw-r--r-- | src/library/scala/runtime/ScalaRunTime.scala | 43 | ||||
-rw-r--r-- | test/files/run/equality.scala | 2 |
6 files changed, 125 insertions, 72 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala index 129a747e64..c4365a82ac 100644 --- a/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala +++ b/src/compiler/scala/tools/nsc/backend/JavaPlatform.scala @@ -26,6 +26,9 @@ trait JavaPlatform extends Platform[AbstractFile] { ) ::: depAnalysisPhase lazy val externalEquals = getMember(BoxesRunTimeClass, nme.equals_) + def externalEqualsNumNum = getMember(BoxesRunTimeClass, "equalsNumNum") + def externalEqualsNumChar = getMember(BoxesRunTimeClass, "equalsNumChar") + def externalEqualsNumObject = getMember(BoxesRunTimeClass, "equalsNumObject") def isMaybeBoxed(sym: Symbol): Boolean = { import definitions._ diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 64e6759da2..77e14139ba 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -9,7 +9,8 @@ package scala.tools.nsc package backend package icode -import scala.collection.mutable.{Map, HashMap, ListBuffer, Buffer, HashSet} +import scala.collection.{ mutable, immutable } +import scala.collection.mutable.{ HashMap, ListBuffer, Buffer, HashSet } import scala.tools.nsc.symtab._ import scala.annotation.switch import PartialFunction._ @@ -28,6 +29,7 @@ abstract class GenICode extends SubComponent { import definitions.{ ArrayClass, ObjectClass, ThrowableClass, StringClass, NothingClass, NullClass, Object_equals, Object_isInstanceOf, Object_asInstanceOf, ScalaRunTimeModule, + BoxedNumberClass, BoxedCharacterClass, getMember } import scalaPrimitives.{ @@ -1362,13 +1364,29 @@ abstract class GenICode extends SubComponent { * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character. * When it is statically known that both sides are equal and subtypes of Number of Character, * not using the rich equality is possible (their own equals method will do ok.)*/ - def mustUseAnyComparator: Boolean = - isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol) + def mustUseAnyComparator: Boolean = { + def areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe) + !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol) + } if (mustUseAnyComparator) { // when -optimise is on we call the @inline-version of equals, found in ScalaRunTime val equalsMethod = - if (!settings.XO.value) platform.externalEquals + if (!settings.XO.value) { + def default = platform.externalEquals + platform match { + case x: JavaPlatform => + import x._ + if (l.tpe <:< BoxedNumberClass.tpe) { + if (r.tpe <:< BoxedNumberClass.tpe) externalEqualsNumNum + else if (r.tpe <:< BoxedCharacterClass.tpe) externalEqualsNumChar + else externalEqualsNumObject + } + else default + + case _ => default + } + } else { ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule)) getMember(ScalaRunTimeModule, nme.inlinedEquals) @@ -1603,7 +1621,7 @@ abstract class GenICode extends SubComponent { * to delay it any more: they will be used at some point. */ class DuplicateLabels(boundLabels: collection.Set[Symbol]) extends Transformer { - val labels: Map[Symbol, Symbol] = new HashMap + val labels: mutable.Map[Symbol, Symbol] = new HashMap var method: Symbol = _ var ctx: Context = _ @@ -2073,7 +2091,7 @@ abstract class GenICode extends SubComponent { * jumps to the given basic block. */ def patch(code: Code) { - def substMap: Map[Instruction, Instruction] = { + def substMap: mutable.Map[Instruction, Instruction] = { val map = new HashMap[Instruction, Instruction]() toPatch foreach (i => map += (i -> patch(i))) diff --git a/src/library/scala/Predef.scala b/src/library/scala/Predef.scala index 760d215d3f..d022b3e4a5 100644 --- a/src/library/scala/Predef.scala +++ b/src/library/scala/Predef.scala @@ -53,22 +53,6 @@ object Predef extends LowPriorityImplicits { @inline def locally[T](x: T): T = x - // hashcode ----------------------------------------------------------- - - @inline def hash(x: Any): Int = - if (x.isInstanceOf[Number]) runtime.BoxesRunTime.hashFromNumber(x.asInstanceOf[Number]) - else x.hashCode - - @inline def hash(x: Number): Int = - runtime.BoxesRunTime.hashFromNumber(x) - - @inline def hash(x: java.lang.Long): Int = { - val iv = x.intValue - if (iv == x.longValue) iv else x.hashCode - } - - @inline def hash(x: Int): Int = x - // errors and asserts ------------------------------------------------- def error(message: String): Nothing = throw new RuntimeException(message) diff --git a/src/library/scala/runtime/BoxesRunTime.java b/src/library/scala/runtime/BoxesRunTime.java index 087331e1c5..7c27835b5a 100644 --- a/src/library/scala/runtime/BoxesRunTime.java +++ b/src/library/scala/runtime/BoxesRunTime.java @@ -28,7 +28,7 @@ import scala.math.ScalaNumber; * @author Martin Odersky * @contributor Stepan Koltsov * @version 2.0 */ -public class BoxesRunTime +public final class BoxesRunTime { private static final int CHAR = 0, BYTE = 1, SHORT = 2, INT = 3, LONG = 4, FLOAT = 5, DOUBLE = 6, OTHER = 7; @@ -136,38 +136,51 @@ public class BoxesRunTime * in any case, we dispatch to it as soon as we spot one on either side. */ public static boolean equals2(Object x, Object y) { - if (x instanceof Number) { - Number xn = (Number)x; - - if (y instanceof Number) { - Number yn = (Number)y; - int xcode = eqTypeCode(xn); - int ycode = eqTypeCode(yn); - switch (ycode > xcode ? ycode : xcode) { - case INT: - return xn.intValue() == yn.intValue(); - case LONG: - return xn.longValue() == yn.longValue(); - case FLOAT: - return xn.floatValue() == yn.floatValue(); - case DOUBLE: - return xn.doubleValue() == yn.doubleValue(); - default: - if ((yn instanceof ScalaNumber) && !(xn instanceof ScalaNumber)) - return y.equals(x); - } - } else if (y instanceof Character) - return equalsNumChar(xn, (Character)y); - } else if (x instanceof Character) { - Character xc = (Character)x; - if (y instanceof Character) - return xc.charValue() == ((Character)y).charValue(); - if (y instanceof Number) - return equalsNumChar((Number)y, xc); - } + if (x instanceof Number) + return equalsNumObject((Number)x, y); + if (x instanceof Character) + return equalsCharObject((Character)x, y); + return x.equals(y); } + public static boolean equalsNumObject(Number xn, Object y) { + if (y instanceof Number) + return equalsNumNum(xn, (Number)y); + else if (y instanceof Character) + return equalsNumChar(xn, (Character)y); + + return xn.equals(y); + } + + public static boolean equalsNumNum(Number xn, Number yn) { + int xcode = eqTypeCode(xn); + int ycode = eqTypeCode(yn); + switch (ycode > xcode ? ycode : xcode) { + case INT: + return xn.intValue() == yn.intValue(); + case LONG: + return xn.longValue() == yn.longValue(); + case FLOAT: + return xn.floatValue() == yn.floatValue(); + case DOUBLE: + return xn.doubleValue() == yn.doubleValue(); + default: + if ((yn instanceof ScalaNumber) && !(xn instanceof ScalaNumber)) + return yn.equals(xn); + } + return xn.equals(yn); + } + + public static boolean equalsCharObject(Character xc, Object y) { + if (y instanceof Character) + return xc.charValue() == ((Character)y).charValue(); + if (y instanceof Number) + return equalsNumChar((Number)y, xc); + + return xc.equals(y); + } + private static boolean equalsNumChar(Number xn, Character yc) { char ch = yc.charValue(); switch (eqTypeCode(xn)) { @@ -212,27 +225,27 @@ public class BoxesRunTime * 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(); + 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; + 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(); + 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; + 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(); + 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); diff --git a/src/library/scala/runtime/ScalaRunTime.scala b/src/library/scala/runtime/ScalaRunTime.scala index d04824bae1..43d60f2383 100644 --- a/src/library/scala/runtime/ScalaRunTime.scala +++ b/src/library/scala/runtime/ScalaRunTime.scala @@ -166,7 +166,8 @@ object ScalaRunTime { @inline def inlinedEquals(x: Object, y: Object): Boolean = if (x eq y) true else if (x eq null) false - else if (x.isInstanceOf[java.lang.Number] || x.isInstanceOf[java.lang.Character]) BoxesRunTime.equals2(x, y) + else if (x.isInstanceOf[java.lang.Number]) BoxesRunTime.equalsNumObject(x.asInstanceOf[java.lang.Number], y) + else if (x.isInstanceOf[java.lang.Character]) BoxesRunTime.equalsCharObject(x.asInstanceOf[java.lang.Character], y) else x.equals(y) def _equals(x: Product, y: Any): Boolean = y match { @@ -174,10 +175,42 @@ object ScalaRunTime { case _ => false } - /** Just a stub for now, but I think this is where the Predef.hash - * methods should be. - */ - @inline def hash(x: Any): Int = Predef.hash(x) + // hashcode ----------------------------------------------------------- + + @inline def hash(x: Any): Int = + if (x.isInstanceOf[java.lang.Number]) BoxesRunTime.hashFromNumber(x.asInstanceOf[java.lang.Number]) + else x.hashCode + + @inline def hash(dv: Double): Int = { + val iv = dv.toInt + if (iv == dv) return iv + + val lv = dv.toLong + if (lv == dv) return lv.hashCode + else dv.hashCode + } + @inline def hash(fv: Float): Int = { + val iv = fv.toInt + if (iv == fv) return iv + + val lv = fv.toLong + if (lv == fv) return lv.hashCode + else fv.hashCode + } + @inline def hash(lv: Long): Int = { + val iv = lv.toInt + if (iv == lv) iv else lv.hashCode + } + @inline def hash(x: Int): Int = x + @inline def hash(x: Short): Int = x.toInt + @inline def hash(x: Byte): Int = x.toInt + @inline def hash(x: Char): Int = x.toInt + + @inline def hash(x: Number): Int = runtime.BoxesRunTime.hashFromNumber(x) + @inline def hash(x: java.lang.Long): Int = { + val iv = x.intValue + if (iv == x.longValue) iv else x.hashCode + } /** A helper method for constructing case class equality methods, * because existential types get in the way of a clean outcome and diff --git a/test/files/run/equality.scala b/test/files/run/equality.scala index 5b9ad207da..6498b232e1 100644 --- a/test/files/run/equality.scala +++ b/test/files/run/equality.scala @@ -1,6 +1,8 @@ // a quickly assembled test of equality. Needs work. object Test { + import scala.runtime.ScalaRunTime.hash + def makeFromInt(x: Int) = List( x.toByte, x.toShort, x.toInt, x.toLong, x.toFloat, x.toDouble, BigInt(x), BigDecimal(x) ) ::: ( |