diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MaxStackFrameSizeAndLocalsCalculator.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MaxStackFrameSizeAndLocalsCalculator.java index 2868d597c01..dd84f3336ba 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MaxStackFrameSizeAndLocalsCalculator.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MaxStackFrameSizeAndLocalsCalculator.java @@ -46,8 +46,9 @@ package org.jetbrains.kotlin.codegen.inline; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.utils.SmartSet; +import org.jetbrains.kotlin.utils.SmartIdentityTable; import org.jetbrains.org.objectweb.asm.*; import java.util.*; @@ -106,7 +107,7 @@ public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator { private int maxStack; private final Collection exceptionHandlers = new LinkedList<>(); - private final Map labelWrappersMap = new HashMap<>(); + private final SmartIdentityTable labelWrappersTable = new SmartIdentityTable<>(); public MaxStackFrameSizeAndLocalsCalculator(int api, int access, String descriptor, MethodVisitor mv) { super(api, access, descriptor, mv); @@ -336,7 +337,7 @@ public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator { */ int max = 0; Stack stack = new Stack<>(); - Set pushed = new HashSet<>(); + Set pushed = SmartSet.create(); stack.push(firstLabel); pushed.add(firstLabel); @@ -434,7 +435,7 @@ public class MaxStackFrameSizeAndLocalsCalculator extends MaxLocalsCalculator { // ------------------------------------------------------------------------ private LabelWrapper getLabelWrapper(Label label) { - return ContainerUtil.getOrCreate(labelWrappersMap, label, () -> new LabelWrapper(label)); + return labelWrappersTable.getOrCreate(label, () -> new LabelWrapper(label)); } private void increaseStackSize(int variation) { diff --git a/compiler/tests/org/jetbrains/kotlin/util/SmartIdentityTableTest.kt b/compiler/tests/org/jetbrains/kotlin/util/SmartIdentityTableTest.kt new file mode 100644 index 00000000000..5bfafa09634 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/util/SmartIdentityTableTest.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.util + +import org.jetbrains.kotlin.utils.SmartIdentityTable +import org.junit.Assert +import org.junit.Test + +class SmartIdentityTableTest { + class Key(val number: Int) { + override fun equals(other: Any?): Boolean { + Assert.fail("equals Should not be called") + return false + } + + override fun hashCode(): Int { + Assert.fail("equals Should not be called") + return 0 + } + } + + class Value(val number: Int) { + override fun equals(other: Any?): Boolean { + Assert.fail("equals Should not be called") + return false + } + + override fun hashCode(): Int { + Assert.fail("equals Should not be called") + return 0 + } + } + + @Test + fun basicTest() { + val key1 = Key(1) + val key2 = Key(2) + val key3 = Key(3) + val val1 = Value(1) + val val2 = Value(2) + val val3 = Value(3) + + val table = SmartIdentityTable() + + // insert two keys and ensure that those are present + table[key1] = val1 + table[key2] = val2 + + Assert.assertEquals(2, table.size) + Assert.assertTrue(table[key1] === val1) + Assert.assertTrue(table[key2] === val2) + + // replace existing key's value + table[key1] = val3 + + // expect size to stay the same + Assert.assertEquals(2, table.size) + + // values should be updated for key1 and same for other key + Assert.assertTrue(table[key1] === val3) + Assert.assertTrue(table[key2] === val2) + + // add a new key with existing value + table[key3] = val2 + + // new key should be added and existing keys should maintain their values + Assert.assertEquals(3, table.size) + Assert.assertTrue(table[key1] === val3) + Assert.assertTrue(table[key2] === val2) + Assert.assertTrue(table[key3] === val2) + + // create a key that has the same contents but a different reference identity, it should not be found in the table. + val secondKey1 = Key(1) + Assert.assertTrue(table[secondKey1] === null) + } + + @Test + fun growToMapTest() { + val table = SmartIdentityTable() + + // insert enough data to trigger conversion of the Table to use a Map + val keys = mutableListOf() + for (i in 0 until 15) { + val key = Key(i) + val value = Value(i) + keys.add(key) + table[key] = value + } + + Assert.assertEquals(15, table.size) + for (key in keys) { + val value = table[key] + Assert.assertNotNull(value) + Assert.assertEquals(key.number, table[key]!!.number) + } + } + + @Test + fun getOrCreateTest() { + val table = SmartIdentityTable() + val f = { Value(5) } + table.getOrCreate(Key(5), f) + } +} diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/SmartIdentityTable.kt b/compiler/util/src/org/jetbrains/kotlin/utils/SmartIdentityTable.kt new file mode 100644 index 00000000000..5d5de9b594f --- /dev/null +++ b/compiler/util/src/org/jetbrains/kotlin/utils/SmartIdentityTable.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.utils + +import java.util.IdentityHashMap + +/** + * [SmartIdentityTable] is a Map like structure that uses reference identity for keys. + * It uses 2 arrays to store keys & values until the number of entries stored is larger than [ARRAY_UNTIL_SIZE]. + * At that point it switches to using an IdentityHashMap. + * + * This structure can be used instead of [HashMap] when reference identity can be used and + * the number of entries inserted is small (<= [ARRAY_UNTIL_SIZE]) on average, drastically reducing the overhead + * of calls to [Object.hashCode]. + * + * The implementation of [SmartIdentityTable] is not synchronized. + */ +class SmartIdentityTable { + + private var keysArray: MutableList? = ArrayList(ARRAY_UNTIL_SIZE) + private var valuesArray: MutableList? = ArrayList(ARRAY_UNTIL_SIZE) + private var largeMap: IdentityHashMap? = null + + val size: Int + get() = keysArray?.size ?: largeMap!!.size + + operator fun get(key: K): V? { + return keysArray?.let { + for ((index, k) in it.withIndex()) { + if (k === key) { + return valuesArray!![index] + } + } + return null + } ?: largeMap!![key] + } + + operator fun set(key: K, value: V): V? { + val ka = keysArray + if (ka != null) { + val va = valuesArray!! + // scan for existing keys in array + for (i in 0 until ka.size) { + if (ka[i] === key) { + val tmp = va[i] + va[i] = value + return tmp + } + } + // if a new key, and array has room + if (ka.size < ARRAY_UNTIL_SIZE) { + ka.add(key) + va.add(value) + return null + } + convertToHashMap() + } + // all other cases, fallback to IdentityHashMap implementation + return largeMap!!.put(key, value) + } + + private fun convertToHashMap() { + val map = IdentityHashMap() + val ka = keysArray!! + val va = valuesArray!! + for (i in 0 until ka.size) { + map[ka[i]] = va[i] + } + largeMap = map + keysArray = null + valuesArray = null + } + + fun getOrCreate(key: K, factory: () -> V): V { + return this[key] ?: factory().also { + this[key] = it + } + } + + companion object { + private const val ARRAY_UNTIL_SIZE = 10 + } +}