From 6259b28cd341763382cc0cfd1f1d8f561cfaf9b8 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Tue, 27 Feb 2018 16:44:50 +0300 Subject: [PATCH] Moved inRange and getCachedBox to C++ runtime --- .../jetbrains/kotlin/backend/konan/Boxing.kt | 170 ++++-------------- .../kotlin/backend/konan/llvm/DataLayout.kt | 3 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 2 +- .../kotlin/backend/konan/llvm/Runtime.kt | 4 +- .../backend/konan/llvm/StaticObjects.kt | 4 +- runtime/src/main/cpp/Boxing.cpp | 121 +++++++++++++ .../src/main/kotlin/konan/internal/Boxing.kt | 32 +++- .../main/kotlin/konan/internal/Intrinsics.kt | 12 -- 8 files changed, 198 insertions(+), 150 deletions(-) create mode 100644 runtime/src/main/cpp/Boxing.cpp diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt index 34ef7891c6f..1aa2b99c19d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2017 JetBrains s.r.o. + * Copyright 2010-2018 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. @@ -16,10 +16,12 @@ package org.jetbrains.kotlin.backend.konan +import kotlinx.cinterop.toByte import llvm.* import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols import org.jetbrains.kotlin.backend.konan.llvm.* import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol +import org.jetbrains.kotlin.ir.util.defaultType import org.jetbrains.kotlin.ir.util.getPropertyGetter import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.types.KotlinType @@ -56,154 +58,59 @@ internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFuncti /** - * Represents static array of boxes. + * Initialize static boxing. + * If output target is native binary then the cache is created. */ - -internal enum class BoxCache(val valueType: ValueType) { - BYTE(ValueType.BYTE), - SHORT(ValueType.SHORT), - CHAR(ValueType.CHAR), - INT(ValueType.INT), - LONG(ValueType.LONG); - - private val valueTypeName = valueType.name.toLowerCase().capitalize() - private val getIntrinsic = "konan.internal.getCached${valueTypeName}Box" - private val checkIntrinsic = "konan.internal.in${valueTypeName}BoxCache" - - val cacheName = "${valueTypeName}Boxes" - val rangeStartName = "${valueTypeName}RangeStart" - val rangeEndName = "${valueTypeName}RangeEnd" - - companion object { - /** - * returns cache corresponding to the given getIntrinsic name. - */ - fun getCacheByBoxGetter(getBoxMethodName: String): BoxCache? = - BoxCache.values().firstOrNull { it.getIntrinsic == getBoxMethodName } - - /** - * returns cache corresponding to the given checkIntrinsic name. - */ - fun getCacheByInRangeChecker(checkRangeMethodName: String): BoxCache? = - BoxCache.values().firstOrNull { it.checkIntrinsic == checkRangeMethodName } - - /** - * Initialize globals. - */ - fun initialize(context: Context) { - values().forEach { - it.initRange(context) - it.initCache(context) - } +internal fun initializeCachedBoxes(context: Context) { + if (context.config.produce.isNativeBinary) { + val cachedTypes = listOf(ValueType.BOOLEAN, ValueType.BYTE, ValueType.CHAR, + ValueType.SHORT, ValueType.INT, ValueType.LONG) + cachedTypes.forEach { valueType -> + val cacheName = "${valueType.name}_CACHE" + val rangeStart = "${valueType.name}_RANGE_FROM" + val rangeEnd = "${valueType.name}_RANGE_TO" + valueType.initCache(context, cacheName, rangeStart, rangeEnd) } } } /** * Adds global that refers to the cache. - * If output target is native binary then the cache is created. */ -private fun BoxCache.initCache(context: Context): LLVMValueRef { - return if (context.config.produce.isNativeBinary) { - context.llvm.staticData.createBoxes(this) - } else { - context.llvm.staticData.addGlobal(cacheName, getLlvmType(context), false) - } +private fun ValueType.initCache(context: Context, cacheName: String, + rangeStartName: String, rangeEndName: String) { + val kotlinType = context.ir.symbols.boxClasses[this]!!.owner + val (start, end) = context.config.target.getBoxCacheRange(this) + // Constancy of these globals allows LLVM's constant propagation and DCE + // to remove fast path of boxing function in case of empty range. + context.llvm.staticData.placeGlobal(rangeStartName, createConstant(start), true) + .setConstant(true) + context.llvm.staticData.placeGlobal(rangeEndName, createConstant(end), true) + .setConstant(true) + val staticData = context.llvm.staticData + val values = (start..end).map { staticData.createInitializer(kotlinType, createConstant(it)) } + val llvmBoxType = structType(context.llvm.runtime.objHeaderType, this.llvmType) + staticData.placeGlobalConstArray(cacheName, llvmBoxType, values, true).llvm } -/** - * Creates globals that defines the smallest and the biggest cached values. - */ -private fun BoxCache.initRange(context: Context) { - if (context.config.produce.isNativeBinary) { - val (start, end) = getRange(context) - // Constancy of these globals allows LLVM's constant propagation and DCE - // to remove fast path of boxing function in case of empty range. - context.llvm.staticData.placeGlobal(rangeStartName, createConstant(start), true) - .setConstant(true) - context.llvm.staticData.placeGlobal(rangeEndName, createConstant(end), true) - .setConstant(true) - } else { - context.llvm.staticData.addGlobal(rangeStartName, valueType.llvmType, false) - context.llvm.staticData.addGlobal(rangeEndName, valueType.llvmType, false) - } -} - -private fun BoxCache.llvmRange(context: Context): Pair { - val start = LLVMGetNamedGlobal(context.llvmModule, rangeStartName)!! - val end = LLVMGetNamedGlobal(context.llvmModule, rangeEndName)!! - return Pair(start, end) -} - -/** - * Checks that box for the given [value] is in the cache. - */ -internal fun BoxCache.inRange(codegen: FunctionGenerationContext, value: LLVMValueRef): LLVMValueRef { - val (startPtr, endPtr) = llvmRange(codegen.context) - val start = codegen.load(startPtr) - val end = codegen.load(endPtr) - val startCheck = codegen.icmpGe(value, start) - val endCheck = codegen.icmpLe(value, end) - return codegen.and(startCheck, endCheck) -} - -private fun BoxCache.getRange(context: Context) = - context.config.target.getBoxCacheRange(valueType) - -internal fun BoxCache.getCachedValue(codegen: FunctionGenerationContext, value: LLVMValueRef): LLVMValueRef { - val startPtr = llvmRange(codegen.context).first - val start = codegen.load(startPtr) - // We should subtract range start to get index of the box. - val index = if (this == BoxCache.BYTE) { - // ByteBox range start has type of i8 and it can't handle values - // that are greater than 127. So we need to cast them to i32. - val startAsInt = codegen.sext(start, LLVMInt32Type()!!) - val valueAsInt = codegen.sext(value, LLVMInt32Type()!!) - LLVMBuildSub(codegen.builder, valueAsInt, startAsInt, "index")!! - } else { - LLVMBuildSub(codegen.builder, value, start, "index")!! - } - val cache = LLVMGetNamedGlobal(codegen.context.llvmModule, cacheName)!! - val elemPtr = codegen.gep(cache, index) - return codegen.bitcast(codegen.kObjHeaderPtr, elemPtr) -} - -private fun StaticData.createBoxes(box: BoxCache): LLVMValueRef { - val kotlinType = context.ir.symbols.boxClasses[box.valueType]!!.descriptor.defaultType - val (start, end) = box.getRange(context) - val values = (start..end).map { createInitializer(kotlinType, box.createConstant(it)) } - return placeGlobalConstArray(box.cacheName, box.getLlvmType(context), values, true).llvm -} - -private fun BoxCache.createConstant(value: Int) = - constValue(when (valueType) { - ValueType.BYTE -> LLVMConstInt(LLVMInt8Type(), value.toByte().toLong(), 1)!! - ValueType.CHAR -> LLVMConstInt(LLVMInt16Type(), value.toChar().toLong(), 0)!! - ValueType.SHORT -> LLVMConstInt(LLVMInt16Type(), value.toShort().toLong(), 1)!! - ValueType.INT -> LLVMConstInt(LLVMInt32Type(), value.toLong(), 1)!! - ValueType.LONG -> LLVMConstInt(LLVMInt64Type(), value.toLong(), 1)!! - else -> error("Cannot box value of type $valueType") +private fun ValueType.createConstant(value: Int) = + constValue(when (this) { + ValueType.BOOLEAN -> LLVMConstInt(LLVMInt1Type(), (value > 0).toByte().toLong(), 1)!! + ValueType.BYTE -> LLVMConstInt(LLVMInt8Type(), value.toByte().toLong(), 1)!! + ValueType.CHAR -> LLVMConstInt(LLVMInt16Type(), value.toChar().toLong(), 0)!! + ValueType.SHORT -> LLVMConstInt(LLVMInt16Type(), value.toShort().toLong(), 1)!! + ValueType.INT -> LLVMConstInt(LLVMInt32Type(), value.toLong(), 1)!! + ValueType.LONG -> LLVMConstInt(LLVMInt64Type(), value.toLong(), 1)!! + else -> error("Cannot box value of type $this") }) -private fun BoxCache.getLlvmType(context: Context) = - structType(context.llvm.runtime.objHeaderType, valueType.llvmType) - -private val ValueType.llvmType - get() = when (this) { - ValueType.BYTE -> LLVMInt8Type()!! - ValueType.CHAR -> LLVMInt16Type()!! - ValueType.SHORT -> LLVMInt16Type()!! - ValueType.INT -> LLVMInt32Type()!! - ValueType.LONG -> LLVMInt64Type()!! - else -> error("Cannot box value of type $this") - } - // When start is greater than end then `inRange` check is always false // and can be eliminated by LLVM. private val emptyRange = 1 to 0 // Memory usage is around 20kb. private val defaultCacheRanges = mapOf( + ValueType.BOOLEAN to (0 to 1), ValueType.BYTE to (-128 to 127), ValueType.SHORT to (-128 to 127), ValueType.CHAR to (0 to 255), @@ -212,7 +119,6 @@ private val defaultCacheRanges = mapOf( ) fun KonanTarget.getBoxCacheRange(valueType: ValueType): Pair = when (this) { - // Just an example. is KonanTarget.ZEPHYR -> emptyRange else -> defaultCacheRanges[valueType]!! } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index df446a1a36b..c2db4a8f9b8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -37,6 +37,9 @@ private val valueTypes = ValueType.values().associate { }!! } +internal val ValueType.llvmType + get() = valueTypes[this]!! + internal fun RuntimeAware.getLLVMType(type: KotlinType): LLVMTypeRef { for ((valueType, llvmType) in valueTypes) { if (type.isRepresentedAs(valueType)) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index aea948cb37c..cd4be13a132 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -336,7 +336,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map +struct KBox { + ObjHeader header; + const T value; +}; + +// Keep naming of these in sync with codegen part. +extern const KBoolean BOOLEAN_RANGE_FROM; +extern const KBoolean BOOLEAN_RANGE_TO; + +extern const KByte BYTE_RANGE_FROM; +extern const KByte BYTE_RANGE_TO; + +extern const KChar CHAR_RANGE_FROM; +extern const KChar CHAR_RANGE_TO; + +extern const KShort SHORT_RANGE_FROM; +extern const KShort SHORT_RANGE_TO; + +extern const KInt INT_RANGE_FROM; +extern const KInt INT_RANGE_TO; + +extern const KLong LONG_RANGE_FROM; +extern const KLong LONG_RANGE_TO; + +extern KBox BOOLEAN_CACHE[]; +extern KBox BYTE_CACHE[]; +extern KBox CHAR_CACHE[]; +extern KBox SHORT_CACHE[]; +extern KBox INT_CACHE[]; +extern KBox LONG_CACHE[]; + +namespace { + +template +inline bool isInRange(T value, T from, T to) { + return value >= from && value <= to; +} + +template +OBJ_GETTER(getCachedBox, T value, KBox cache[], T from) { + uint64_t index = value - from; + RETURN_OBJ(&cache[index].header); +} + +} // namespace + +extern "C" { + +bool inBooleanBoxCache(KBoolean value) { + return isInRange(value, BOOLEAN_RANGE_FROM, BOOLEAN_RANGE_TO); +} + +bool inByteBoxCache(KByte value) { + return isInRange(value, BYTE_RANGE_FROM, BYTE_RANGE_TO); +} + +bool inCharBoxCache(KChar value) { + return isInRange(value, CHAR_RANGE_FROM, CHAR_RANGE_TO); +} + +bool inShortBoxCache(KShort value) { + return isInRange(value, SHORT_RANGE_FROM, SHORT_RANGE_TO); +} + +bool inIntBoxCache(KInt value) { + return isInRange(value, INT_RANGE_FROM, INT_RANGE_TO); +} + +bool inLongBoxCache(KLong value) { + return isInRange(value, LONG_RANGE_FROM, LONG_RANGE_TO); +} + +OBJ_GETTER(getCachedBooleanBox, KBoolean value) { + RETURN_RESULT_OF(getCachedBox, value, BOOLEAN_CACHE, BOOLEAN_RANGE_FROM); +} + +OBJ_GETTER(getCachedByteBox, KByte value) { + // Remember that KByte can't handle values >= 127 + // so it can't be used as indexing type. + RETURN_RESULT_OF(getCachedBox, value, BYTE_CACHE, BYTE_RANGE_FROM); +} + +OBJ_GETTER(getCachedCharBox, KChar value) { + RETURN_RESULT_OF(getCachedBox, value, CHAR_CACHE, CHAR_RANGE_FROM); +} + +OBJ_GETTER(getCachedShortBox, KShort value) { + RETURN_RESULT_OF(getCachedBox, value, SHORT_CACHE, SHORT_RANGE_FROM); +} + +OBJ_GETTER(getCachedIntBox, KInt value) { + RETURN_RESULT_OF(getCachedBox, value, INT_CACHE, INT_RANGE_FROM); +} + +OBJ_GETTER(getCachedLongBox, KLong value) { + RETURN_RESULT_OF(getCachedBox, value, LONG_CACHE, LONG_RANGE_FROM); +} + +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Boxing.kt b/runtime/src/main/kotlin/konan/internal/Boxing.kt index 3150e02ccc2..92e8dc87109 100644 --- a/runtime/src/main/kotlin/konan/internal/Boxing.kt +++ b/runtime/src/main/kotlin/konan/internal/Boxing.kt @@ -16,6 +16,32 @@ package konan.internal + +@SymbolName("getCachedBooleanBox") +external fun getCachedBooleanBox(value: Boolean): BooleanBox +@SymbolName("inBooleanBoxCache") +external fun inBooleanBoxCache(value: Boolean): Boolean +@SymbolName("getCachedByteBox") +external fun getCachedByteBox(value: Byte): ByteBox +@SymbolName("inByteBoxCache") +external fun inByteBoxCache(value: Byte): Boolean +@SymbolName("getCachedCharBox") +external fun getCachedCharBox(value: Char): CharBox +@SymbolName("inCharBoxCache") +external fun inCharBoxCache(value: Char): Boolean +@SymbolName("getCachedShortBox") +external fun getCachedShortBox(value: Short): ShortBox +@SymbolName("inShortBoxCache") +external fun inShortBoxCache(value: Short): Boolean +@SymbolName("getCachedIntBox") +external fun getCachedIntBox(idx: Int): IntBox +@SymbolName("inIntBoxCache") +external fun inIntBoxCache(value: Int): Boolean +@SymbolName("getCachedLongBox") +external fun getCachedLongBox(value: Long): LongBox +@SymbolName("inLongBoxCache") +external fun inLongBoxCache(value: Long): Boolean + class BooleanBox(val value: Boolean) : Comparable { override fun equals(other: Any?): Boolean { if (other !is BooleanBox) { @@ -33,7 +59,11 @@ class BooleanBox(val value: Boolean) : Comparable { } @ExportForCppRuntime("Kotlin_boxBoolean") -fun boxBoolean(value: Boolean) = BooleanBox(value) +fun boxBoolean(value: Boolean) = if (inBooleanBoxCache(value)) { + getCachedBooleanBox(value) +} else { + BooleanBox(value) +} class CharBox(val value: Char) : Comparable { override fun equals(other: Any?): Boolean { diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index 45166905201..f79d0cc7ec7 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -48,18 +48,6 @@ inline fun ieee754NullableEquals(first: Double?, second: Double?): Boolean = whe @Intrinsic external fun areEqualByValue(first: NativePointed?, second: NativePointed?): Boolean @Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): Boolean - -@Intrinsic external fun getCachedByteBox(value: Byte): ByteBox -@Intrinsic external fun inByteBoxCache(value: Byte): Boolean -@Intrinsic external fun getCachedCharBox(value: Char): CharBox -@Intrinsic external fun inCharBoxCache(value: Char): Boolean -@Intrinsic external fun getCachedShortBox(value: Short): ShortBox -@Intrinsic external fun inShortBoxCache(value: Short): Boolean -@Intrinsic external fun getCachedIntBox(idx: Int): IntBox -@Intrinsic external fun inIntBoxCache(value: Int): Boolean -@Intrinsic external fun getCachedLongBox(value: Long): LongBox -@Intrinsic external fun inLongBoxCache(value: Long): Boolean - @Suppress("NOTHING_TO_INLINE") inline fun areEqual(first: Any?, second: Any?): Boolean { return if (first == null) second == null else first.equals(second)