Boxing of bytes, shorts, chars, ints and longs. Cogegen only implementation
This commit is contained in:
committed by
Sergey Bogolepov
parent
0abcd62c13
commit
ddff7b365e
+166
@@ -16,9 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.konan
|
package org.jetbrains.kotlin.backend.konan
|
||||||
|
|
||||||
|
import llvm.*
|
||||||
import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols
|
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.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.util.getPropertyGetter
|
import org.jetbrains.kotlin.ir.util.getPropertyGetter
|
||||||
|
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
internal fun KonanSymbols.getTypeConversion(
|
internal fun KonanSymbols.getTypeConversion(
|
||||||
@@ -50,3 +53,166 @@ internal fun KonanSymbols.getTypeConversion(
|
|||||||
internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFunctionSymbol =
|
internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFunctionSymbol =
|
||||||
this.unboxFunctions[valueType]
|
this.unboxFunctions[valueType]
|
||||||
?: this.boxClasses[valueType]!!.getPropertyGetter("value")!! as IrSimpleFunctionSymbol
|
?: 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<LLVMValueRef, LLVMValueRef> {
|
||||||
|
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<Int, Int> = when (this) {
|
||||||
|
// Just an example.
|
||||||
|
is KonanTarget.ZEPHYR -> emptyRange
|
||||||
|
else -> defaultCacheRanges[valueType]!!
|
||||||
|
}
|
||||||
+1
@@ -336,6 +336,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
override fun visitModuleFragment(declaration: IrModuleFragment) {
|
override fun visitModuleFragment(declaration: IrModuleFragment) {
|
||||||
context.log{"visitModule : ${ir2string(declaration)}"}
|
context.log{"visitModule : ${ir2string(declaration)}"}
|
||||||
|
|
||||||
|
BoxCache.initialize(context)
|
||||||
declaration.acceptChildrenVoid(this)
|
declaration.acceptChildrenVoid(this)
|
||||||
|
|
||||||
// Note: it is here because it also generates some bitcode.
|
// Note: it is here because it also generates some bitcode.
|
||||||
|
|||||||
+3
@@ -93,6 +93,9 @@ internal fun StaticData.createKotlinObject(type: IrClass, body: ConstValue): Con
|
|||||||
return createRef(objHeaderPtr)
|
return createRef(objHeaderPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun StaticData.createInitializer(type: KotlinType, body: ConstValue): ConstValue =
|
||||||
|
Struct(objHeader(type.typeInfoPtr!!), body)
|
||||||
|
|
||||||
private fun StaticData.getArrayListClass(): ClassDescriptor {
|
private fun StaticData.getArrayListClass(): ClassDescriptor {
|
||||||
val module = context.irModule!!.descriptor
|
val module = context.irModule!!.descriptor
|
||||||
val pkg = module.getPackage(FqName.fromSegments(listOf("kotlin", "collections")))
|
val pkg = module.getPackage(FqName.fromSegments(listOf("kotlin", "collections")))
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ class CompileCppToBitcode extends DefaultTask {
|
|||||||
args compilerArgs
|
args compilerArgs
|
||||||
|
|
||||||
args "-I$headersDir"
|
args "-I$headersDir"
|
||||||
|
|
||||||
args '-c', '-emit-llvm'
|
args '-c', '-emit-llvm'
|
||||||
args project.fileTree(srcDir) {
|
args project.fileTree(srcDir) {
|
||||||
include('**/*.cpp')
|
include('**/*.cpp')
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
|
|
||||||
package konan.internal
|
package konan.internal
|
||||||
|
|
||||||
// TODO: cache some boxes.
|
|
||||||
|
|
||||||
class BooleanBox(val value: Boolean) : Comparable<Boolean> {
|
class BooleanBox(val value: Boolean) : Comparable<Boolean> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (other !is BooleanBox) {
|
if (other !is BooleanBox) {
|
||||||
@@ -54,7 +52,11 @@ class CharBox(val value: Char) : Comparable<Char> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime("Kotlin_boxChar")
|
@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<Byte> {
|
class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -81,7 +83,11 @@ class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime("Kotlin_boxByte")
|
@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<Short> {
|
class ShortBox(val value: Short) : Number(), Comparable<Short> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -108,7 +114,11 @@ class ShortBox(val value: Short) : Number(), Comparable<Short> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime("Kotlin_boxShort")
|
@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<Int> {
|
class IntBox(val value: Int) : Number(), Comparable<Int> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -135,7 +145,11 @@ class IntBox(val value: Int) : Number(), Comparable<Int> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime("Kotlin_boxInt")
|
@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<Long> {
|
class LongBox(val value: Long) : Number(), Comparable<Long> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -162,7 +176,11 @@ class LongBox(val value: Long) : Number(), Comparable<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ExportForCppRuntime("Kotlin_boxLong")
|
@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<Float> {
|
class FloatBox(val value: Float) : Number(), Comparable<Float> {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
|||||||
@@ -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: NativePointed?, second: NativePointed?): Boolean
|
||||||
@Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): 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")
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
inline fun areEqual(first: Any?, second: Any?): Boolean {
|
inline fun areEqual(first: Any?, second: Any?): Boolean {
|
||||||
return if (first == null) second == null else first.equals(second)
|
return if (first == null) second == null else first.equals(second)
|
||||||
|
|||||||
Reference in New Issue
Block a user