Skip to content
27 changes: 27 additions & 0 deletions docs/hard-fork-changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## A list of hard-fork changes

Please describe here all changes which may lead to hard fork (HF for short).

**Pull requests based on the next HF branch should be rejected,
if they contain HF change, which is not described here**.

### Hard-fork changes in v0.7.0 (since v0.6.0)

1. Removed RW rule `case IsNumericToLong(Def(IsNumericToInt(x))) if x.elem == LongElement => x`
This is a bug, but its fix may lead to hard-fork.
Example:
The follwing expression `MaxLong.toInt.toLong == MaxLong`
with this rule will evaluate to `true`,
without this rule will throw ArithmeticException.
MaxLong here can be any Long which is larger than MaxInt.
With this rule the expression becomes `MaxLong == MaxLong`

2. Removed RW rule `case CM.map(CM.map(_xs, f: RFunc[a, b]), _g: RFunc[_,c]) =>`.
Such kind of transformations in general don't preserve expression equivalence
in a strict (Call-By-Value) language.
Having such rule is another bug, which is safe by itself, but cannot
be fixed without HF.

3. CReplColl.updated (bug fixed)
Added index boundary check throwing IndexOutOfBoundsException.
This check is necessary to preserve Coll.updated contract.
39 changes: 39 additions & 0 deletions library-impl/src/main/scala/scalan/RTypeUtil.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package scalan

import spire.syntax.all._
import spire.syntax.trig.trigOps

