diff --git a/kotlin-native/performance/ring/src/main/kotlin/main.kt b/kotlin-native/performance/ring/src/main/kotlin/main.kt index 562a9778c8c..b0b23a1a49f 100644 --- a/kotlin-native/performance/ring/src/main/kotlin/main.kt +++ b/kotlin-native/performance/ring/src/main/kotlin/main.kt @@ -218,6 +218,15 @@ class RingLauncher : Launcher() { "ParameterNotNull.invokeEightArgsWithNullCheck" to BenchmarkEntryWithInit.create(::ParameterNotNullAssertionBenchmark, { invokeEightArgsWithNullCheck() }), "ParameterNotNull.invokeEightArgsWithoutNullCheck" to BenchmarkEntryWithInit.create(::ParameterNotNullAssertionBenchmark, { invokeEightArgsWithoutNullCheck() }), "String.stringConcatNullable" to BenchmarkEntryWithInit.create(::StringBenchmark, { stringConcatNullable() }), + "SubList.concatenate" to BenchmarkEntryWithInit.create(::SubListBenchmark, { concatenate() }), + "SubList.concatenateManual" to BenchmarkEntryWithInit.create(::SubListBenchmark, { concatenateManual() }), + "SubList.filterAndCount" to BenchmarkEntryWithInit.create(::SubListBenchmark, { filterAndCount() }), + "SubList.filterAndCountWithLambda" to BenchmarkEntryWithInit.create(::SubListBenchmark, { filterAndCountWithLambda() }), + "SubList.countWithLambda" to BenchmarkEntryWithInit.create(::SubListBenchmark, { countWithLambda() }), + "SubList.filterManual" to BenchmarkEntryWithInit.create(::SubListBenchmark, { filterManual() }), + "SubList.countFilteredManual" to BenchmarkEntryWithInit.create(::SubListBenchmark, { countFilteredManual() }), + "SubList.countFiltered" to BenchmarkEntryWithInit.create(::SubListBenchmark, { countFiltered() }), + "SubList.reduce" to BenchmarkEntryWithInit.create(::SubListBenchmark, { reduce() }), "Switch.testSparseIntSwitch" to BenchmarkEntryWithInit.create(::SwitchBenchmark, { testSparseIntSwitch() }), "Switch.testDenseIntSwitch" to BenchmarkEntryWithInit.create(::SwitchBenchmark, { testDenseIntSwitch() }), "Switch.testConstSwitch" to BenchmarkEntryWithInit.create(::SwitchBenchmark, { testConstSwitch() }), diff --git a/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/SubListBenchmark.kt b/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/SubListBenchmark.kt new file mode 100644 index 00000000000..60508655a29 --- /dev/null +++ b/kotlin-native/performance/ring/src/main/kotlin/org/jetbrains/ring/SubListBenchmark.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.ring + +class SubListBenchmark { + private var _data: List? = null + + fun getData(subList: Boolean): List { + val data: List = _data!! + // 1 to ensure that .subList can't return the list itself + if (subList) return data.subList(1, data.size) + else return data + } + + init { + val list = ArrayList(BENCHMARK_SIZE) + for (n in classValues(BENCHMARK_SIZE)) + list.add(n) + _data = list + } + + //Benchmark + fun concatenate(): List { + return getData(false) + getData(true) + } + + //Benchmark + fun concatenateManual(): List { + val list = ArrayList(2 * BENCHMARK_SIZE) + // outer loop to ensure single call site for list and sublist + for (data in listOf(getData(false), getData(true))) { + for (item in data) { + list.add(item) + } + } + return list + } + + //Benchmark + fun filterAndCount(): Int { + var count = 0 + for (data in listOf(getData(false), getData(true))) { + count += data.filter { filterLoad(it) }.count() + } + return count + } + + //Benchmark + fun filterAndCountWithLambda(): Int { + var count = 0 + for (data in listOf(getData(false), getData(true))) { + count += data.filter { it.value % 2 == 0 }.count() + } + return count + } + + //Benchmark + fun countWithLambda(): Int { + var count = 0 + for (data in listOf(getData(false), getData(true))) { + count += data.count { it.value % 2 == 0 } + } + return count + } + + //Benchmark + fun filterManual(): List { + val list = ArrayList() + for (data in listOf(getData(false), getData(true))) { + for (it in data) { + if (filterLoad(it)) + list.add(it) + } + } + return list + } + + //Benchmark + fun countFilteredManual(): Int { + var count = 0 + for (data in listOf(getData(false), getData(true))) { + for (it in data) { + if (filterLoad(it)) + count++ + } + } + return count + } + + //Benchmark + fun countFiltered(): Int { + var count = 0 + for (data in listOf(getData(false), getData(true))) { + count += data.count { filterLoad(it) } + } + return count + } + + //Benchmark + fun reduce(): Int { + var res = 0 + for (data in listOf(getData(false), getData(true))) { + res = data.fold(res) { acc, it -> if (filterLoad(it)) acc + 1 else acc } + } + return res + } +} diff --git a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt index 2bc3da02598..da9116c4d9c 100644 --- a/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt +++ b/libraries/stdlib/native-wasm/src/kotlin/collections/ArrayList.kt @@ -7,20 +7,13 @@ package kotlin.collections actual class ArrayList private constructor( private var backingArray: Array, - private var offset: Int, private var length: Int, - private var isReadOnly: Boolean, - private val backingList: ArrayList?, - private val root: ArrayList? + private var isReadOnly: Boolean ) : MutableList, RandomAccess, AbstractMutableList() { private companion object { private val Empty = ArrayList(0).also { it.isReadOnly = true } } - init { - if (backingList != null) this.modCount = backingList.modCount - } - /** * Creates a new empty [ArrayList]. */ @@ -39,7 +32,7 @@ actual class ArrayList private constructor( * @throws IllegalArgumentException if [initialCapacity] is negative. */ actual constructor(initialCapacity: Int) : this( - arrayOfUninitializedElements(initialCapacity), 0, 0, false, null, null) + arrayOfUninitializedElements(initialCapacity), 0, false) /** * Creates a new [ArrayList] filled with the elements of the specified collection. @@ -52,7 +45,6 @@ actual class ArrayList private constructor( @PublishedApi internal fun build(): List { - if (backingList != null) throw IllegalStateException() // just in case somebody casts subList to ArrayList checkIsMutable() isReadOnly = true return if (length > 0) this else Empty @@ -60,45 +52,39 @@ actual class ArrayList private constructor( override actual val size: Int get() { - checkForComodification() return length } override actual fun isEmpty(): Boolean { - checkForComodification() return length == 0 } override actual fun get(index: Int): E { - checkForComodification() AbstractList.checkElementIndex(index, length) - return backingArray[offset + index] + return backingArray[index] } override actual operator fun set(index: Int, element: E): E { checkIsMutable() - checkForComodification() AbstractList.checkElementIndex(index, length) - val old = backingArray[offset + index] - backingArray[offset + index] = element + val old = backingArray[index] + backingArray[index] = element return old } override actual fun indexOf(element: E): Int { - checkForComodification() var i = 0 while (i < length) { - if (backingArray[offset + i] == element) return i + if (backingArray[i] == element) return i i++ } return -1 } override actual fun lastIndexOf(element: E): Int { - checkForComodification() var i = length - 1 while (i >= 0) { - if (backingArray[offset + i] == element) return i + if (backingArray[i] == element) return i i-- } return -1 @@ -108,58 +94,50 @@ actual class ArrayList private constructor( override actual fun listIterator(): MutableListIterator = listIterator(0) override actual fun listIterator(index: Int): MutableListIterator { - checkForComodification() AbstractList.checkPositionIndex(index, length) return Itr(this, index) } override actual fun add(element: E): Boolean { checkIsMutable() - checkForComodification() - addAtInternal(offset + length, element) + addAtInternal(length, element) return true } override actual fun add(index: Int, element: E) { checkIsMutable() - checkForComodification() AbstractList.checkPositionIndex(index, length) - addAtInternal(offset + index, element) + addAtInternal(index, element) } override actual fun addAll(elements: Collection): Boolean { checkIsMutable() - checkForComodification() val n = elements.size - addAllInternal(offset + length, elements, n) + addAllInternal(length, elements, n) return n > 0 } override actual fun addAll(index: Int, elements: Collection): Boolean { checkIsMutable() - checkForComodification() AbstractList.checkPositionIndex(index, length) val n = elements.size - addAllInternal(offset + index, elements, n) + addAllInternal(index, elements, n) return n > 0 } override actual fun clear() { checkIsMutable() - checkForComodification() - removeRangeInternal(offset, length) + removeRangeInternal(0, length) } override actual fun removeAt(index: Int): E { checkIsMutable() - checkForComodification() AbstractList.checkElementIndex(index, length) - return removeAtInternal(offset + index) + return removeAtInternal(index) } override actual fun remove(element: E): Boolean { checkIsMutable() - checkForComodification() val i = indexOf(element) if (i >= 0) removeAt(i) return i >= 0 @@ -167,67 +145,58 @@ actual class ArrayList private constructor( override actual fun removeAll(elements: Collection): Boolean { checkIsMutable() - checkForComodification() - return retainOrRemoveAllInternal(offset, length, elements, false) > 0 + return retainOrRemoveAllInternal(0, length, elements, false) > 0 } override actual fun retainAll(elements: Collection): Boolean { checkIsMutable() - checkForComodification() - return retainOrRemoveAllInternal(offset, length, elements, true) > 0 + return retainOrRemoveAllInternal(0, length, elements, true) > 0 } override actual fun subList(fromIndex: Int, toIndex: Int): MutableList { AbstractList.checkRangeIndexes(fromIndex, toIndex, length) - return ArrayList(backingArray, offset + fromIndex, toIndex - fromIndex, isReadOnly, this, root ?: this) + return ArraySubList(backingArray, fromIndex, toIndex - fromIndex, null, this) } actual fun trimToSize() { - if (backingList != null) throw IllegalStateException() // just in case somebody casts subList to ArrayList registerModification() if (length < backingArray.size) backingArray = backingArray.copyOfUninitializedElements(length) } final actual fun ensureCapacity(minCapacity: Int) { - if (backingList != null) throw IllegalStateException() // just in case somebody casts subList to ArrayList if (minCapacity <= backingArray.size) return registerModification() ensureCapacityInternal(minCapacity) } override fun equals(other: Any?): Boolean { - checkForComodification() return other === this || (other is List<*>) && contentEquals(other) } override fun hashCode(): Int { - checkForComodification() - return backingArray.subarrayContentHashCode(offset, length) + return backingArray.subarrayContentHashCode(0, length) } override fun toString(): String { - checkForComodification() - return backingArray.subarrayContentToString(offset, length, this) + return backingArray.subarrayContentToString(0, length, this) } @Suppress("UNCHECKED_CAST") override fun toArray(array: Array): Array { - checkForComodification() if (array.size < length) { - return backingArray.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array + return backingArray.copyOfRange(fromIndex = 0, toIndex = length) as Array } - (backingArray as Array).copyInto(array, 0, startIndex = offset, endIndex = offset + length) + (backingArray as Array).copyInto(array, 0, startIndex = 0, endIndex = length) return terminateCollectionToArray(length, array) } override fun toArray(): Array { - checkForComodification() @Suppress("UNCHECKED_CAST") - return backingArray.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array + return backingArray.copyOfRange(fromIndex = 0, toIndex = length) as Array } // ---------------------------- private ---------------------------- @@ -236,13 +205,8 @@ actual class ArrayList private constructor( modCount += 1 } - private fun checkForComodification() { - if (root != null && root.modCount != modCount) - throw ConcurrentModificationException() - } - private fun checkIsMutable() { - if (isReadOnly || root != null && root.isReadOnly) throw UnsupportedOperationException() + if (isReadOnly) throw UnsupportedOperationException() } private fun ensureExtraCapacity(n: Int) { @@ -258,89 +222,62 @@ actual class ArrayList private constructor( } private fun contentEquals(other: List<*>): Boolean { - return backingArray.subarrayContentEquals(offset, length, other) + return backingArray.subarrayContentEquals(0, length, other) } private fun insertAtInternal(i: Int, n: Int) { ensureExtraCapacity(n) - backingArray.copyInto(backingArray, startIndex = i, endIndex = offset + length, destinationOffset = i + n) + backingArray.copyInto(backingArray, startIndex = i, endIndex = length, destinationOffset = i + n) length += n } private fun addAtInternal(i: Int, element: E) { registerModification() - if (backingList != null) { - backingList.addAtInternal(i, element) - backingArray = backingList.backingArray - length++ - } else { - insertAtInternal(i, 1) - backingArray[i] = element - } + insertAtInternal(i, 1) + backingArray[i] = element } private fun addAllInternal(i: Int, elements: Collection, n: Int) { registerModification() - if (backingList != null) { - backingList.addAllInternal(i, elements, n) - backingArray = backingList.backingArray - length += n - } else { - insertAtInternal(i, n) - var j = 0 - val it = elements.iterator() - while (j < n) { - backingArray[i + j] = it.next() - j++ - } + insertAtInternal(i, n) + var j = 0 + val it = elements.iterator() + while (j < n) { + backingArray[i + j] = it.next() + j++ } } private fun removeAtInternal(i: Int): E { registerModification() - if (backingList != null) { - val old = backingList.removeAtInternal(i) - length-- - return old - } else { - val old = backingArray[i] - backingArray.copyInto(backingArray, startIndex = i + 1, endIndex = offset + length, destinationOffset = i) - backingArray.resetAt(offset + length - 1) - length-- - return old - } + val old = backingArray[i] + backingArray.copyInto(backingArray, startIndex = i + 1, endIndex = length, destinationOffset = i) + backingArray.resetAt(length - 1) + length-- + return old } private fun removeRangeInternal(rangeOffset: Int, rangeLength: Int) { if (rangeLength > 0) registerModification() - if (backingList != null) { - backingList.removeRangeInternal(rangeOffset, rangeLength) - } else { - backingArray.copyInto(backingArray, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset) - backingArray.resetRange(fromIndex = length - rangeLength, toIndex = length) - } + backingArray.copyInto(backingArray, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset) + backingArray.resetRange(fromIndex = length - rangeLength, toIndex = length) length -= rangeLength } /** Retains elements if [retain] == true and removes them it [retain] == false. */ private fun retainOrRemoveAllInternal(rangeOffset: Int, rangeLength: Int, elements: Collection, retain: Boolean): Int { - val removed = if (backingList != null) { - backingList.retainOrRemoveAllInternal(rangeOffset, rangeLength, elements, retain) - } else { - var i = 0 - var j = 0 - while (i < rangeLength) { - if (elements.contains(backingArray[rangeOffset + i]) == retain) { - backingArray[rangeOffset + j++] = backingArray[rangeOffset + i++] - } else { - i++ - } + var i = 0 + var j = 0 + while (i < rangeLength) { + if (elements.contains(backingArray[rangeOffset + i]) == retain) { + backingArray[rangeOffset + j++] = backingArray[rangeOffset + i++] + } else { + i++ } - val removed = rangeLength - j - backingArray.copyInto(backingArray, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset + j) - backingArray.resetRange(fromIndex = length - removed, toIndex = length) - removed } + val removed = rangeLength - j + backingArray.copyInto(backingArray, startIndex = rangeOffset + rangeLength, endIndex = length, destinationOffset = rangeOffset + j) + backingArray.resetRange(fromIndex = length - removed, toIndex = length) if (removed > 0) registerModification() length -= removed return removed @@ -369,14 +306,14 @@ actual class ArrayList private constructor( checkForComodification() if (index <= 0) throw NoSuchElementException() lastIndex = --index - return list.backingArray[list.offset + lastIndex] + return list.backingArray[lastIndex] } override fun next(): E { checkForComodification() if (index >= list.length) throw NoSuchElementException() lastIndex = index++ - return list.backingArray[list.offset + lastIndex] + return list.backingArray[lastIndex] } override fun set(element: E) { @@ -401,11 +338,339 @@ actual class ArrayList private constructor( expectedModCount = list.modCount } - private fun checkForComodification() { + // Must inline for native, suppress warning for WASM + @Suppress("NOTHING_TO_INLINE") + private inline fun checkForComodification() { if (list.modCount != expectedModCount) throw ConcurrentModificationException() } } + + private class ArraySubList( + private var backingArray: Array, + private val offset: Int, + private var length: Int, + private val backingList: ArraySubList?, + private val root: ArrayList + ) : MutableList, RandomAccess, AbstractMutableList() { + + init { + this.modCount = root.modCount + } + + override val size: Int + get() { + checkForComodification() + return length + } + + override fun isEmpty(): Boolean { + checkForComodification() + return length == 0 + } + + override fun get(index: Int): E { + checkForComodification() + AbstractList.checkElementIndex(index, length) + return backingArray[offset + index] + } + + override operator fun set(index: Int, element: E): E { + checkIsMutable() + checkForComodification() + AbstractList.checkElementIndex(index, length) + val old = backingArray[offset + index] + backingArray[offset + index] = element + return old + } + + override fun indexOf(element: E): Int { + checkForComodification() + var i = 0 + while (i < length) { + if (backingArray[offset + i] == element) return i + i++ + } + return -1 + } + + override fun lastIndexOf(element: E): Int { + checkForComodification() + var i = length - 1 + while (i >= 0) { + if (backingArray[offset + i] == element) return i + i-- + } + return -1 + } + + override fun iterator(): MutableIterator = listIterator(0) + override fun listIterator(): MutableListIterator = listIterator(0) + + override fun listIterator(index: Int): MutableListIterator { + checkForComodification() + AbstractList.checkPositionIndex(index, length) + return Itr(this, index) + } + + override fun add(element: E): Boolean { + checkIsMutable() + checkForComodification() + addAtInternal(offset + length, element) + return true + } + + override fun add(index: Int, element: E) { + checkIsMutable() + checkForComodification() + AbstractList.checkPositionIndex(index, length) + addAtInternal(offset + index, element) + } + + override fun addAll(elements: Collection): Boolean { + checkIsMutable() + checkForComodification() + val n = elements.size + addAllInternal(offset + length, elements, n) + return n > 0 + } + + override fun addAll(index: Int, elements: Collection): Boolean { + checkIsMutable() + checkForComodification() + AbstractList.checkPositionIndex(index, length) + val n = elements.size + addAllInternal(offset + index, elements, n) + return n > 0 + } + + override fun clear() { + checkIsMutable() + checkForComodification() + removeRangeInternal(offset, length) + } + + override fun removeAt(index: Int): E { + checkIsMutable() + checkForComodification() + AbstractList.checkElementIndex(index, length) + return removeAtInternal(offset + index) + } + + override fun remove(element: E): Boolean { + checkIsMutable() + checkForComodification() + val i = indexOf(element) + if (i >= 0) removeAt(i) + return i >= 0 + } + + override fun removeAll(elements: Collection): Boolean { + checkIsMutable() + checkForComodification() + return retainOrRemoveAllInternal(offset, length, elements, false) > 0 + } + + override fun retainAll(elements: Collection): Boolean { + checkIsMutable() + checkForComodification() + return retainOrRemoveAllInternal(offset, length, elements, true) > 0 + } + + override fun subList(fromIndex: Int, toIndex: Int): MutableList { + AbstractList.checkRangeIndexes(fromIndex, toIndex, length) + return ArraySubList(backingArray, offset + fromIndex, toIndex - fromIndex, this, root) + } + + override fun equals(other: Any?): Boolean { + checkForComodification() + return other === this || + (other is List<*>) && contentEquals(other) + } + + override fun hashCode(): Int { + checkForComodification() + return backingArray.subarrayContentHashCode(offset, length) + } + + override fun toString(): String { + checkForComodification() + return backingArray.subarrayContentToString(offset, length, this) + } + + @Suppress("UNCHECKED_CAST") + override fun toArray(array: Array): Array { + checkForComodification() + if (array.size < length) { + return backingArray.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array + } + + (backingArray as Array).copyInto(array, 0, startIndex = offset, endIndex = offset + length) + + return terminateCollectionToArray(length, array) + } + + override fun toArray(): Array { + checkForComodification() + @Suppress("UNCHECKED_CAST") + return backingArray.copyOfRange(fromIndex = offset, toIndex = offset + length) as Array + } + + // ---------------------------- private ---------------------------- + + private fun registerModification() { + modCount += 1 + } + + private fun checkForComodification() { + if (root.modCount != modCount) + throw ConcurrentModificationException() + } + + private fun checkIsMutable() { + if (root.isReadOnly) throw UnsupportedOperationException() + } + + private fun ensureExtraCapacity(n: Int) { + ensureCapacityInternal(length + n) + } + + private fun ensureCapacityInternal(minCapacity: Int) { + if (minCapacity < 0) throw OutOfMemoryError() // overflow + if (minCapacity > backingArray.size) { + val newSize = AbstractList.newCapacity(backingArray.size, minCapacity) + backingArray = backingArray.copyOfUninitializedElements(newSize) + } + } + + private fun contentEquals(other: List<*>): Boolean { + return backingArray.subarrayContentEquals(offset, length, other) + } + + private fun addAtInternal(i: Int, element: E) { + registerModification() + val backingList = backingList + if (backingList != null) { + backingList.addAtInternal(i, element) + } else { + root.addAtInternal(i, element) + } + backingArray = root.backingArray + length++ + } + + private fun addAllInternal(i: Int, elements: Collection, n: Int) { + registerModification() + val backingList = backingList + if (backingList != null) { + backingList.addAllInternal(i, elements, n) + } else { + root.addAllInternal(i, elements, n) + } + backingArray = root.backingArray + length += n + } + + private fun removeAtInternal(i: Int): E { + registerModification() + val backingList = backingList + val old = if (backingList != null) { + backingList.removeAtInternal(i) + } else { + root.removeAtInternal(i) + } + length-- + return old + } + + private fun removeRangeInternal(rangeOffset: Int, rangeLength: Int) { + if (rangeLength > 0) registerModification() + val backingList = backingList + if (backingList != null) { + backingList.removeRangeInternal(rangeOffset, rangeLength) + } else { + root.removeRangeInternal(rangeOffset, rangeLength) + } + length -= rangeLength + } + + /** Retains elements if [retain] == true and removes them it [retain] == false. */ + private fun retainOrRemoveAllInternal(rangeOffset: Int, rangeLength: Int, elements: Collection, retain: Boolean): Int { + val backingList = backingList + val removed = + if (backingList != null) { + backingList.retainOrRemoveAllInternal(rangeOffset, rangeLength, elements, retain) + } else { + root.retainOrRemoveAllInternal(rangeOffset, rangeLength, elements, retain) + } + if (removed > 0) registerModification() + length -= removed + return removed + } + + private class Itr : MutableListIterator { + private val list: ArraySubList + private var index: Int + private var lastIndex: Int + private var expectedModCount: Int + + constructor(list: ArraySubList, index: Int) { + this.list = list + this.index = index + this.lastIndex = -1 + this.expectedModCount = list.modCount + } + + override fun hasPrevious(): Boolean = index > 0 + override fun hasNext(): Boolean = index < list.length + + override fun previousIndex(): Int = index - 1 + override fun nextIndex(): Int = index + + override fun previous(): E { + checkForComodification() + if (index <= 0) throw NoSuchElementException() + lastIndex = --index + return list.backingArray[list.offset + lastIndex] + } + + override fun next(): E { + checkForComodification() + if (index >= list.length) throw NoSuchElementException() + lastIndex = index++ + return list.backingArray[list.offset + lastIndex] + } + + override fun set(element: E) { + checkForComodification() + check(lastIndex != -1) { "Call next() or previous() before replacing element from the iterator." } + list.set(lastIndex, element) + } + + override fun add(element: E) { + checkForComodification() + list.add(index++, element) + lastIndex = -1 + expectedModCount = list.modCount + } + + override fun remove() { + checkForComodification() + check(lastIndex != -1) { "Call next() or previous() before removing element from the iterator." } + list.removeAt(lastIndex) + index = lastIndex + lastIndex = -1 + expectedModCount = list.modCount + } + + // Must inline for native, suppress warning for WASM + @Suppress("NOTHING_TO_INLINE") + private inline fun checkForComodification() { + if (list.modCount != expectedModCount) + throw ConcurrentModificationException() + } + } + } } private fun Array.subarrayContentHashCode(offset: Int, length: Int): Int {