Moved inRange and getCachedBox to C++ runtime

This commit is contained in:
Sergey Bogolepov
2018-02-27 16:44:50 +03:00
committed by Sergey Bogolepov
parent ddff7b365e
commit 6259b28cd3
8 changed files with 198 additions and 150 deletions
@@ -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<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 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<Int, Int> = when (this) {
// Just an example.
is KonanTarget.ZEPHYR -> emptyRange
else -> defaultCacheRanges[valueType]!!
}
@@ -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)) {
@@ -336,7 +336,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
override fun visitModuleFragment(declaration: IrModuleFragment) {
context.log{"visitModule : ${ir2string(declaration)}"}
BoxCache.initialize(context)
initializeCachedBoxes(context)
declaration.acceptChildrenVoid(this)
// Note: it is here because it also generates some bitcode.
@@ -30,7 +30,8 @@ class Runtime(bitcodeFile: String) {
val llvmModule: LLVMModuleRef = parseBitcodeFile(bitcodeFile)
internal fun getStructTypeOrNull(name: String) = LLVMGetTypeByName(llvmModule, "struct.$name")
internal fun getStructType(name: String) = getStructTypeOrNull(name)!!
internal fun getStructType(name: String) = getStructTypeOrNull(name)
?: throw Error("struct.$name is not found in the Runtime module.")
val typeInfoType = getStructType("TypeInfo")
val writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo")
@@ -57,7 +58,6 @@ class Runtime(bitcodeFile: String) {
val kotlinToObjCMethodAdapter by lazy { getStructType("KotlinToObjCMethodAdapter") }
val typeInfoObjCExportAddition by lazy { getStructType("TypeInfoObjCExportAddition") }
val pointerSize: Int by lazy {
LLVMABISizeOfType(targetData, objHeaderPtrType).toInt()
}
@@ -93,8 +93,8 @@ internal fun StaticData.createKotlinObject(type: IrClass, body: ConstValue): Con
return createRef(objHeaderPtr)
}
internal fun StaticData.createInitializer(type: KotlinType, body: ConstValue): ConstValue =
Struct(objHeader(type.typeInfoPtr!!), body)
internal fun StaticData.createInitializer(type: IrClass, body: ConstValue): ConstValue =
Struct(objHeader(type.typeInfoPtr), body)
private fun StaticData.getArrayListClass(): ClassDescriptor {
val module = context.irModule!!.descriptor
+121
View File
@@ -0,0 +1,121 @@
/*
* 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.
* 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.
*/
#include "Memory.h"
#include "Types.h"
// C++ part of box caching.
template<class T>
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<KBoolean> BOOLEAN_CACHE[];
extern KBox<KByte> BYTE_CACHE[];
extern KBox<KChar> CHAR_CACHE[];
extern KBox<KShort> SHORT_CACHE[];
extern KBox<KInt> INT_CACHE[];
extern KBox<KLong> LONG_CACHE[];
namespace {
template<class T>
inline bool isInRange(T value, T from, T to) {
return value >= from && value <= to;
}
template<class T>
OBJ_GETTER(getCachedBox, T value, KBox<T> 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);
}
}
@@ -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<Boolean> {
override fun equals(other: Any?): Boolean {
if (other !is BooleanBox) {
@@ -33,7 +59,11 @@ class BooleanBox(val value: Boolean) : Comparable<Boolean> {
}
@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<Char> {
override fun equals(other: Any?): Boolean {
@@ -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)