Generate optimized hashCode for primitive type with jvmTarget 1.8+

#KT-7571 Fixed
This commit is contained in:
Mikhael Bogdanov
2017-06-27 14:09:26 +02:00
parent d7e4350d42
commit 69457ef3f1
15 changed files with 293 additions and 29 deletions
@@ -27,10 +27,12 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.builtins.PrimitiveType;
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure;
import org.jetbrains.kotlin.codegen.context.CodegenContext;
import org.jetbrains.kotlin.codegen.intrinsics.HashCode;
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods;
import org.jetbrains.kotlin.codegen.serialization.JvmStringTable;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.lexer.KtTokens;
@@ -512,7 +514,7 @@ public class AsmUtil {
});
}
static void genHashCode(MethodVisitor mv, InstructionAdapter iv, Type type) {
static void genHashCode(MethodVisitor mv, InstructionAdapter iv, Type type, JvmTarget jvmTarget) {
if (type.getSort() == Type.ARRAY) {
Type elementType = correctElementType(type);
if (elementType.getSort() == Type.OBJECT || elementType.getSort() == Type.ARRAY) {
@@ -525,16 +527,6 @@ public class AsmUtil {
else if (type.getSort() == Type.OBJECT) {
iv.invokevirtual("java/lang/Object", "hashCode", "()I", false);
}
else if (type.getSort() == Type.LONG) {
genLongHashCode(mv, iv);
}
else if (type.getSort() == Type.DOUBLE) {
iv.invokestatic("java/lang/Double", "doubleToLongBits", "(D)J", false);
genLongHashCode(mv, iv);
}
else if (type.getSort() == Type.FLOAT) {
iv.invokestatic("java/lang/Float", "floatToIntBits", "(F)I", false);
}
else if (type.getSort() == Type.BOOLEAN) {
Label end = new Label();
iv.dup();
@@ -543,8 +535,24 @@ public class AsmUtil {
iv.iconst(1);
iv.mark(end);
}
else { // byte short char int
// do nothing
else {
if (JvmTarget.JVM_1_6 == jvmTarget) {
if (type.getSort() == Type.LONG) {
genLongHashCode(mv, iv);
}
else if (type.getSort() == Type.DOUBLE) {
iv.invokestatic("java/lang/Double", "doubleToLongBits", "(D)J", false);
genLongHashCode(mv, iv);
}
else if (type.getSort() == Type.FLOAT) {
iv.invokestatic("java/lang/Float", "floatToIntBits", "(F)I", false);
}
else { // byte short char int
// do nothing
}
} else {
HashCode.Companion.invokeHashCode(iv, type);
}
}
}
@@ -573,7 +573,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
iv.ifnull(ifNull);
}
genHashCode(mv, iv, asmType);
genHashCode(mv, iv, asmType, state.getTarget());
if (ifNull != null) {
Label end = new Label();
@@ -16,22 +16,40 @@
package org.jetbrains.kotlin.codegen.intrinsics
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.Callable
import org.jetbrains.kotlin.codegen.CallableMethod
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
class HashCode : IntrinsicMethod() {
override fun toCallable(method: CallableMethod): Callable =
object : IntrinsicCallable(
Type.INT_TYPE,
emptyList(),
nullOrObject(method.dispatchReceiverType),
nullOrObject(method.extensionReceiverType)
) {
override fun invokeIntrinsic(v: InstructionAdapter) {
v.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false)
}
class HashCode(private val jvmTarget: JvmTarget) : IntrinsicMethod() {
override fun toCallable(method: CallableMethod): Callable {
val receiverType = method.dispatchReceiverType ?: method.extensionReceiverType ?: error("No receiver for callable: $method")
val useObjectHashCode = JvmTarget.JVM_1_6 == jvmTarget || !AsmUtil.isPrimitive(receiverType)
return object : IntrinsicCallable(
Type.INT_TYPE,
emptyList(),
if (useObjectHashCode) nullOrObject(method.dispatchReceiverType) else method.dispatchReceiverType,
if (useObjectHashCode) nullOrObject(method.extensionReceiverType) else method.extensionReceiverType
) {
override fun invokeIntrinsic(v: InstructionAdapter) {
v.invokeHashCode(if (useObjectHashCode) AsmTypes.OBJECT_TYPE else receiverType)
}
}
}
companion object {
fun InstructionAdapter.invokeHashCode(type: Type) {
if (AsmUtil.isPrimitive(type)) {
val boxedType = AsmUtil.boxType(type)
visitMethodInsn(Opcodes.INVOKESTATIC, boxedType.internalName, "hashCode", Type.getMethodDescriptor(Type.INT_TYPE, type), false)
}
else {
visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "hashCode", "()I", false)
}
}
}
}
@@ -22,6 +22,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.builtins.PrimitiveType;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.FqNameUnsafe;
@@ -45,7 +47,7 @@ public class IntrinsicMethods {
private static final IntrinsicMethod RANGE_TO = new RangeTo();
private static final IntrinsicMethod INC = new Increment(1);
private static final IntrinsicMethod DEC = new Increment(-1);
private static final IntrinsicMethod HASH_CODE = new HashCode();
private final IntrinsicMethod HASH_CODE;
private static final IntrinsicMethod ARRAY_SIZE = new ArraySize();
private static final Equals EQUALS = new Equals();
@@ -59,7 +61,8 @@ public class IntrinsicMethods {
private static final IntrinsicMethod ARRAY_ITERATOR = new ArrayIterator();
private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap();
public IntrinsicMethods() {
public IntrinsicMethods(JvmTarget jvmTarget) {
HASH_CODE = new HashCode(jvmTarget);
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, RECEIVER_PARAMETER_FQ_NAME, "javaClass", -1, JavaClassProperty.INSTANCE);
intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty());
intrinsicsMap.registerIntrinsic(KotlinBuiltIns.FQ_NAMES.kCallable.toSafe(), null, "name", -1, new KCallableNameProperty());
@@ -138,7 +138,7 @@ class GenerationState @JvmOverloads constructor(
this.bindingContext, classBuilderMode, fileClassesProvider, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace),
this.moduleName, isJvm8Target, isJvm8TargetWithDefaults
)
val intrinsics: IntrinsicMethods = IntrinsicMethods()
val intrinsics: IntrinsicMethods = IntrinsicMethods(target)
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics)
val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this)
@@ -0,0 +1,16 @@
fun box(): String {
true.hashCode()
1.toByte().hashCode()
1.toChar().hashCode()
1.toShort().hashCode()
1.hashCode()
1L.hashCode()
1.0F.hashCode()
1.0.hashCode()
"".hashCode()
return "OK"
}
// 9 \.hashCode
// 9 \.hashCode \(\)I
@@ -0,0 +1,15 @@
// JVM_TARGET: 1.8
fun box(): String {
true.hashCode()
1.toByte().hashCode()
1.toChar().hashCode()
1.toShort().hashCode()
1.hashCode()
1L.hashCode()
1.0F.hashCode()
1.0.hashCode()
"".hashCode()
return "OK"
}
@@ -0,0 +1,25 @@
// JVM_TARGET: 1.8
data class Hash(
val a: Boolean,
val b: Byte,
val c: Char,
val d: Short,
val e: Int,
val f: Long,
val g: Float,
val j: Double,
val k: Any
)
// 8 \.hashCode
// 0 INVOKESTATIC java/lang/Boolean\.hashCode \(Z\)I
// 1 INVOKESTATIC java/lang/Byte\.hashCode \(B\)I
// 1 INVOKESTATIC java/lang/Character\.hashCode \(C\)I
// 1 INVOKESTATIC java/lang/Short\.hashCode \(S\)I
// 1 INVOKESTATIC java/lang/Integer\.hashCode \(I\)I
// 1 INVOKESTATIC java/lang/Long\.hashCode \(J\)I
// 1 INVOKESTATIC java/lang/Float\.hashCode \(F\)I
// 1 INVOKESTATIC java/lang/Double\.hashCode \(D\)I
// 1 INVOKEVIRTUAL java/lang/Object\.hashCode \(\)I
@@ -0,0 +1,26 @@
// JVM_TARGET: 1.8
fun box(): String {
true.hashCode()
1.toByte().hashCode()
1.toChar().hashCode()
1.toShort().hashCode()
1.hashCode()
1L.hashCode()
1.0F.hashCode()
1.0.hashCode()
"".hashCode()
return "OK"
}
// 9 \.hashCode
// 1 INVOKESTATIC java/lang/Boolean\.hashCode \(Z\)I
// 1 INVOKESTATIC java/lang/Byte\.hashCode \(B\)I
// 1 INVOKESTATIC java/lang/Character\.hashCode \(C\)I
// 1 INVOKESTATIC java/lang/Short\.hashCode \(S\)I
// 1 INVOKESTATIC java/lang/Integer\.hashCode \(I\)I
// 1 INVOKESTATIC java/lang/Long\.hashCode \(J\)I
// 1 INVOKESTATIC java/lang/Float\.hashCode \(F\)I
// 1 INVOKESTATIC java/lang/Double\.hashCode \(D\)I
// 1 INVOKEVIRTUAL java/lang/String\.hashCode \(\)I
@@ -487,6 +487,21 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/java8/box/jvm8/optimizations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class Optimizations extends AbstractBlackBoxCodegenTest {
public void testAllFilesPresentInOptimizations() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/box/jvm8/optimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/jvm8/optimizations/hashCode.kt");
doTest(fileName);
}
}
}
@TestMetadata("compiler/testData/codegen/java8/box/mapGetOrDefault")
@@ -0,0 +1,59 @@
/*
* 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.kotlin.codegen;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("compiler/testData/codegen/java8/bytecodeText")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class BytecodeTextJava8TestGenerated extends AbstractBytecodeTextTest {
public void testAllFilesPresentInBytecodeText() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/bytecodeText"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class HashCode extends AbstractBytecodeTextTest {
public void testAllFilesPresentInHashCode() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/bytecodeText/hashCode"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("dataClass.kt")
public void testDataClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode/dataClass.kt");
doTest(fileName);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode/hashCode.kt");
doTest(fileName);
}
}
}
@@ -0,0 +1,59 @@
/*
* 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.kotlin.codegen;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TargetBackend;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("compiler/testData/codegen/java8/bytecodeText")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
public void testAllFilesPresentInBytecodeText() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/bytecodeText"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class HashCode extends AbstractBytecodeTextTest {
public void testAllFilesPresentInHashCode() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/bytecodeText/hashCode"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("dataClass.kt")
public void testDataClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode/dataClass.kt");
doTest(fileName);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/bytecodeText/hashCode/hashCode.kt");
doTest(fileName);
}
}
}
@@ -1308,6 +1308,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/hashCode")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class HashCode extends AbstractBytecodeTextTest {
public void testAllFilesPresentInHashCode() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/hashCode"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/hashCode/hashCode.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/bytecodeText/ieee754")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
@@ -489,6 +489,10 @@ fun main(args: Array<String>) {
testClass<AbstractWriteFlagsTest> {
model("codegen/java8/writeFlags")
}
testClass<AbstractBytecodeTextTest>("BytecodeTextJava8TestGenerated") {
model("codegen/java8/bytecodeText")
}
}
testGroup("idea/tests", "idea/testData") {
@@ -28,6 +28,7 @@ import com.intellij.util.containers.OrderedSet
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
@@ -220,7 +221,7 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
}
private val methods = IntrinsicMethods()
private val methods = IntrinsicMethods(JvmTarget.JVM_1_6)
private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean {
return methods.getIntrinsic(descriptor) != null