diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index bb128e9dfcd..54033bdd9e5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -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, @@ -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)!! diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index fb4f453431e..d3be020a764 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -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]. * 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 a341ffd407e..648827c143f 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 @@ -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 { + + 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? { + 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(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`, but `createArrayList` expects `Array`; + // however `vararg` is immutable, and in current implementation it has type `Array`, + // 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) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 015dc3e890c..127ce3bfe76 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -57,6 +57,8 @@ internal open class Struct(val type: LLVMTypeRef?, val elements: List): LLVMTypeRef = memScoped { + LLVMStructType(allocArrayOf(types)[0].ptr, types.size, 0)!! } internal fun ContextUtils.getLlvmFunctionType(function: FunctionDescriptor): LLVMTypeRef { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt index 53b28124b2b..a68f10458ef 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt @@ -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) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt index 0bb2f5736ec..97d660d0632 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticObjects.kt @@ -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): 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) = + createKotlinArray(arrayType, elements.map { constValue(it) }).llvm + +internal fun StaticData.createKotlinArray(arrayType: KotlinType, elements: List): 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` with given values of fields. + * + * @param array value for `array: Array` 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: + val type = arrayListClass.defaultType.replace(listOf(elementType)) + + // FIXME: properly import body type from stdlib + + val body = Struct( + array, // array: Array + Int32(0), // offset: Int + Int32(length), // length: Int + NullPointer(kObjHeader) // backing: ArrayList? + ) + + return createKotlinObject(type, body) +} \ No newline at end of file