Implement "proper numeric comparisons" support in JVM BE

This commit is contained in:
Dmitry Petrov
2018-02-08 09:50:22 +03:00
parent e23a48e8d0
commit a790195808
18 changed files with 652 additions and 138 deletions
@@ -127,6 +127,12 @@ public class AsmUtil {
return primitiveTypeByBoxedType.get(boxedType);
}
@NotNull
public static Type unboxUnlessPrimitive(@NotNull Type boxedOrPrimitiveType) {
if (isPrimitive(boxedOrPrimitiveType)) return boxedOrPrimitiveType;
return unboxType(boxedOrPrimitiveType);
}
public static boolean isBoxedTypeOf(@NotNull Type boxedType, @NotNull Type unboxedType) {
return unboxPrimitiveTypeOrNull(boxedType) == unboxedType;
}
@@ -294,4 +294,84 @@ class PrimitiveToObjectEquality private constructor(
AsmUtil.isIntOrLongPrimitive(leftType) &&
rightType.sort == Type.OBJECT
}
}
class Ieee754Equality private constructor(
private val frameMap: FrameMap,
left: StackValue,
right: StackValue,
operandType: Type
) : NumberLikeCompare(left, right, operandType, KtTokens.EQEQ) {
init {
assert(operandType == Type.FLOAT_TYPE || operandType == Type.DOUBLE_TYPE) {
"Unexpected operandType for IEEE 754 equality (should be F or D): $operandType"
}
}
private val leftType = left.type
private val rightType = right.type
override fun condJump(jumpLabel: Label, v: InstructionAdapter, jumpIfFalse: Boolean) {
val leftIsBoxed = !AsmUtil.isPrimitive(leftType)
val rightIsBoxed = !AsmUtil.isPrimitive(rightType)
frameMap.evaluateOnce(arg1, leftType, v) { left ->
frameMap.evaluateOnce(arg2!!, rightType, v) { right ->
val endLabel = Label()
val bothNonNullLabel = Label()
when {
leftIsBoxed && rightIsBoxed -> {
val leftNonNullLabel = Label()
left.put(leftType, v)
v.ifnonnull(leftNonNullLabel)
// left == null
right.put(rightType, v)
v.ifnonnull(if (jumpIfFalse) jumpLabel else endLabel)
// left == null && right == null
if (jumpIfFalse) v.goTo(endLabel) else v.goTo(jumpLabel)
v.mark(leftNonNullLabel)
// left != null
right.put(rightType, v)
v.ifnull(if (jumpIfFalse) jumpLabel else endLabel)
}
leftIsBoxed -> {
left.put(leftType, v)
v.ifnull(if (jumpIfFalse) jumpLabel else endLabel)
}
rightIsBoxed -> {
right.put(rightType, v)
v.ifnull(if (jumpIfFalse) jumpLabel else endLabel)
}
}
v.mark(bothNonNullLabel)
left.put(operandType, v)
right.put(operandType, v)
v.cmpg(operandType)
if (jumpIfFalse) {
v.ifne(jumpLabel)
} else {
v.ifeq(jumpLabel)
}
v.mark(endLabel)
}
}
}
companion object {
@JvmStatic
fun create(frameMap: FrameMap, left: StackValue, right: StackValue, comparisonType: Type, opToken: IElementType) =
Ieee754Equality(frameMap, left, right, comparisonType).let {
when (opToken) {
KtTokens.EQEQ -> it
KtTokens.EXCLEQ -> Invert(it)
else -> throw AssertionError("Unexpected operator: $opToken")
}
}
}
}
@@ -124,7 +124,7 @@ open class BranchedValue(
IFEQ
)
fun cmp(opToken: IElementType, operandType: Type, left: StackValue, right: StackValue): StackValue =
fun cmp(opToken: IElementType, operandType: Type, left: StackValue, right: StackValue): BranchedValue =
if (operandType.sort == Type.OBJECT)
ObjectCompare(opToken, operandType, left, right)
else
@@ -62,6 +62,7 @@ import org.jetbrains.kotlin.resolve.calls.model.*;
import org.jetbrains.kotlin.resolve.calls.util.CallMaker;
import org.jetbrains.kotlin.resolve.calls.util.FakeCallableDescriptorForObject;
import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt;
import org.jetbrains.kotlin.resolve.checkers.PrimitiveNumericComparisonInfo;
import org.jetbrains.kotlin.resolve.constants.*;
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluatorKt;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
@@ -100,9 +101,7 @@ import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtilsKt.*;
import static org.jetbrains.kotlin.resolve.BindingContext.*;
import static org.jetbrains.kotlin.resolve.BindingContextUtils.getDelegationConstructorCall;
import static org.jetbrains.kotlin.resolve.BindingContextUtils.isVarCapturedInClosure;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumClass;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isObject;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.*;
import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.*;
import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionExpression;
import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isFunctionLiteral;
@@ -2991,7 +2990,10 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
}
else if (opToken == KtTokens.EQEQ || opToken == KtTokens.EXCLEQ ||
opToken == KtTokens.EQEQEQ || opToken == KtTokens.EXCLEQEQEQ) {
return generateEquals(expression.getLeft(), expression.getRight(), opToken, null);
return generateEquals(
expression.getLeft(), expression.getRight(), opToken, null,
bindingContext.get(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO, expression)
);
}
else if (opToken == KtTokens.LT || opToken == KtTokens.LTEQ ||
opToken == KtTokens.GT || opToken == KtTokens.GTEQ) {
@@ -3054,7 +3056,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
@Nullable KtExpression left,
@Nullable KtExpression right,
@NotNull IElementType opToken,
@Nullable StackValue pregeneratedLeft
@Nullable StackValue pregeneratedLeft,
@Nullable PrimitiveNumericComparisonInfo primitiveNumericComparisonInfo
) {
Type leftType = expressionType(left);
Type rightType = expressionType(right);
@@ -3139,7 +3142,9 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
);
}
return genEqualsForExpressionsPreferIEEE754Arithmetic(left, right, opToken, leftType, rightType, pregeneratedLeft);
return genEqualsForExpressionsPreferIeee754Arithmetic(
left, right, opToken, leftType, rightType, pregeneratedLeft, primitiveNumericComparisonInfo
);
}
private boolean isSelectorPureNonNullType(@NotNull KtSafeQualifiedExpression safeExpression) {
@@ -3186,38 +3191,65 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
);
}
@Nullable
private static KotlinType getLeftOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
if (numericComparisonInfo == null) return null;
return numericComparisonInfo.getLeftType();
}
@Nullable
private static KotlinType getRightOperandType(@Nullable PrimitiveNumericComparisonInfo numericComparisonInfo) {
if (numericComparisonInfo == null) return null;
return numericComparisonInfo.getRightType();
}
/*tries to use IEEE 754 arithmetic*/
private StackValue genEqualsForExpressionsPreferIEEE754Arithmetic(
private StackValue genEqualsForExpressionsPreferIeee754Arithmetic(
@Nullable KtExpression left,
@Nullable KtExpression right,
@NotNull IElementType opToken,
@NotNull Type leftType,
@NotNull Type rightType,
@Nullable StackValue pregeneratedLeft
@Nullable StackValue pregeneratedLeft,
@Nullable PrimitiveNumericComparisonInfo primitiveNumericComparisonInfo
) {
assert (opToken == KtTokens.EQEQ || opToken == KtTokens.EXCLEQ) : "Optoken should be '==' or '!=', but: " + opToken;
TypeAndNullability left754Type = calcTypeForIEEE754ArithmeticIfNeeded(left);
TypeAndNullability right754Type = calcTypeForIEEE754ArithmeticIfNeeded(right);
if (left754Type != null && right754Type != null && left754Type.type.equals(right754Type.type)) {
//check nullability cause there is some optimizations in codegen for non-nullable case
if (left754Type.isNullable || right754Type.isNullable) {
if (state.getLanguageVersionSettings().getApiVersion().compareTo(ApiVersion.KOTLIN_1_1) >= 0) {
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
generate754EqualsForNullableTypesViaIntrinsic(v, opToken, pregeneratedLeft, left, left754Type, right, right754Type);
return Unit.INSTANCE;
});
TypeAndNullability left754Type = calcTypeForIeee754ArithmeticIfNeeded(left, getLeftOperandType(primitiveNumericComparisonInfo));
TypeAndNullability right754Type = calcTypeForIeee754ArithmeticIfNeeded(right, getRightOperandType(primitiveNumericComparisonInfo));
if (left754Type != null && right754Type != null) {
if (left754Type.type.equals(right754Type.type)) {
//check nullability cause there is some optimizations in codegen for non-nullable case
if (left754Type.isNullable || right754Type.isNullable) {
if (state.getLanguageVersionSettings().getApiVersion().compareTo(ApiVersion.KOTLIN_1_1) >= 0) {
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
generate754EqualsForNullableTypesViaIntrinsic(v, opToken, pregeneratedLeft, left, left754Type, right, right754Type);
return Unit.INSTANCE;
});
}
else {
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
generate754EqualsForNullableTypes(v, opToken, pregeneratedLeft, left, left754Type, right, right754Type);
return Unit.INSTANCE;
});
}
}
else {
return StackValue.operation(Type.BOOLEAN_TYPE, v -> {
generate754EqualsForNullableTypes(v, opToken, pregeneratedLeft, left, left754Type, right, right754Type);
return Unit.INSTANCE;
});
leftType = left754Type.type;
rightType = right754Type.type;
}
}
else {
leftType = left754Type.type;
rightType = right754Type.type;
else if (shouldUseProperNumberComparisons()) {
Type comparisonType = comparisonOperandType(left754Type.type, right754Type.type);
if (comparisonType == Type.FLOAT_TYPE || comparisonType == Type.DOUBLE_TYPE) {
return Ieee754Equality.create(
myFrameMap,
genLazy(left, boxIfNullable(left754Type)),
genLazy(right, boxIfNullable(right754Type)),
comparisonType,
opToken
);
}
}
}
@@ -3228,6 +3260,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
);
}
@NotNull
private static Type boxIfNullable(@NotNull TypeAndNullability ieee754Type) {
if (ieee754Type.isNullable) return AsmUtil.boxType(ieee754Type.type);
return ieee754Type.type;
}
private void generate754EqualsForNullableTypesViaIntrinsic(
@NotNull InstructionAdapter v,
@NotNull IElementType opToken,
@@ -3400,6 +3438,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
private StackValue generateComparison(KtBinaryExpression expression, StackValue receiver) {
ResolvedCall<?> resolvedCall = CallUtilKt.getResolvedCallWithAssert(expression, bindingContext);
PrimitiveNumericComparisonInfo primitiveNumericComparisonInfo =
bindingContext.get(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO, expression);
KtExpression left = expression.getLeft();
KtExpression right = expression.getRight();
@@ -3409,12 +3449,20 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
StackValue rightValue;
Type leftType = expressionType(left);
Type rightType = expressionType(right);
TypeAndNullability left754Type = calcTypeForIEEE754ArithmeticIfNeeded(left);
TypeAndNullability right754Type = calcTypeForIEEE754ArithmeticIfNeeded(right);
TypeAndNullability left754Type = calcTypeForIeee754ArithmeticIfNeeded(left, getLeftOperandType(primitiveNumericComparisonInfo));
TypeAndNullability right754Type = calcTypeForIeee754ArithmeticIfNeeded(right, getRightOperandType(primitiveNumericComparisonInfo));
Callable callable = resolveToCallable((FunctionDescriptor) resolvedCall.getResultingDescriptor(), false, resolvedCall);
boolean is754Arithmetic = left754Type != null && right754Type != null && left754Type.type.equals(right754Type.type);
if (callable instanceof IntrinsicCallable && ((isPrimitive(leftType) && isPrimitive(rightType)) || is754Arithmetic)) {
type = is754Arithmetic ? left754Type.type : comparisonOperandType(leftType, rightType);
boolean isSame754ArithmeticTypes = left754Type != null && right754Type != null && left754Type.type.equals(right754Type.type);
boolean properNumberComparisons = shouldUseProperNumberComparisons();
if (properNumberComparisons && left754Type != null && right754Type != null) {
type = comparisonOperandType(leftType, rightType);
leftValue = gen(left);
rightValue = gen(right);
}
else if (!properNumberComparisons &&
callable instanceof IntrinsicCallable && ((isPrimitive(leftType) && isPrimitive(rightType)) || isSame754ArithmeticTypes)) {
type = isSame754ArithmeticTypes ? left754Type.type : comparisonOperandType(leftType, rightType);
leftValue = gen(left);
rightValue = gen(right);
}
@@ -3426,9 +3474,25 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return StackValue.cmp(expression.getOperationToken(), type, leftValue, rightValue);
}
private TypeAndNullability calcTypeForIEEE754ArithmeticIfNeeded(@Nullable KtExpression expression) {
return CodegenUtilKt.calcTypeForIEEE754ArithmeticIfNeeded(
expression, bindingContext, context.getFunctionDescriptor(), state.getLanguageVersionSettings());
@Nullable
private TypeAndNullability calcTypeForIeee754ArithmeticIfNeeded(
@Nullable KtExpression expression,
@Nullable KotlinType inferredPrimitiveType
) {
if (expression == null) {
return null;
}
else if (shouldUseProperNumberComparisons()) {
return Ieee754Kt.calcProperTypeForIeee754ArithmeticIfNeeded(expression, bindingContext, inferredPrimitiveType, typeMapper);
}
else {
return Ieee754Kt.legacyCalcTypeForIeee754ArithmeticIfNeeded(
expression, bindingContext, context.getFunctionDescriptor(), state.getLanguageVersionSettings());
}
}
private boolean shouldUseProperNumberComparisons() {
return state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ProperNumberComparisons);
}
private StackValue generateAssignmentExpression(KtBinaryExpression expression) {
@@ -4251,7 +4315,10 @@ The "returned" value of try expression with no finally is either the last expres
private StackValue generateExpressionMatch(StackValue expressionToMatch, KtExpression subjectExpression, KtExpression patternExpression) {
if (expressionToMatch != null) {
return generateEquals(subjectExpression, patternExpression, KtTokens.EQEQ, expressionToMatch);
return generateEquals(
subjectExpression, patternExpression, KtTokens.EQEQ, expressionToMatch,
bindingContext.get(BindingContext.PRIMITIVE_NUMERIC_COMPARISON_INFO, patternExpression)
);
}
else {
return gen(patternExpression);
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics
import org.jetbrains.kotlin.codegen.signature.JvmSignatureWriter
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.deserialization.PLATFORM_DEPENDENT_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
@@ -35,10 +34,8 @@ import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.DescriptorUtils.isSubclass
import org.jetbrains.kotlin.resolve.annotations.hasJvmStaticAnnotation
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.callUtil.getFirstArgumentExpression
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
@@ -49,7 +46,6 @@ import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
@@ -274,38 +270,8 @@ private fun CallableDescriptor.isJvmStaticIn(predicate: (DeclarationDescriptor)
fun Collection<VariableDescriptor>.filterOutDescriptorsWithSpecialNames() = filterNot { it.name.isSpecial }
class TypeAndNullability(@JvmField val type: Type, @JvmField val isNullable: Boolean)
class JvmKotlinType(val type: Type, val kotlinType: KotlinType?)
fun calcTypeForIEEE754ArithmeticIfNeeded(
expression: KtExpression?,
bindingContext: BindingContext,
descriptor: DeclarationDescriptor,
languageVersionSettings: LanguageVersionSettings
): TypeAndNullability? {
val ktType = expression.kotlinType(bindingContext) ?: return null
if (KotlinBuiltIns.isDoubleOrNullableDouble(ktType)) {
return TypeAndNullability(Type.DOUBLE_TYPE, TypeUtils.isNullableType(ktType))
}
if (KotlinBuiltIns.isFloatOrNullableFloat(ktType)) {
return TypeAndNullability(Type.FLOAT_TYPE, TypeUtils.isNullableType(ktType))
}
val dataFlow = DataFlowValueFactory.createDataFlowValue(expression!!, ktType, bindingContext, descriptor)
val stableTypes = bindingContext.getDataFlowInfoBefore(expression).getStableTypes(dataFlow, languageVersionSettings)
return stableTypes.firstNotNullResult {
when {
KotlinBuiltIns.isDoubleOrNullableDouble(it) -> TypeAndNullability(Type.DOUBLE_TYPE, TypeUtils.isNullableType(it))
KotlinBuiltIns.isFloatOrNullableFloat(it) -> TypeAndNullability(Type.FLOAT_TYPE, TypeUtils.isNullableType(it))
else -> null
}
}
}
fun KotlinType.asmType(typeMapper: KotlinTypeMapper) = typeMapper.mapType(this)
fun KtExpression?.asmType(typeMapper: KotlinTypeMapper, bindingContext: BindingContext): Type =
@@ -439,3 +405,22 @@ val CodegenContext<*>.parentContexts
val CodegenContext<*>.contextStackText
get() = parentContextsWithSelf.joinToString(separator = "\n") { it.toString() }
inline fun FrameMap.evaluateOnce(
value: StackValue,
asType: Type,
v: InstructionAdapter,
body: (StackValue) -> Unit
) {
val valueOrTmp: StackValue =
if (value.canHaveSideEffects())
StackValue.local(enterTemp(asType), asType).apply { store(value, v) }
else
value
body(valueOrTmp)
if (valueOrTmp != value) {
leaveTemp(asType)
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. 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.codegen
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.bindingContextUtil.getDataFlowInfoBefore
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactory
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
import org.jetbrains.org.objectweb.asm.Type
class TypeAndNullability(@JvmField val type: Type, @JvmField val isNullable: Boolean)
fun calcProperTypeForIeee754ArithmeticIfNeeded(
expression: KtExpression,
bindingContext: BindingContext,
inferredPrimitiveType: KotlinType?,
typeMapper: KotlinTypeMapper
): TypeAndNullability? {
if (inferredPrimitiveType == null) return null
val ktType = expression.kotlinType(bindingContext) ?: return null
val isNullable = TypeUtils.isNullableType(ktType)
val asmType = typeMapper.mapType(inferredPrimitiveType)
if (!AsmUtil.isPrimitive(asmType)) return null
return TypeAndNullability(asmType, isNullable)
}
fun legacyCalcTypeForIeee754ArithmeticIfNeeded(
expression: KtExpression?,
bindingContext: BindingContext,
descriptor: DeclarationDescriptor,
languageVersionSettings: LanguageVersionSettings
): TypeAndNullability? {
val ktType = expression.kotlinType(bindingContext) ?: return null
if (KotlinBuiltIns.isDoubleOrNullableDouble(ktType)) {
return TypeAndNullability(
Type.DOUBLE_TYPE,
TypeUtils.isNullableType(ktType)
)
}
if (KotlinBuiltIns.isFloatOrNullableFloat(ktType)) {
return TypeAndNullability(
Type.FLOAT_TYPE,
TypeUtils.isNullableType(ktType)
)
}
val dataFlow = DataFlowValueFactory.createDataFlowValue(
expression!!,
ktType,
bindingContext,
descriptor
)
val stableTypes = bindingContext.getDataFlowInfoBefore(expression).getStableTypes(dataFlow, languageVersionSettings)
return stableTypes.firstNotNullResult {
when {
KotlinBuiltIns.isDoubleOrNullableDouble(it) -> TypeAndNullability(
Type.DOUBLE_TYPE,
TypeUtils.isNullableType(
it
)
)
KotlinBuiltIns.isFloatOrNullableFloat(it) -> TypeAndNullability(
Type.FLOAT_TYPE,
TypeUtils.isNullableType(
it
)
)
else -> null
}
}
}
@@ -95,6 +95,7 @@ class InFloatingPointRangeLiteralExpressionGenerator(
}
// TODO evaluateOnce
private fun introduceTemporaryIfRequired(v: InstructionAdapter, value: StackValue, type: Type): Pair<StackValue, Type?> {
val resultValue: StackValue
val resultType: Type?
@@ -49,66 +49,67 @@ import org.jetbrains.kotlin.storage.LockBasedStorageManager
import java.io.File
class GenerationState private constructor(
val project: Project,
builderFactory: ClassBuilderFactory,
val module: ModuleDescriptor,
bindingContext: BindingContext,
val files: List<KtFile>,
val configuration: CompilerConfiguration,
val generateDeclaredClassFilter: GenerateClassFilter,
val codegenFactory: CodegenFactory,
val targetId: TargetId?,
moduleName: String?,
val outDirectory: File?,
private val onIndependentPartCompilationEnd: GenerationStateEventCallback,
wantsDiagnostics: Boolean
val project: Project,
builderFactory: ClassBuilderFactory,
val module: ModuleDescriptor,
bindingContext: BindingContext,
val files: List<KtFile>,
val configuration: CompilerConfiguration,
val generateDeclaredClassFilter: GenerateClassFilter,
val codegenFactory: CodegenFactory,
val targetId: TargetId?,
moduleName: String?,
val outDirectory: File?,
private val onIndependentPartCompilationEnd: GenerationStateEventCallback,
wantsDiagnostics: Boolean
) {
class Builder(
private val project: Project,
private val builderFactory: ClassBuilderFactory,
private val module: ModuleDescriptor,
private val bindingContext: BindingContext,
private val files: List<KtFile>,
private val configuration: CompilerConfiguration
private val project: Project,
private val builderFactory: ClassBuilderFactory,
private val module: ModuleDescriptor,
private val bindingContext: BindingContext,
private val files: List<KtFile>,
private val configuration: CompilerConfiguration
) {
private var generateDeclaredClassFilter: GenerateClassFilter = GenerateClassFilter.GENERATE_ALL
fun generateDeclaredClassFilter(v: GenerateClassFilter) =
apply { generateDeclaredClassFilter = v }
apply { generateDeclaredClassFilter = v }
private var codegenFactory: CodegenFactory = DefaultCodegenFactory
fun codegenFactory(v: CodegenFactory) =
apply { codegenFactory = v }
apply { codegenFactory = v }
private var targetId: TargetId? = null
fun targetId(v: TargetId?) =
apply { targetId = v }
apply { targetId = v }
private var moduleName: String? = configuration[CommonConfigurationKeys.MODULE_NAME]
fun moduleName(v: String?) =
apply { moduleName = v }
apply { moduleName = v }
// 'outDirectory' is a hack to correctly determine if a compiled class is from the same module as the callee during
// partial compilation. Module chunks are treated as a single module.
// TODO: get rid of it with the proper module infrastructure
private var outDirectory: File? = null
fun outDirectory(v: File?) =
apply { outDirectory = v }
apply { outDirectory = v }
private var onIndependentPartCompilationEnd: GenerationStateEventCallback = GenerationStateEventCallback.DO_NOTHING
fun onIndependentPartCompilationEnd(v: GenerationStateEventCallback) =
apply { onIndependentPartCompilationEnd = v }
apply { onIndependentPartCompilationEnd = v }
private var wantsDiagnostics: Boolean = true
fun wantsDiagnostics(v: Boolean) =
apply { wantsDiagnostics = v }
apply { wantsDiagnostics = v }
fun build() =
GenerationState(
project, builderFactory, module, bindingContext, files, configuration,
generateDeclaredClassFilter, codegenFactory, targetId,
moduleName, outDirectory, onIndependentPartCompilationEnd, wantsDiagnostics
)
GenerationState(
project, builderFactory, module, bindingContext, files, configuration,
generateDeclaredClassFilter, codegenFactory, targetId,
moduleName, outDirectory, onIndependentPartCompilationEnd, wantsDiagnostics
)
}
abstract class GenerateClassFilter {
@@ -119,7 +120,8 @@ class GenerationState private constructor(
open fun shouldGenerateClassMembers(processingClassOrObject: KtClassOrObject) = shouldGenerateClass(processingClassOrObject)
companion object {
@JvmField val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
@JvmField
val GENERATE_ALL: GenerateClassFilter = object : GenerateClassFilter() {
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject): Boolean = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject): Boolean = true
@@ -137,7 +139,7 @@ class GenerationState private constructor(
val packagesWithObsoleteParts: Set<FqName>
val obsoleteMultifileClasses: List<FqName>
val deserializationConfiguration: DeserializationConfiguration =
CompilerDeserializationConfiguration(configuration.languageVersionSettings)
CompilerDeserializationConfiguration(configuration.languageVersionSettings)
val deprecationProvider = DeprecationResolver(LockBasedStorageManager.NO_LOCKS, configuration.languageVersionSettings)
@@ -152,15 +154,15 @@ class GenerationState private constructor(
obsoleteMultifileClasses = incrementalCacheForThisTarget.getObsoleteMultifileClasses().map {
JvmClassName.byInternalName(it).fqNameForClassNameWithoutDollars
}
}
else {
} else {
incrementalCacheForThisTarget = null
packagesWithObsoleteParts = emptySet()
obsoleteMultifileClasses = emptyList()
}
}
val extraJvmDiagnosticsTrace: BindingTrace = DelegatingBindingTrace(bindingContext, "For extra diagnostics in ${this::class.java}", false)
val extraJvmDiagnosticsTrace: BindingTrace =
DelegatingBindingTrace(bindingContext, "For extra diagnostics in ${this::class.java}", false)
private val interceptedBuilderFactory: ClassBuilderFactory
private var used = false
@@ -174,21 +176,23 @@ class GenerationState private constructor(
val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) ?: JvmTarget.DEFAULT
val isJvm8Target: Boolean = target == JvmTarget.JVM_1_8
val isJvm8TargetWithDefaults: Boolean = isJvm8Target && configuration.getBoolean(JVMConfigurationKeys.JVM8_TARGET_WITH_DEFAULTS)
val isJvm8TargetWithDefaults: Boolean = isJvm8Target && configuration.getBoolean(JVMConfigurationKeys.JVM8_TARGET_WITH_DEFAULTS)
val generateDefaultImplsForJvm8: Boolean = configuration.getBoolean(JVMConfigurationKeys.INTERFACE_COMPATIBILITY)
val moduleName: String = moduleName ?: JvmCodegenUtil.getModuleName(module)
val classBuilderMode: ClassBuilderMode = builderFactory.classBuilderMode
val bindingTrace: BindingTrace = DelegatingBindingTrace(bindingContext, "trace in GenerationState",
filter = if (wantsDiagnostics) BindingTraceFilter.ACCEPT_ALL else BindingTraceFilter.NO_DIAGNOSTICS)
val bindingTrace: BindingTrace = DelegatingBindingTrace(
bindingContext, "trace in GenerationState",
filter = if (wantsDiagnostics) BindingTraceFilter.ACCEPT_ALL else BindingTraceFilter.NO_DIAGNOSTICS
)
val bindingContext: BindingContext = bindingTrace.bindingContext
val typeMapper: KotlinTypeMapper = KotlinTypeMapper(
this.bindingContext, classBuilderMode, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace),
this.moduleName, isJvm8Target, isJvm8TargetWithDefaults
this.bindingContext, classBuilderMode, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace),
this.moduleName, isJvm8Target, isJvm8TargetWithDefaults
)
val intrinsics: IntrinsicMethods = run {
val shouldUseConsistentEquals = languageVersionSettings.supportsFeature(LanguageFeature.ThrowNpeOnExplicitEqualsForBoxedNull) &&
!configuration.getBoolean(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL)
!configuration.getBoolean(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL)
IntrinsicMethods(target, shouldUseConsistentEquals)
}
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
@@ -210,8 +214,8 @@ class GenerationState private constructor(
val isCallAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS)
val isReceiverAssertionsDisabled: Boolean =
configuration.getBoolean(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS) ||
!languageVersionSettings.supportsFeature(LanguageFeature.NullabilityAssertionOnExtensionReceiver)
configuration.getBoolean(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS) ||
!languageVersionSettings.supportsFeature(LanguageFeature.NullabilityAssertionOnExtensionReceiver)
val isParamAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS)
val isInlineDisabled: Boolean = configuration.getBoolean(CommonConfigurationKeys.DISABLE_INLINE)
val useTypeTableInSerializer: Boolean = configuration.getBoolean(JVMConfigurationKeys.USE_TYPE_TABLE)
@@ -225,26 +229,32 @@ class GenerationState private constructor(
val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
val constructorCallNormalizationMode = configuration.get(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE,
JVMConstructorCallNormalizationMode.DEFAULT)
val constructorCallNormalizationMode = configuration.get(
JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE,
JVMConstructorCallNormalizationMode.DEFAULT
)
init {
val disableOptimization = configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)
this.interceptedBuilderFactory = builderFactory
.wrapWith(
{ OptimizationClassBuilderFactory(it, disableOptimization, constructorCallNormalizationMode) },
{ BuilderFactoryForDuplicateSignatureDiagnostics(
it, this.bindingContext, diagnostics, this.moduleName,
shouldGenerate = { !shouldOnlyCollectSignatures(it) }
).apply { duplicateSignatureFactory = this } },
{ BuilderFactoryForDuplicateClassNameDiagnostics(it, diagnostics) },
{ configuration.get(JVMConfigurationKeys.DECLARATIONS_JSON_PATH)
?.let { destination -> SignatureDumpingBuilderFactory(it, File(destination)) } ?: it }
)
.wrapWith(ClassBuilderInterceptorExtension.getInstances(project)) { builderFactory, extension ->
extension.interceptClassBuilderFactory(builderFactory, bindingContext, diagnostics)
.wrapWith(
{ OptimizationClassBuilderFactory(it, disableOptimization, constructorCallNormalizationMode) },
{
BuilderFactoryForDuplicateSignatureDiagnostics(
it, this.bindingContext, diagnostics, this.moduleName,
shouldGenerate = { !shouldOnlyCollectSignatures(it) }
).apply { duplicateSignatureFactory = this }
},
{ BuilderFactoryForDuplicateClassNameDiagnostics(it, diagnostics) },
{
configuration.get(JVMConfigurationKeys.DECLARATIONS_JSON_PATH)
?.let { destination -> SignatureDumpingBuilderFactory(it, File(destination)) } ?: it
}
)
.wrapWith(ClassBuilderInterceptorExtension.getInstances(project)) { builderFactory, extension ->
extension.interceptClassBuilderFactory(builderFactory, bindingContext, diagnostics)
}
this.factory = ClassFileFactory(this, interceptedBuilderFactory)
}
@@ -269,13 +279,13 @@ class GenerationState private constructor(
interceptedBuilderFactory.close()
}
private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin)
= classBuilderMode == ClassBuilderMode.LIGHT_CLASSES && origin.originKind in doNotGenerateInLightClassMode
private fun shouldOnlyCollectSignatures(origin: JvmDeclarationOrigin) =
classBuilderMode == ClassBuilderMode.LIGHT_CLASSES && origin.originKind in doNotGenerateInLightClassMode
}
private val doNotGenerateInLightClassMode = setOf(CLASS_MEMBER_DELEGATION_TO_DEFAULT_IMPL, BRIDGE, COLLECTION_STUB, AUGMENTED_BUILTIN_API)
private class LazyJvmDiagnostics(compute: () -> Diagnostics): Diagnostics {
private class LazyJvmDiagnostics(compute: () -> Diagnostics) : Diagnostics {
private val delegate by lazy(LazyThreadSafetyMode.SYNCHRONIZED, compute)
override val modificationTracker: ModificationTracker
@@ -283,7 +293,7 @@ private class LazyJvmDiagnostics(compute: () -> Diagnostics): Diagnostics {
override fun all(): Collection<Diagnostic> = delegate.all()
override fun forElement(psiElement: PsiElement) = delegate.forElement(psiElement)
override fun forElement(psiElement: PsiElement) = delegate.forElement(psiElement)
override fun isEmpty() = delegate.isEmpty()
@@ -294,17 +304,20 @@ private class LazyJvmDiagnostics(compute: () -> Diagnostics): Diagnostics {
interface GenerationStateEventCallback : (GenerationState) -> Unit {
companion object {
val DO_NOTHING = GenerationStateEventCallback { }
val DO_NOTHING = GenerationStateEventCallback { }
}
}
fun GenerationStateEventCallback(block: (GenerationState) -> Unit): GenerationStateEventCallback =
object : GenerationStateEventCallback {
override fun invoke(s: GenerationState) = block(s)
}
object : GenerationStateEventCallback {
override fun invoke(s: GenerationState) = block(s)
}
private fun ClassBuilderFactory.wrapWith(vararg wrappers: (ClassBuilderFactory) -> ClassBuilderFactory): ClassBuilderFactory =
wrappers.fold(this) { builderFactory, wrapper -> wrapper(builderFactory) }
wrappers.fold(this) { builderFactory, wrapper -> wrapper(builderFactory) }
private inline fun <T> ClassBuilderFactory.wrapWith(elements: Iterable<T>, wrapper: (ClassBuilderFactory, T) -> ClassBuilderFactory): ClassBuilderFactory =
elements.fold(this, wrapper)
private inline fun <T> ClassBuilderFactory.wrapWith(
elements: Iterable<T>,
wrapper: (ClassBuilderFactory, T) -> ClassBuilderFactory
): ClassBuilderFactory =
elements.fold(this, wrapper)