backend: implement compile-time eval of varargs and listOf;

also perform some refactoring around.
This commit is contained in:
Svyatoslav Scherbina
2016-12-05 11:24:11 +07:00
committed by SvyatoslavScherbina
parent e28a9c71a8
commit 415f2f1d35
6 changed files with 207 additions and 36 deletions
@@ -76,6 +76,8 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
fun load(value: LLVMValueRef, varName: String): LLVMValueRef = LLVMBuildLoad(builder, value, varName)!!
fun store(value: LLVMValueRef, ptr: LLVMValueRef): LLVMValueRef = LLVMBuildStore(builder, value, ptr)!!
fun isConst(value: LLVMValueRef): Boolean = (LLVMIsConstant(value) == 1)
//-------------------------------------------------------------------------//
fun invoke(llvmFunction: LLVMValueRef?, args: List<LLVMValueRef?>,
@@ -124,6 +126,11 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
fun classType(descriptor: ClassDescriptor): LLVMTypeRef = LLVMGetTypeByName(context.llvmModule, descriptor.symbolName)!!
fun typeInfoValue(descriptor: ClassDescriptor): LLVMValueRef = descriptor.llvmTypeInfoPtr
/**
* Pointer to type info for given type, or `null` if the type doesn't have corresponding type info.
*/
fun typeInfoValue(type: KotlinType): LLVMValueRef? = type.typeInfoPtr?.llvm
fun param(fn: FunctionDescriptor, i: Int): LLVMValueRef {
assert (i >= 0 && i < countParams(fn))
return LLVMGetParam(fn.llvmFunction, i)!!
@@ -12,6 +12,8 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
/**
* Provides utility methods to the implementer.
@@ -94,6 +96,12 @@ internal interface ContextUtils {
val ClassDescriptor.typeInfoPtr: ConstPointer
get() = constPointer(this.llvmTypeInfoPtr)
/**
* Pointer to type info for this type, or `null` if the type doesn't have corresponding type info.
*/
val KotlinType.typeInfoPtr: ConstPointer?
get() = TypeUtils.getClassDescriptor(this)?.typeInfoPtr
/**
* Returns contents of this [GlobalHash].
*
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.classId
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
@@ -694,12 +695,30 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
private fun evaluateVararg(tmpVariableName: String, value: IrVararg): LLVMValueRef? {
val arrayCreationArgs = listOf(codegen.kTheArrayTypeInfo, kImmInt32One, Int32(value.elements.size).llvm)
val arguments = value.elements.map {
assert (it !is IrSpreadElement)
evaluateExpression(codegen.newVar(), it)!!
}
return genCreateArray(tmpVariableName, value.type, arguments)
}
private fun genCreateArray(tmpVariableName: String,
arrayType: KotlinType, elements: List<LLVMValueRef>): LLVMValueRef {
if (elements.all { codegen.isConst(it) }) {
// Note: even if all elements are const, they aren't guaranteed to be statically initialized.
// E.g. an element may be a pointer to lazy-initialized object (aka singleton).
// However it is guaranteed that all elements are already initialized at this point.
return codegen.staticData.createKotlinArray(arrayType, elements)
}
val typeInfo = codegen.typeInfoValue(arrayType)!!
val arrayCreationArgs = listOf(typeInfo, kImmInt32One, Int32(elements.size).llvm)
val array = currentCodeContext.genCall(context.allocArrayFunction, arrayCreationArgs, tmpVariableName)
value.elements.forEachIndexed { i, it ->
val elementValueRaw = evaluateExpression(codegen.newVar(), it)
currentCodeContext.genCall(context.setArrayFunction, listOf(array, Int32(i).llvm, elementValueRaw!!), "")
return@forEachIndexed
elements.forEachIndexed { i, it ->
currentCodeContext.genCall(context.setArrayFunction, listOf(array, Int32(i).llvm, it), "")
}
return array
}
@@ -1367,11 +1386,54 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
//-------------------------------------------------------------------------//
/**
* Tries to evaluate given expression with given (already evaluated) arguments in compile time.
* Returns `null` on failure.
*/
private fun compileTimeEvaluate(expression: IrMemberAccessExpression, args: List<LLVMValueRef>): LLVMValueRef? {
if (!args.all { codegen.isConst(it) }) {
return null
}
val function = expression.descriptor
if (function.fqNameSafe.asString() == "kotlin.collections.listOf" && function.valueParameters.size == 1) {
val varargExpression = expression.getValueArgument(0) as? IrVararg
if (varargExpression != null) {
// The function is kotlin.collections.listOf<T>(vararg args: T).
// TODO: refer functions more reliably.
val vararg = args.single()
if (varargExpression.elements.any { it is IrSpreadElement }) {
return null // not supported yet, see `length` calculation below.
}
val length = varargExpression.elements.size
// TODO: store length in `vararg` itself when more abstract types will be used for values.
// `elementType` is type argument of function return type:
val elementType = function.returnType!!.arguments.single()
val array = constPointer(vararg)
// Note: dirty hack here: `vararg` has type `Array<out E>`, but `createArrayList` expects `Array<E>`;
// however `vararg` is immutable, and in current implementation it has type `Array<E>`,
// so let's ignore this mismatch currently for simplicity.
return context.staticData.createArrayList(elementType, array, length).llvm
}
}
return null
}
private fun evaluateCall(tmpVariableName: String, value: IrMemberAccessExpression): LLVMValueRef {
logger.log("evaluateCall : $tmpVariableName = ${ir2string(value)}")
val args = evaluateExplicitArgs(value)
compileTimeEvaluate(value, args)?.let { return it }
when {
value is IrDelegatingConstructorCall ->
return delegatingConstructorCall(tmpVariableName, value.descriptor, args)
@@ -57,6 +57,8 @@ internal open class Struct(val type: LLVMTypeRef?, val elements: List<ConstValue
constructor(type: LLVMTypeRef?, vararg elements: ConstValue) : this(type, elements.toList())
constructor(vararg elements: ConstValue) : this(structType(elements.map { it.llvmType }), *elements)
override val llvm = memScoped {
val values = elements.map { it.llvm }.toTypedArray()
val valuesNativeArrayPtr = allocArrayOf(*values)[0].ptr
@@ -128,8 +130,10 @@ internal val ContextUtils.kNullArrayHeaderPtr: LLVMValueRef
internal fun pointerType(pointeeType: LLVMTypeRef) = LLVMPointerType(pointeeType, 0)!!
internal fun structType(vararg types: LLVMTypeRef): LLVMTypeRef = memScoped {
LLVMStructType(allocArrayOf(*types)[0].ptr, types.size, 0)!!
internal fun structType(vararg types: LLVMTypeRef): LLVMTypeRef = structType(types.toList())
internal fun structType(types: List<LLVMTypeRef>): LLVMTypeRef = memScoped {
LLVMStructType(allocArrayOf(types)[0].ptr, types.size, 0)!!
}
internal fun ContextUtils.getLlvmFunctionType(function: FunctionDescriptor): LLVMTypeRef {
@@ -15,11 +15,16 @@ internal class StaticData(override val context: Context): ContextUtils {
companion object {
fun create(staticData: StaticData, type: LLVMTypeRef, name: String): Global {
val module = staticData.context.llvmModule
if (LLVMGetNamedGlobal(module, name) != null) {
if (name != "" && LLVMGetNamedGlobal(module, name) != null) {
throw IllegalArgumentException("Global '$name' already exists")
}
val llvmGlobal = LLVMAddGlobal(module, type, name)!!
if (name == "") {
LLVMSetLinkage(llvmGlobal, LLVMLinkage.LLVMPrivateLinkage)
}
return Global(staticData, llvmGlobal)
}
}
@@ -2,8 +2,13 @@ package org.jetbrains.kotlin.backend.konan.llvm
import llvm.*
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeProjection
import org.jetbrains.kotlin.types.replace
private fun StaticData.objHeader(containerOffsetNegative: Int, typeInfo: ConstPointer): Struct {
assert (containerOffsetNegative >= 0)
@@ -16,41 +21,121 @@ private fun StaticData.arrayHeader(containerOffsetNegative: Int, typeInfo: Const
return Struct(runtime.arrayHeaderType, objHeader, Int32(length))
}
private fun requiredPadding(size: Int, align: Int): Int {
val rem = size % align
return (align - rem) % align
}
private fun StaticData.staticContainerHeader(): Struct {
val CONTAINER_TAG_NOCOUNT = 1 // FIXME: copy-pasted from runtime
return Struct(runtime.containerHeaderType, Int32(CONTAINER_TAG_NOCOUNT))
val header = Struct(runtime.containerHeaderType, Int32(CONTAINER_TAG_NOCOUNT))
// ArrayHeader struct is represented as packed in LLVM;
// so add explicit padding to container's header to make its objects properly aligned:
val llvmSize = LLVMABISizeOfType(llvmTargetData, header.type).toInt()
val paddingSize = requiredPadding(llvmSize, 8)
val padding = ConstArray(int8Type, ByteArray(paddingSize).map { Int8(it) })
// TODO: handle such cases more generally by using C types alignments instead of LLVM ones.
return Struct(header, padding)
}
internal fun StaticData.createKotlinStringLiteral(value: IrConst<String>): ConstPointer {
val base64Str = value.value.globalHashBase64
val valueBytes = value.value.toByteArray(Charsets.UTF_8)
val name = "kstr:" + value.value.globalHashBase64
val elements = value.value.toByteArray(Charsets.UTF_8).map { Int8(it) }
val arrayCount = valueBytes.size
val arrayType = LLVMArrayType(int8Type, arrayCount)
val objRef = createKotlinArray(value.type, elements)
val compositeType = structType(runtime.containerHeaderType, runtime.arrayHeaderType, arrayType)
// TODO: use C types alignments instead of LLVM ones
val global = this.createGlobal(compositeType, "kstrcont:$base64Str")
LLVMSetLinkage(global.llvmGlobal, LLVMLinkage.LLVMPrivateLinkage)
val containerHeader = staticContainerHeader()
val objHeaderPtr = global.pointer.getElementPtr(1)
val containerHeaderPtr = global.pointer.getElementPtr(0)
val containerOffsetNegative = objHeaderPtr.sub(containerHeaderPtr)
val stringClass = value.type.constructor.declarationDescriptor as ClassDescriptor
assert (stringClass.fqNameSafe.asString() == "kotlin.String")
val arrayHeader = arrayHeader(containerOffsetNegative, stringClass.typeInfoPtr, arrayCount)
val array = ConstArray(int8Type, valueBytes.map { Int8(it) } )
global.setInitializer(Struct(compositeType, containerHeader, arrayHeader, array))
val stringRef = objHeaderPtr.bitcast(getLLVMType(value.type))
val res = createAlias("kstr:$base64Str", stringRef)
val res = createAlias(name, objRef)
LLVMSetLinkage(res.llvm, LLVMLinkage.LLVMWeakAnyLinkage)
return res
}
private fun StaticData.createRef(type: KotlinType, objHeaderPtr: ConstPointer): ConstPointer {
val llvmType = getLLVMType(type)
return if (llvmType != objHeaderPtr.llvmType) {
objHeaderPtr.bitcast(llvmType)
} else {
objHeaderPtr
}
}
internal fun StaticData.createKotlinArray(arrayType: KotlinType, elements: List<LLVMValueRef>) =
createKotlinArray(arrayType, elements.map { constValue(it) }).llvm
internal fun StaticData.createKotlinArray(arrayType: KotlinType, elements: List<ConstValue>): ConstPointer {
val typeInfo = arrayType.typeInfoPtr!!
val bodyElementType: LLVMTypeRef = elements.firstOrNull()?.llvmType ?: int8Type
// (use [0 x i8] as body if there are no elements)
val arrayBody = ConstArray(bodyElementType, elements)
val containerHeader = staticContainerHeader()
val compositeType = structType(containerHeader.llvmType, runtime.arrayHeaderType, arrayBody.llvmType)
val global = this.createGlobal(compositeType, "")
val objHeaderPtr = global.pointer.getElementPtr(1)
val containerHeaderPtr = global.pointer.getElementPtr(0)
val containerOffsetNegative = objHeaderPtr.sub(containerHeaderPtr)
val arrayHeader = arrayHeader(containerOffsetNegative, typeInfo, elements.size)
global.setInitializer(Struct(compositeType, containerHeader, arrayHeader, arrayBody))
return createRef(arrayType, objHeaderPtr)
}
internal fun StaticData.createKotlinObject(type: KotlinType, body: ConstValue): ConstPointer {
val typeInfo = type.typeInfoPtr!!
val containerHeader = staticContainerHeader()
val compositeType = structType(containerHeader.llvmType, runtime.objHeaderType, body.llvmType)
val global = this.createGlobal(compositeType, "")
val objHeaderPtr = global.pointer.getElementPtr(1)
val containerHeaderPtr = global.pointer.getElementPtr(0)
val containerOffsetNegative = objHeaderPtr.sub(containerHeaderPtr)
val objHeader = objHeader(containerOffsetNegative, typeInfo)
global.setInitializer(Struct(compositeType, containerHeader, objHeader, body))
return createRef(type, objHeaderPtr)
}
private fun StaticData.getArrayListClass(): ClassDescriptor {
val module = context.irModule.descriptor
val pkg = module.getPackage(FqName.fromSegments(listOf("kotlin", "collections")))
val classifier = pkg.memberScope.getContributedClassifier(Name.identifier("ArrayList"),
NoLookupLocation.FROM_BACKEND)
return classifier as ClassDescriptor
}
/**
* Creates static instance of `kotlin.collections.ArrayList<elementType>` with given values of fields.
*
* @param array value for `array: Array<E>` field.
* @param length value for `length: Int` field.
*/
internal fun StaticData.createArrayList(elementType: TypeProjection, array: ConstPointer, length: Int): ConstPointer {
val arrayListClass = getArrayListClass()
// type is ArrayList<elementType>:
val type = arrayListClass.defaultType.replace(listOf(elementType))
// FIXME: properly import body type from stdlib
val body = Struct(
array, // array: Array<E>
Int32(0), // offset: Int
Int32(length), // length: Int
NullPointer(kObjHeader) // backing: ArrayList<E>?
)
return createKotlinObject(type, body)
}