object RTypeUtil {
import scalan.RType._
import special.collection._

def clone[T](value: T)(implicit t: RType[T]): T = t match {
case prim: PrimitiveType[a] => value
case arrayType: ArrayType[a] =>
val array = value.asInstanceOf[Array[a]]
var copy = Array.ofDim[a](array.length)(arrayType.tA.classTag)
cfor(0)(_ < array.length, _ + 1) { i =>
copy(i) = clone(array(i))(arrayType.tA).asInstanceOf[a]
}
copy.asInstanceOf[T]
case pairType: PairType[a, b] =>
val pair = value.asInstanceOf[Tuple2[a, b]]
return (clone(pair._1)(pairType.tFst), clone(pair._2)(pairType.tSnd)).asInstanceOf[T]
case optionType: OptionType[a] =>
val option = value.asInstanceOf[Option[a]]
val cloned = if (option.isDefined) Some(clone(option.get)(optionType.tA)) else None
cloned.asInstanceOf[T]
case replCollType: ReplCollType[a] =>
val coll = value.asInstanceOf[ReplColl[a]]
val cloned = clone(coll.value)(replCollType.tItem)
(new CReplColl(cloned, coll.length)(replCollType.tItem)).asInstanceOf[T]
case collType: CollType[a] =>
val coll = value.asInstanceOf[Coll[a]]
val cloned = clone(coll.toArray)(ArrayType(collType.tItem))
coll.builder.fromArray(cloned)(collType.tItem).asInstanceOf[T]
case StringType =>
val arr = value.asInstanceOf[String].toArray
arr.mkString.asInstanceOf[T]
case _ => throw new RuntimeException(s"Can't clone ${t}.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,11 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A]
override def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] = {
requireSameLength(indexes, values)
val resArr = toArray.clone()
var i = 0
while (i < indexes.length) {
val limit = indexes.length
cfor(0)(_ < limit, _ + 1) { i =>
val pos = indexes(i)
if (pos < 0 || pos >= toArray.length) throw new IndexOutOfBoundsException(pos.toString)
resArr(pos) = values(i)
i += 1
}
builder.fromArray(resArr)
}
Expand Down Expand Up @@ -194,10 +193,10 @@ class CollOverArray[@specialized A](val toArray: Array[A])(implicit tA: RType[A]

@Internal
override def equals(obj: scala.Any): Boolean = obj match {
case obj: CollOverArray[_] if obj.tItem == this.tItem =>
util.Objects.deepEquals(obj.toArray, toArray)
case repl: CReplColl[A]@unchecked if repl.tItem == this.tItem =>
isReplArray(repl.length, repl.value)
case otherColl: Coll[A] if otherColl.tItem == this.tItem =>
util.Objects.deepEquals(otherColl.toArray, toArray)
case _ => false
}

Expand Down Expand Up @@ -326,14 +325,16 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R

@Internal
override def equals(that: scala.Any) = (this eq that.asInstanceOf[AnyRef]) || (that match {
case that: PairColl[_,_] if that.tItem == this.tItem => ls == that.ls && rs == that.rs
case that: ReplColl[(L,R)]@unchecked if that.tItem == this.tItem =>
ls.isReplArray(that.length, that.value._1) &&
rs.isReplArray(that.length, that.value._2)
case that: PairColl[_,_] if that.tItem == this.tItem =>
ls == that.ls && rs == that.rs
case that: Coll[_] if that.tItem == this.tItem =>
util.Objects.deepEquals(that.toArray, toArray)
case _ => false
})

@Internal
override def hashCode() = ls.hashCode() * 41 + rs.hashCode()
override def hashCode() = CollectionUtil.deepHashCode(toArray)

@Internal @inline
implicit def tL = ls.tItem
@Internal @inline
Expand Down Expand Up @@ -374,29 +375,25 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R
cfor(0)(_ < limit, _ + 1) { i =>
res(i) = f((ls(i), rs(i)))
}
new CollOverArray(res)
builder.fromArray(res)
}

@NeverInline
override def exists(p: ((L, R)) => Boolean): Boolean = {
val len = ls.length
var i = 0
while (i < len) {
cfor(0)(_ < len, _ + 1) { i =>
val found = p((ls(i), rs(i)))
if (found) return true
i += 1
}
false
}

@NeverInline
override def forall(p: ((L, R)) => Boolean): Boolean = {
val len = ls.length
var i = 0
while (i < len) {
cfor(0)(_ < len, _ + 1) { i =>
val ok = p((ls(i), rs(i)))
if (!ok) return false
i += 1
}
true
}
Expand All @@ -405,16 +402,14 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R
val len = ls.length
val resL: Buffer[L] = Buffer.empty[L](ls.tItem.classTag)
val resR: Buffer[R] = Buffer.empty[R](rs.tItem.classTag)
var i = 0
while (i < len) {
cfor(0)(_ < len, _ + 1) { i =>
val l = ls.apply(i)
val r = rs.apply(i)
val ok = p((l, r))
if (ok) {
resL += l
resR += r
}
i += 1
}
builder.pairCollFromArrays(resL.toArray(), resR.toArray())
}
Expand Down Expand Up @@ -510,13 +505,12 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R
requireSameLength(indexes, values)
val resL = ls.toArray.clone()
val resR = rs.toArray.clone()
var i = 0
while (i < indexes.length) {
val limit = indexes.length
cfor(0)(_ < limit, _ + 1) { i =>
val pos = indexes(i)
if (pos < 0 || pos >= length) throw new IndexOutOfBoundsException(pos.toString)
resL(pos) = values(i)._1
resR(pos) = values(i)._2
i += 1
}
builder.pairColl(builder.fromArray(resL), builder.fromArray(resR))
}
Expand All @@ -541,17 +535,13 @@ class PairOfCols[@specialized L, @specialized R](val ls: Coll[L], val rs: Coll[R
resR += item._2
}
}
var i = 0
val thisLen = math.min(ls.length, rs.length)
while (i < thisLen) {
val thisLimit = math.min(ls.length, rs.length)
cfor(0)(_ < thisLimit, _ + 1) { i =>
addToSet((ls(i), rs(i)))
i += 1
}
i = 0
val thatLen = that.length
while (i < thatLen) {
val thatLimit = that.length
cfor(0)(_ < thatLimit, _ + 1) { i =>
addToSet(that(i))
i += 1
}
builder.pairCollFromArrays(resL.toArray, resR.toArray)
}
Expand Down Expand Up @@ -598,7 +588,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp

@NeverInline
def getOrElse(i: Int, default: A): A = if (i >= 0 && i < this.length) value else default
def map[@specialized B: RType](f: A => B): Coll[B] = new CReplColl(f(value), length)
def map[@specialized B: RType](f: A => B): Coll[B] = builder.replicate(length, f(value))
@NeverInline
def foreach(f: A => Unit): Unit = (0 until length).foreach(_ => f(value))
@NeverInline
Expand All @@ -610,7 +600,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
if (length == 0) this
else
if (p(value)) this
else new CReplColl(value, 0)
else builder.replicate(0, value)

@NeverInline
def foldLeft[B](zero: B, op: ((B, A)) => B): B =
Expand All @@ -626,13 +616,13 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
val lo = math.max(from, 0)
val hi = math.min(math.max(until, 0), length)
val size = math.max(hi - lo, 0)
new CReplColl(value, size)
builder.replicate(size, value)
}

@NeverInline
def append(other: Coll[A]): Coll[A] = other match {
case repl: ReplColl[A@unchecked] if this.value == repl.value =>
new CReplColl(value, this.length + repl.length)
case repl: ReplColl[A@unchecked] if this.value == repl.value && this.length > 0 && repl.length > 0 =>
builder.replicate(this.length + repl.length, value)
case _ =>
builder.fromArray(toArray).append(builder.fromArray(other.toArray))
}
Expand Down Expand Up @@ -680,7 +670,7 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
if (n <= 0) builder.emptyColl
else {
val m = new RichInt(n).min(length)
new CReplColl(value, m)
builder.replicate(m, value)
}

@NeverInline
Expand All @@ -696,6 +686,8 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp

@NeverInline
override def updated(index: Int, elem: A): Coll[A] = {
if (index < 0 || index >= length)
throw new IndexOutOfBoundsException
if (elem == value) this
else {
val res = toArray.updated(index, elem)
Expand All @@ -707,12 +699,10 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
override def updateMany(indexes: Coll[Int], values: Coll[A]): Coll[A] = {
requireSameLength(indexes, values)
val resArr = toArray.clone()
var i = 0
while (i < indexes.length) {
cfor(0)(_ < indexes.length, _ + 1) { i =>
val pos = indexes(i)
if (pos < 0 || pos >= length) throw new IndexOutOfBoundsException(pos.toString)
resArr(pos) = values(i)
i += 1
}
builder.fromArray(resArr)
}
Expand All @@ -722,10 +712,8 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
if (length <= 0) return builder.pairColl(builder.emptyColl[K], builder.emptyColl[V])
val (k, v) = m(value)
var reducedV = v
var i = 1
while (i < length) {
cfor(1)(_ < length, _ + 1) { i =>
reducedV = r((reducedV, v))
i += 1
}
builder.pairColl(builder.fromItems(k), builder.fromItems(reducedV))
}
Expand All @@ -737,19 +725,19 @@ class CReplColl[@specialized A](val value: A, val length: Int)(implicit tA: RTyp
if (repl.length > 0) {
if (value == repl.value) {
// both replications have the same element `value`, just return it in a singleton set
new CReplColl(value, 1)
builder.replicate(1, value)
}
else {
builder.fromItems(value, repl.value)
}
}
else
new CReplColl(value, 1)
builder.replicate(1, value)
} else {
if (repl.length > 0) {
new CReplColl(repl.value, 1)
builder.replicate(1, repl.value)
} else
new CReplColl(value, 0) // empty set
builder.replicate(0, value) // empty set
}
case _ =>
if (this.length > 0)
Expand Down
4 changes: 1 addition & 3 deletions library-impl/src/main/scala/special/collection/Helpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ object Helpers {
val keyPositions = new java.util.HashMap[K, Int](32)
val keys = mutable.ArrayBuilder.make[K]
val values = Array.ofDim[V](arr.length)
var i = 0
var nValues = 0
while (i < arr.length) {
cfor(0)(_ < arr.length, _ + 1) { i =>
val (key, value) = m(arr(i))
val pos = keyPositions.getOrDefault(key, 0)
if (pos == 0) {
Expand All @@ -36,7 +35,6 @@ object Helpers {
} else {
values(pos - 1) = r((values(pos - 1), value))
}
i += 1
}
val resValues = Array.ofDim[V](nValues)
Array.copy(values, 0, resValues, 0, nValues)
Expand Down
Loading