From ddff7b365e61113bc296622ce23e2e4ca2bbf784 Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Wed, 14 Feb 2018 14:24:13 +0300 Subject: [PATCH] Boxing of bytes, shorts, chars, ints and longs. Cogegen only implementation --- .../jetbrains/kotlin/backend/konan/Boxing.kt | 166 ++++++++++++++++++ .../kotlin/backend/konan/llvm/IrToBitcode.kt | 1 + .../backend/konan/llvm/StaticObjects.kt | 3 + .../jetbrains/kotlin/CompileToBitcode.groovy | 1 - .../src/main/kotlin/konan/internal/Boxing.kt | 32 +++- .../main/kotlin/konan/internal/Intrinsics.kt | 12 ++ 6 files changed, 207 insertions(+), 8 deletions(-) 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 bc69442c691..34ef7891c6f 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 @@ -16,9 +16,12 @@ package org.jetbrains.kotlin.backend.konan +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.getPropertyGetter +import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.types.KotlinType internal fun KonanSymbols.getTypeConversion( @@ -50,3 +53,166 @@ internal fun KonanSymbols.getTypeConversion( internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFunctionSymbol = this.unboxFunctions[valueType] ?: this.boxClasses[valueType]!!.getPropertyGetter("value")!! as IrSimpleFunctionSymbol + + +/** + * Represents static array of boxes. + */ + +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) + } + } + } +} + +/** + * 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) + } +} + +/** + * 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 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.BYTE to (-128 to 127), + ValueType.SHORT to (-128 to 127), + ValueType.CHAR to (0 to 255), + ValueType.INT to (-128 to 127), + ValueType.LONG to (-128 to 127) +) + +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/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 92e8ecafa31..aea948cb37c 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,6 +336,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { override fun equals(other: Any?): Boolean { if (other !is BooleanBox) { @@ -54,7 +52,11 @@ class CharBox(val value: Char) : Comparable { } @ExportForCppRuntime("Kotlin_boxChar") -fun boxChar(value: Char) = CharBox(value) +fun boxChar(value: Char) = if (inCharBoxCache(value)) { + getCachedCharBox(value) +} else { + CharBox(value) +} class ByteBox(val value: Byte) : Number(), Comparable { override fun equals(other: Any?): Boolean { @@ -81,7 +83,11 @@ class ByteBox(val value: Byte) : Number(), Comparable { } @ExportForCppRuntime("Kotlin_boxByte") -fun boxByte(value: Byte) = ByteBox(value) +fun boxByte(value: Byte) = if (inByteBoxCache(value)) { + getCachedByteBox(value) +} else { + ByteBox(value) +} class ShortBox(val value: Short) : Number(), Comparable { override fun equals(other: Any?): Boolean { @@ -108,7 +114,11 @@ class ShortBox(val value: Short) : Number(), Comparable { } @ExportForCppRuntime("Kotlin_boxShort") -fun boxShort(value: Short) = ShortBox(value) +fun boxShort(value: Short) = if (inShortBoxCache(value)) { + getCachedShortBox(value) +} else { + ShortBox(value) +} class IntBox(val value: Int) : Number(), Comparable { override fun equals(other: Any?): Boolean { @@ -135,7 +145,11 @@ class IntBox(val value: Int) : Number(), Comparable { } @ExportForCppRuntime("Kotlin_boxInt") -fun boxInt(value: Int) = IntBox(value) +fun boxInt(value: Int) = if (inIntBoxCache(value)) { + getCachedIntBox(value) +} else { + IntBox(value) +} class LongBox(val value: Long) : Number(), Comparable { override fun equals(other: Any?): Boolean { @@ -162,7 +176,11 @@ class LongBox(val value: Long) : Number(), Comparable { } @ExportForCppRuntime("Kotlin_boxLong") -fun boxLong(value: Long) = LongBox(value) +fun boxLong(value: Long) = if (inLongBoxCache(value)) { + getCachedLongBox(value) +} else { + LongBox(value) +} class FloatBox(val value: Float) : Number(), 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 f79d0cc7ec7..45166905201 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -48,6 +48,18 @@ 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)