[kotlin.reflect] Introduce ClassValue-based cache for KClassImpl

* Replace pcollections with ClassValue/ConcurrentHashMap-based caches
* Do not store weak references, instead cache strong references and count on ClassValue to unload the corresponding classloader if necessary
* ConcurrentHashMap does not rely on WeakReference as it's only selected on Android where classloader leaks don't exist
* Update reflect/scripting JDK requirement to Java 8 in order to proceed

#KT-53454
#KT-50705


Merge-request: KT-MR-6788
Merged-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
This commit is contained in:
Vsevolod Tolstopyatov
2022-08-05 15:35:34 +00:00
committed by Space
parent d5164fbc86
commit 14b13a2f17
37 changed files with 110 additions and 1129 deletions
@@ -19022,52 +19022,6 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("empty.kt")
public void testEmpty() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/empty.kt");
}
@Test
@TestMetadata("manyNumbers.kt")
public void testManyNumbers() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/manyNumbers.kt");
}
@Test
@TestMetadata("rewriteWithDifferent.kt")
public void testRewriteWithDifferent() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithDifferent.kt");
}
@Test
@TestMetadata("rewriteWithEqual.kt")
public void testRewriteWithEqual() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithEqual.kt");
}
@Test
@TestMetadata("simplePlusGet.kt")
public void testSimplePlusGet() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusGet.kt");
}
@Test
@TestMetadata("simplePlusMinus.kt")
public void testSimplePlusMinus() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusMinus.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
-26
View File
@@ -1,26 +0,0 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun box(): String {
val map = HashPMap.empty<String, Any>()!!
assertEquals(0, map.size())
assertFalse(map.containsKey(""))
assertFalse(map.containsKey("abacaba"))
assertEquals(null, map[""])
assertEquals(null, map["lol"])
// Check that doesn't create a new map
assertEquals(map, map.minus(""))
// Check that all empty()s are equal
val other = HashPMap.empty<String, Any>()!!
assertEquals(map, other)
return "OK"
}
-54
View File
@@ -1,54 +0,0 @@
// TARGET_BACKEND: JVM
// Out of memory on Android 4.4
// IGNORE_BACKEND: ANDROID
// WITH_REFLECT
import java.util.*
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun digitSum(number: Int): Int {
var x = number
var ans = 0
while (x != 0) {
ans += x % 10
x /= 10
}
return ans
}
val N = 1000000
fun box(): String {
var map = HashPMap.empty<Int, Any>()!!
for (x in 1..N) {
map = map.plus(x, digitSum(x))!!
}
assertEquals(N, map.size())
// Check in reverse order just in case
for (x in N downTo 1) {
assertTrue(map.containsKey(x), "Not found: $x")
assertEquals(digitSum(x), map[x], "Incorrect value for $x")
}
// Delete in random order
val list = (1..N).toCollection(ArrayList<Int>())
Collections.shuffle(list, Random(42))
for (x in list) {
map = map.minus(x)!!
}
assertEquals(0, map.size())
for (x in 1..N) {
assertFalse(map.containsKey(x), "Incorrectly found: $x")
assertEquals(null, map[x], "Incorrectly found value for $x")
}
return "OK"
}
@@ -1,32 +0,0 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun box(): String {
var map = HashPMap.empty<String, Any>()!!
map = map.plus("lol", 42)!!
map = map.plus("lol", 239)!!
assertEquals(1, map.size())
assertTrue(map.containsKey("lol"))
assertFalse(map.containsKey(""))
assertEquals(239, map["lol"])
assertEquals(null, map[""])
map = map.plus("", 0)!!
map = map.plus("", 2.71828)!!
map = map.plus("lol", 42)!!
map = map.plus("", 3.14)!!
assertEquals(2, map.size())
assertTrue(map.containsKey("lol"))
assertTrue(map.containsKey(""))
assertEquals(42, map["lol"])
assertEquals(3.14, map[""])
return "OK"
}
@@ -1,32 +0,0 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun box(): String {
var map = HashPMap.empty<String, Any>()!!
map = map.plus("lol", 42)!!
map = map.plus("lol", 42)!!
assertEquals(1, map.size())
assertTrue(map.containsKey("lol"))
assertFalse(map.containsKey(""))
assertEquals(42, map["lol"])
assertEquals(null, map[""])
map = map.plus("", 0)!!
map = map.plus("", 0)!!
map = map.plus("lol", 42)!!
map = map.plus("", 0)!!
assertEquals(2, map.size())
assertTrue(map.containsKey("lol"))
assertTrue(map.containsKey(""))
assertEquals(42, map["lol"])
assertEquals(0, map[""])
return "OK"
}
-28
View File
@@ -1,28 +0,0 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun box(): String {
var map = HashPMap.empty<String, Any>()!!
map = map.plus("lol", 42)!!
assertEquals(1, map.size())
assertTrue(map.containsKey("lol"))
assertFalse(map.containsKey(""))
assertEquals(42, map["lol"])
assertEquals(null, map[""])
map = map.plus("", 0)!!
assertEquals(2, map.size())
assertTrue(map.containsKey("lol"))
assertTrue(map.containsKey(""))
assertEquals(42, map["lol"])
assertEquals(0, map[""])
return "OK"
}
@@ -1,27 +0,0 @@
// TARGET_BACKEND: JVM
// WITH_REFLECT
import kotlin.reflect.jvm.internal.pcollections.HashPMap
import kotlin.test.*
fun box(): String {
var map = HashPMap.empty<String, Any>()!!
map = map.plus("lol", 42)!!
map = map.minus("lol")!!
assertEquals(0, map.size())
assertFalse(map.containsKey("lol"))
assertEquals(null, map["lol"])
map = map.plus("abc", "a")!!
map = map.minus("abc")!!
map = map.plus("abc", "d")!!
assertEquals(1, map.size())
assertTrue(map.containsKey("abc"))
assertEquals("d", map["abc"])
return "OK"
}
@@ -18452,52 +18452,6 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@Test
@TestMetadata("empty.kt")
public void testEmpty() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/empty.kt");
}
@Test
@TestMetadata("manyNumbers.kt")
public void testManyNumbers() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/manyNumbers.kt");
}
@Test
@TestMetadata("rewriteWithDifferent.kt")
public void testRewriteWithDifferent() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithDifferent.kt");
}
@Test
@TestMetadata("rewriteWithEqual.kt")
public void testRewriteWithEqual() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithEqual.kt");
}
@Test
@TestMetadata("simplePlusGet.kt")
public void testSimplePlusGet() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusGet.kt");
}
@Test
@TestMetadata("simplePlusMinus.kt")
public void testSimplePlusMinus() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusMinus.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@@ -19022,52 +19022,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@Test
@TestMetadata("empty.kt")
public void testEmpty() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/empty.kt");
}
@Test
@TestMetadata("manyNumbers.kt")
public void testManyNumbers() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/manyNumbers.kt");
}
@Test
@TestMetadata("rewriteWithDifferent.kt")
public void testRewriteWithDifferent() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithDifferent.kt");
}
@Test
@TestMetadata("rewriteWithEqual.kt")
public void testRewriteWithEqual() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithEqual.kt");
}
@Test
@TestMetadata("simplePlusGet.kt")
public void testSimplePlusGet() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusGet.kt");
}
@Test
@TestMetadata("simplePlusMinus.kt")
public void testSimplePlusMinus() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusMinus.kt");
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@@ -15341,49 +15341,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
}
}
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class HashPMap extends AbstractLightAnalysisModeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("empty.kt")
public void testEmpty() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/empty.kt");
}
@TestMetadata("manyNumbers.kt")
public void testManyNumbers() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/manyNumbers.kt");
}
@TestMetadata("rewriteWithDifferent.kt")
public void testRewriteWithDifferent() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithDifferent.kt");
}
@TestMetadata("rewriteWithEqual.kt")
public void testRewriteWithEqual() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/rewriteWithEqual.kt");
}
@TestMetadata("simplePlusGet.kt")
public void testSimplePlusGet() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusGet.kt");
}
@TestMetadata("simplePlusMinus.kt")
public void testSimplePlusMinus() throws Exception {
runTest("compiler/testData/codegen/box/hashPMap/simplePlusMinus.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -31,7 +31,6 @@ class CodeConformanceTest : TestCase() {
"compiler/testData/psi/kdoc",
"compiler/tests/org/jetbrains/kotlin/code/CodeConformanceTest.kt",
"compiler/util/src/org/jetbrains/kotlin/config/MavenComparableVersion.java",
"core/reflection.jvm/src/kotlin/reflect/jvm/internal/pcollections",
"dependencies",
"dependencies/protobuf/protobuf-relocated/build",
"dist",
-7
View File
@@ -25,13 +25,6 @@
** toString();
}
# For tests on HashPMap, see compiler/testData/codegen/box/hashPMap
-keepclassmembers class kotlin.reflect.jvm.internal.pcollections.HashPMap {
public int size();
public boolean containsKey(java.lang.Object);
public kotlin.reflect.jvm.internal.pcollections.HashPMap minus(java.lang.Object);
}
# This is needed because otherwise ProGuard strips generic signature of this class (even though we pass `-keepattributes Signature` above)
# See KT-23962 and https://sourceforge.net/p/proguard/bugs/482/
-keep class kotlin.reflect.jvm.internal.impl.protobuf.GeneratedMessageLite$ExtendableMessageOrBuilder
@@ -18,4 +18,7 @@
-dontnote kotlin.internal.PlatformImplementationsKt
# Don't note on internal APIs, as there is some class relocating that shrinkers may unnecessarily find suspicious.
-dontwarn kotlin.reflect.jvm.internal.**
-dontwarn kotlin.reflect.jvm.internal.**
# Statically guarded by try-catch block and not used on Android, see CacheByClass
-dontwarn java.lang.ClassValue
@@ -13,4 +13,12 @@
-dontnote kotlin.internal.PlatformImplementationsKt
# Don't note on internal APIs, as there is some class relocating that shrinkers may unnecessarily find suspicious.
-dontwarn kotlin.reflect.jvm.internal.**
-dontwarn kotlin.reflect.jvm.internal.**
# Statically guarded by try-catch block and not used on Android, see CacheByClass
-dontwarn java.lang.ClassValue
# Do not even execute try-catch block for ClassValue
-assumenosideeffects class kotlin.reflect.jvm.internal.CacheByClassKt {
boolean useClassValue return false;
}
@@ -20,4 +20,7 @@
-dontnote kotlin.internal.PlatformImplementationsKt
# Don't note on internal APIs, as there is some class relocating that shrinkers may unnecessarily find suspicious.
-dontwarn kotlin.reflect.jvm.internal.**
-dontwarn kotlin.reflect.jvm.internal.**
# Statically guarded by try-catch block and not used on Android, see CacheByClass
-dontwarn java.lang.ClassValue
@@ -20,4 +20,7 @@
-dontnote kotlin.internal.PlatformImplementationsKt
# Don't note on internal APIs, as there is some class relocating that shrinkers may unnecessarily find suspicious.
-dontwarn kotlin.reflect.jvm.internal.**
-dontwarn kotlin.reflect.jvm.internal.**
# Statically guarded by try-catch block and not used on Android, see CacheByClass
-dontwarn java.lang.ClassValue
@@ -18,7 +18,6 @@
package kotlin.reflect.full
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.types.*
import kotlin.reflect.KClassifier
import kotlin.reflect.KType
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2022 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 kotlin.reflect.jvm.internal
import java.util.concurrent.ConcurrentHashMap
/*
* By default, we use ClassValue-based caches in reflection to avoid classloader leaks,
* but ClassValue is not available on Android, thus we attempt to check it dynamically
* and fallback to ConcurrentHashMap-based cache.
*
* NB: if you are changing the name of the outer file (CacheByClass.kt), please also change the corresponding
* proguard rules
*/
private val useClassValue = runCatching {
Class.forName("java.lang.ClassValue")
}.map { true }.getOrDefault(false)
internal abstract class CacheByClass<V> {
abstract fun get(key: Class<*>): V
abstract fun clear()
}
/**
* Creates a **strongly referenced** cache of values associated with [Class].
* Values are computed using provided [compute] function.
*
* `null` values are not supported, though there aren't any technical limitations.
*/
internal fun <V : Any> createCache(compute: (Class<*>) -> V): CacheByClass<V> {
return if (useClassValue) ClassValueCache(compute) else ConcurrentHashMapCache(compute)
}
private class ClassValueCache<V>(private val compute: (Class<*>) -> V) : CacheByClass<V>() {
@Volatile
private var classValue = initClassValue()
private fun initClassValue() = object : ClassValue<V>() {
override fun computeValue(type: Class<*>): V {
return compute(type)
}
}
override fun get(key: Class<*>): V = classValue[key]
override fun clear() {
/*
* ClassValue does not have a proper `clear()` method but is properly weak-referenced,
* thus abandoning ClassValue instance will eventually clear all associated values.
*/
classValue = initClassValue()
}
}
/**
* We no longer support Java 6, so the only place we use this cache is Android, where there
* are no classloader leaks issue, thus we can safely use strong references and do not bother
* with WeakReference wrapping.
*/
private class ConcurrentHashMapCache<V>(private val compute: (Class<*>) -> V) : CacheByClass<V>() {
private val cache = ConcurrentHashMap<Class<*>, V>()
override fun get(key: Class<*>): V = cache.getOrPut(key) { compute(key) }
override fun clear() {
cache.clear()
}
}
@@ -1,22 +1,11 @@
/*
* Copyright 2010-2015 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.
* Copyright 2010-2022 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 kotlin.reflect.jvm.internal;
import kotlin.SinceKotlin;
import kotlin.jvm.internal.*;
import kotlin.reflect.*;
import kotlin.reflect.full.KClassifiers;
@@ -1,69 +1,17 @@
/*
* Copyright 2010-2015 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.
* Copyright 2010-2022 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 kotlin.reflect.jvm.internal
import java.lang.ref.WeakReference
import kotlin.reflect.jvm.internal.pcollections.HashPMap
// TODO: collect nulls periodically
// Key of the map is Class.getName(), each value is either a WeakReference<KClassImpl<*>> or an Array<WeakReference<KClassImpl<*>>>.
// Arrays are needed because the same class can be loaded by different class loaders, which results in different Class instances.
// This variable is not volatile intentionally: we don't care if there's a data race on it and some KClass instances will be lost.
// We do care however about general performance on read access to it, thus no synchronization is done here whatsoever
private var K_CLASS_CACHE = HashPMap.empty<String, Any>()
private val K_CLASS_CACHE = createCache { KClassImpl(it) }
// This function is invoked on each reflection access to Java classes, properties, etc. Performance is critical here.
internal fun <T : Any> getOrCreateKotlinClass(jClass: Class<T>): KClassImpl<T> {
val name = jClass.name
val cached = K_CLASS_CACHE[name]
if (cached is WeakReference<*>) {
@Suppress("UNCHECKED_CAST")
val kClass = cached.get() as KClassImpl<T>?
if (kClass?.jClass == jClass) {
return kClass
}
} else if (cached != null) {
// If the cached value is not a weak reference, it's an array of weak references
@Suppress("UNCHECKED_CAST")
(cached as Array<WeakReference<KClassImpl<T>>>)
for (ref in cached) {
val kClass = ref.get()
if (kClass?.jClass == jClass) {
return kClass
}
}
// This is the most unlikely case: we found a cached array of references of length at least 2 (can't be 1 because
// the single element would be cached instead), and none of those classes is the one we're looking for
val size = cached.size
val newArray = arrayOfNulls<WeakReference<KClassImpl<*>>>(size + 1)
// Don't use Arrays.copyOf because it works reflectively
System.arraycopy(cached, 0, newArray, 0, size)
val newKClass = KClassImpl(jClass)
newArray[size] = WeakReference(newKClass)
K_CLASS_CACHE = K_CLASS_CACHE.plus(name, newArray)
return newKClass
}
val newKClass = KClassImpl(jClass)
K_CLASS_CACHE = K_CLASS_CACHE.plus(name, WeakReference(newKClass))
return newKClass
}
@Suppress("UNCHECKED_CAST")
internal fun <T : Any> getOrCreateKotlinClass(jClass: Class<T>): KClassImpl<T> = K_CLASS_CACHE.get(jClass) as KClassImpl<T>
internal fun clearKClassCache() {
K_CLASS_CACHE = HashPMap.empty()
K_CLASS_CACHE.clear()
}
@@ -1,124 +0,0 @@
/*
* Copyright 2010-2015 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 kotlin.reflect.jvm.internal.pcollections;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* A simple persistent stack of non-null values.
* <p/>
* This implementation is thread-safe, although its iterators may not be.
*/
final class ConsPStack<E> implements Iterable<E> {
private static final ConsPStack<Object> EMPTY = new ConsPStack<Object>();
@SuppressWarnings("unchecked")
public static <E> ConsPStack<E> empty() {
return (ConsPStack<E>) EMPTY;
}
final E first;
final ConsPStack<E> rest;
private final int size;
private ConsPStack() { // EMPTY constructor
size = 0;
first = null;
rest = null;
}
private ConsPStack(E first, ConsPStack<E> rest) {
this.first = first;
this.rest = rest;
this.size = 1 + rest.size;
}
public E get(int index) {
if (index < 0 || index > size) throw new IndexOutOfBoundsException();
try {
return iterator(index).next();
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("Index: " + index);
}
}
@Override
public Iterator<E> iterator() {
return iterator(0);
}
public int size() {
return size;
}
private Iterator<E> iterator(int index) {
return new Itr<E>(subList(index));
}
private static class Itr<E> implements Iterator<E> {
private ConsPStack<E> next;
public Itr(ConsPStack<E> first) {
this.next = first;
}
@Override
public boolean hasNext() {
return next.size > 0;
}
@Override
public E next() {
E e = next.first;
next = next.rest;
return e;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
public ConsPStack<E> plus(E e) {
return new ConsPStack<E>(e, this);
}
private ConsPStack<E> minus(Object e) {
if (size == 0) return this;
if (first.equals(e)) // found it
return rest; // don't recurse (only remove one)
// otherwise keep looking:
ConsPStack<E> newRest = rest.minus(e);
if (newRest == rest) return this;
return new ConsPStack<E>(first, newRest);
}
public ConsPStack<E> minus(int i) {
return minus(get(i));
}
private ConsPStack<E> subList(int start) {
if (start < 0 || start > size)
throw new IndexOutOfBoundsException();
if (start == 0)
return this;
return rest.subList(start - 1);
}
}
@@ -1,101 +0,0 @@
/*
* Copyright 2010-2015 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 kotlin.reflect.jvm.internal.pcollections;
import org.jetbrains.annotations.NotNull;
/**
* A persistent map from non-null keys to non-null values.
* @suppress
*/
public final class HashPMap<K, V> {
private static final HashPMap<Object, Object> EMPTY = new HashPMap<Object, Object>(IntTreePMap.<ConsPStack<MapEntry<Object, Object>>>empty(), 0);
@SuppressWarnings("unchecked")
@NotNull
public static <K, V> HashPMap<K, V> empty() {
return (HashPMap<K, V>) EMPTY;
}
private final IntTreePMap<ConsPStack<MapEntry<K, V>>> intMap;
private final int size;
private HashPMap(IntTreePMap<ConsPStack<MapEntry<K, V>>> intMap, int size) {
this.intMap = intMap;
this.size = size;
}
public int size() {
return size;
}
public boolean containsKey(Object key) {
return keyIndexIn(getEntries(key.hashCode()), key) != -1;
}
public V get(Object key) {
ConsPStack<MapEntry<K, V>> entries = getEntries(key.hashCode());
while (entries != null && entries.size() > 0) {
MapEntry<K, V> entry = entries.first;
if (entry.key.equals(key))
return entry.value;
entries = entries.rest;
}
return null;
}
@NotNull
public HashPMap<K, V> plus(K key, V value) {
ConsPStack<MapEntry<K, V>> entries = getEntries(key.hashCode());
int size0 = entries.size();
int i = keyIndexIn(entries, key);
if (i != -1) entries = entries.minus(i);
entries = entries.plus(new MapEntry<K, V>(key, value));
return new HashPMap<K, V>(intMap.plus(key.hashCode(), entries), size - size0 + entries.size());
}
@NotNull
public HashPMap<K, V> minus(Object key) {
ConsPStack<MapEntry<K, V>> entries = getEntries(key.hashCode());
int i = keyIndexIn(entries, key);
if (i == -1) // key not in this
return this;
entries = entries.minus(i);
if (entries.size() == 0) // get rid of the entire hash entry
return new HashPMap<K, V>(intMap.minus(key.hashCode()), size - 1);
// otherwise replace hash entry with new smaller one:
return new HashPMap<K, V>(intMap.plus(key.hashCode(), entries), size - 1);
}
private ConsPStack<MapEntry<K, V>> getEntries(int hash) {
ConsPStack<MapEntry<K, V>> entries = intMap.get(hash);
if (entries == null) return ConsPStack.empty();
return entries;
}
private static <K, V> int keyIndexIn(ConsPStack<MapEntry<K, V>> entries, Object key) {
int i = 0;
while (entries != null && entries.size() > 0) {
MapEntry<K, V> entry = entries.first;
if (entry.key.equals(key))
return i;
entries = entries.rest;
i++;
}
return -1;
}
}
@@ -1,264 +0,0 @@
/*
* Copyright 2010-2015 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 kotlin.reflect.jvm.internal.pcollections;
/**
* A non-public utility class for persistent balanced tree maps with integer keys.
* <p/>
* To allow for efficiently increasing all keys above a certain value or decreasing
* all keys below a certain value, the keys values are stored relative to their parent.
* This makes this map a good backing for fast insertion and removal of indices in a
* vector.
* <p/>
* This implementation is thread-safe except for its iterators.
* <p/>
* Other than that, this tree is based on the Glasgow Haskell Compiler's Data.Map implementation,
* which in turn is based on "size balanced binary trees" as described by:
* <p/>
* Stephen Adams, "Efficient sets: a balancing act",
* Journal of Functional Programming 3(4):553-562, October 1993,
* http://www.swiss.ai.mit.edu/~adams/BB/.
* <p/>
* J. Nievergelt and E.M. Reingold, "Binary search trees of bounded balance",
* SIAM journal of computing 2(1), March 1973.
*
* @author harold
*/
final class IntTree<V> {
// marker value:
static final IntTree<Object> EMPTYNODE = new IntTree<Object>();
// we use longs so relative keys can express all ints
// (e.g. if this has key -10 and right has 'absolute' key MAXINT,
// then its relative key is MAXINT+10 which overflows)
// there might be some way to deal with this based on left-verse-right logic,
// but that sounds like a mess.
private final long key;
private final V value; // null value means this is empty node
private final IntTree<V> left, right;
private final int size;
private IntTree() {
size = 0;
key = 0;
value = null;
left = null;
right = null;
}
private IntTree(long key, V value, IntTree<V> left, IntTree<V> right) {
this.key = key;
this.value = value;
this.left = left;
this.right = right;
size = 1 + left.size + right.size;
}
private IntTree<V> withKey(long newKey) {
if (size == 0 || newKey == key) return this;
return new IntTree<V>(newKey, value, left, right);
}
boolean containsKey(long key) {
if (size == 0)
return false;
if (key < this.key)
return left.containsKey(key - this.key);
if (key > this.key)
return right.containsKey(key - this.key);
// otherwise key==this.key:
return true;
}
V get(long key) {
if (size == 0)
return null;
if (key < this.key)
return left.get(key - this.key);
if (key > this.key)
return right.get(key - this.key);
// otherwise key==this.key:
return value;
}
IntTree<V> plus(long key, V value) {
if (size == 0)
return new IntTree<V>(key, value, this, this);
if (key < this.key)
return rebalanced(left.plus(key - this.key, value), right);
if (key > this.key)
return rebalanced(left, right.plus(key - this.key, value));
// otherwise key==this.key, so we simply replace this, with no effect on balance:
if (value == this.value)
return this;
return new IntTree<V>(key, value, left, right);
}
IntTree<V> minus(long key) {
if (size == 0)
return this;
if (key < this.key)
return rebalanced(left.minus(key - this.key), right);
if (key > this.key)
return rebalanced(left, right.minus(key - this.key));
// otherwise key==this.key, so we are killing this node:
if (left.size == 0) // we can just become right node
// make key 'absolute':
return right.withKey(right.key + this.key);
if (right.size == 0) // we can just become left node
return left.withKey(left.key + this.key);
// otherwise replace this with the next key (i.e. the smallest key to the right):
// TODO have minNode() instead of minKey to avoid having to call get()
// TODO get node from larger subtree, i.e. if left.size>right.size use left.maxNode()
// TODO have faster minusMin() instead of just using minus()
long newKey = right.minKey() + this.key;
//(right.minKey() is relative to this; adding this.key makes it 'absolute'
// where 'absolute' really means relative to the parent of this)
V newValue = right.get(newKey - this.key);
// now that we've got the new stuff, take it out of the right subtree:
IntTree<V> newRight = right.minus(newKey - this.key);
// lastly, make the subtree keys relative to newKey (currently they are relative to this.key):
newRight = newRight.withKey((newRight.key + this.key) - newKey);
// left is definitely not empty:
IntTree<V> newLeft = left.withKey((left.key + this.key) - newKey);
return rebalanced(newKey, newValue, newLeft, newRight);
}
/**
* Changes every key k>=key to k+delta.
* <p/>
* This method will create an _invalid_ tree if delta<0
* and the distance between the smallest k>=key in this
* and the largest j<key in this is |delta| or less.
* <p/>
* In other words, this method must not result in any change
* in the order of the keys in this, since the tree structure is
* not being changed at all.
*/
IntTree<V> changeKeysAbove(long key, int delta) {
if (size == 0 || delta == 0)
return this;
if (this.key >= key)
// adding delta to this.key changes the keys of _all_ children of this,
// so we now need to un-change the children of this smaller than key,
// all of which are to the left. note that we still use the 'old' relative key...:
return new IntTree<V>(this.key + delta, value, left.changeKeysBelow(key - this.key, -delta), right);
// otherwise, doesn't apply yet, look to the right:
IntTree<V> newRight = right.changeKeysAbove(key - this.key, delta);
if (newRight == right) return this;
return new IntTree<V>(this.key, value, left, newRight);
}
/**
* Changes every key k<key to k+delta.
* <p/>
* This method will create an _invalid_ tree if delta>0
* and the distance between the largest k<key in this
* and the smallest j>=key in this is delta or less.
* <p/>
* In other words, this method must not result in any overlap or change
* in the order of the keys in this, since the tree _structure_ is
* not being changed at all.
*/
IntTree<V> changeKeysBelow(long key, int delta) {
if (size == 0 || delta == 0)
return this;
if (this.key < key)
// adding delta to this.key changes the keys of _all_ children of this,
// so we now need to un-change the children of this larger than key,
// all of which are to the right. note that we still use the 'old' relative key...:
return new IntTree<V>(this.key + delta, value, left, right.changeKeysAbove(key - this.key, -delta));
// otherwise, doesn't apply yet, look to the left:
IntTree<V> newLeft = left.changeKeysBelow(key - this.key, delta);
if (newLeft == left) return this;
return new IntTree<V>(this.key, value, newLeft, right);
}
// min key in this:
private long minKey() {
if (left.size == 0)
return key;
// make key 'absolute' (i.e. relative to the parent of this):
return left.minKey() + this.key;
}
private IntTree<V> rebalanced(IntTree<V> newLeft, IntTree<V> newRight) {
if (newLeft == left && newRight == right)
return this; // already balanced
return rebalanced(key, value, newLeft, newRight);
}
private static final int OMEGA = 5;
private static final int ALPHA = 2;
// rebalance a tree that is off-balance by at most 1:
private static <V> IntTree<V> rebalanced(long key, V value, IntTree<V> left, IntTree<V> right) {
if (left.size + right.size > 1) {
if (left.size >= OMEGA * right.size) { // rotate to the right
IntTree<V> ll = left.left, lr = left.right;
if (lr.size < ALPHA * ll.size) // single rotation
return new IntTree<V>(left.key + key, left.value,
ll,
new IntTree<V>(-left.key, value,
lr.withKey(lr.key + left.key),
right));
else { // double rotation:
IntTree<V> lrl = lr.left, lrr = lr.right;
return new IntTree<V>(lr.key + left.key + key, lr.value,
new IntTree<V>(-lr.key, left.value,
ll,
lrl.withKey(lrl.key + lr.key)),
new IntTree<V>(-left.key - lr.key, value,
lrr.withKey(lrr.key + lr.key + left.key),
right));
}
} else if (right.size >= OMEGA * left.size) { // rotate to the left
IntTree<V> rl = right.left, rr = right.right;
if (rl.size < ALPHA * rr.size) // single rotation
return new IntTree<V>(right.key + key, right.value,
new IntTree<V>(-right.key, value,
left,
rl.withKey(rl.key + right.key)),
rr);
else { // double rotation:
IntTree<V> rll = rl.left, rlr = rl.right;
return new IntTree<V>(rl.key + right.key + key, rl.value,
new IntTree<V>(-right.key - rl.key, value,
left,
rll.withKey(rll.key + rl.key + right.key)),
new IntTree<V>(-rl.key, right.value,
rlr.withKey(rlr.key + rl.key),
rr));
}
}
}
// otherwise already balanced enough:
return new IntTree<V>(key, value, left, right);
}
}
@@ -1,52 +0,0 @@
/*
* Copyright 2010-2015 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 kotlin.reflect.jvm.internal.pcollections;
/**
* An efficient persistent map from integer keys to non-null values.
*/
final class IntTreePMap<V> {
private static final IntTreePMap<Object> EMPTY = new IntTreePMap<Object>(IntTree.EMPTYNODE);
@SuppressWarnings("unchecked")
public static <V> IntTreePMap<V> empty() {
return (IntTreePMap<V>) EMPTY;
}
private final IntTree<V> root;
private IntTreePMap(IntTree<V> root) {
this.root = root;
}
private IntTreePMap<V> withRoot(IntTree<V> root) {
if (root == this.root) return this;
return new IntTreePMap<V>(root);
}
public V get(int key) {
return root.get(key);
}
public IntTreePMap<V> plus(int key, V value) {
return withRoot(root.plus(key, value));
}
public IntTreePMap<V> minus(int key) {
return withRoot(root.minus(key));
}
}
@@ -1,47 +0,0 @@
/*
* Copyright 2010-2015 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 kotlin.reflect.jvm.internal.pcollections;
final class MapEntry<K, V> implements java.io.Serializable {
private static final long serialVersionUID = 7138329143949025153L;
public final K key;
public final V value;
public MapEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof MapEntry)) return false;
MapEntry<?, ?> e = (MapEntry<?, ?>) o;
return (key == null ? e.key == null : key.equals(e.key)) &&
(value == null ? e.value == null : value.equals(e.value));
}
@Override
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
@Override
public String toString() {
return key + "=" + value;
}
}
@@ -14344,16 +14344,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@@ -14386,16 +14386,6 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@@ -12776,19 +12776,6 @@ public class IrCodegenBoxWasmTestGenerated extends AbstractIrCodegenBoxWasmTest
}
}
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class HashPMap extends AbstractIrCodegenBoxWasmTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.WASM, testDataFilePath);
}
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.WASM, true);
}
}
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
+1 -1
View File
@@ -2,7 +2,7 @@ description = ''
apply plugin: 'kotlin'
JvmToolchain.configureJvmToolchain(project, JdkMajorVersion.JDK_1_6)
JvmToolchain.configureJvmToolchain(project, JdkMajorVersion.JDK_1_8)
def includeJava9 = BuildPropertiesExtKt.getIncludeJava9(project.kotlinBuildProperties)
+2 -2
View File
@@ -20,7 +20,7 @@ plugins {
`java-library`
}
configureJavaOnlyToolchain(JdkMajorVersion.JDK_1_6)
configureJavaOnlyToolchain(JdkMajorVersion.JDK_1_8)
publish()
@@ -146,7 +146,7 @@ val proguard by task<CacheableProguardTask> {
injars(mapOf("filter" to "!META-INF/**,!**/*.kotlin_builtins"), proguardAdditionalInJars)
outjars(fileFrom(base.libsDirectory.asFile.get(), "${base.archivesName.get()}-$version-proguard.jar"))
javaLauncher.set(project.getToolchainLauncherFor(chooseJdk18ForJpsBuild(JdkMajorVersion.JDK_1_6)))
javaLauncher.set(project.getToolchainLauncherFor(chooseJdk18ForJpsBuild(JdkMajorVersion.JDK_1_8)))
libraryjars(mapOf("filter" to "!META-INF/versions/**"), proguardDeps)
libraryjars(
project.files(
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
id("jps-compatible")
}
project.updateJvmTarget("1.6")
project.updateJvmTarget("1.8")
dependencies {
api(kotlinStdlib())
@@ -3,7 +3,7 @@ plugins {
id("jps-compatible")
}
project.updateJvmTarget("1.6")
project.updateJvmTarget("1.8")
dependencies {
implementation(kotlinStdlib())
@@ -3,7 +3,7 @@ plugins {
id("jps-compatible")
}
project.updateJvmTarget("1.6")
project.updateJvmTarget("1.8")
dependencies {
api(kotlinStdlib())
+1 -1
View File
@@ -3,7 +3,7 @@ plugins {
id("jps-compatible")
}
project.configureJvmToolchain(JdkMajorVersion.JDK_1_6)
project.configureJvmToolchain(JdkMajorVersion.JDK_1_8)
dependencies {
api(project(":kotlin-script-runtime"))
-5
View File
@@ -35,10 +35,6 @@ the Kotlin IntelliJ IDEA plugin:
- License: BSD ([license/third_party/asm_license.txt][asm])
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
- Path: core/reflection.jvm/src/kotlin.reflect/jvm/internal/pcollections
- License: MIT ([license/third_party/pcollections_LICENSE.txt][pcollections])
- Origin: Derived from PCollections, A Persistent Java Collections Library (https://pcollections.org/)
- Path: eval4j/src/org/jetbrains/eval4j/interpreterLoop.kt
- License: BSD ([license/third_party/asm_license.txt][asm])
- Origin: Derived from ASM: a very small and fast Java bytecode manipulation framework, Copyright (c) 2000-2011 INRIA, France Telecom
@@ -284,7 +280,6 @@ any distributions of the compiler, libraries or plugin:
[gwt]: third_party/gwt_license.txt
[jquery]: third_party/jquery_license.txt
[lombok]: third_party/testdata/lombok_license.txt
[pcollections]: third_party/pcollections_LICENSE.txt
[qunit]: third_party/qunit_license.txt
[rhino]: third_party/rhino_LICENSE.txt
[rxjava]: third_party/testdata/rxjava_license.txt
-19
View File
@@ -1,19 +0,0 @@
Copyright (c) 2008 Harold Cooper
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -15440,18 +15440,6 @@ public class NativeCodegenBoxTestGenerated extends AbstractNativeCodegenBoxTest
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/hashPMap")
@TestDataPath("$PROJECT_ROOT")
@Tag("codegen")
@UseExtTestCaseGroupProvider()
public class HashPMap {
@Test
public void testAllFilesPresentInHashPMap() throws Exception {
KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/hashPMap"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.NATIVE, true);
}
}
@Nested
@TestMetadata("compiler/testData/codegen/box/ieee754")
@TestDataPath("$PROJECT_ROOT")