From 109f84f9af34620b144dab8f2cc7d3e650dc18bb Mon Sep 17 00:00:00 2001 From: Nikolay Igotti Date: Tue, 22 Aug 2017 16:21:10 +0300 Subject: [PATCH] Immutable data prototype. (#799) --- .../kotlin/backend/konan/KonanPlatform.kt | 4 + .../konan/descriptors/DescriptorUtils.kt | 3 +- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 7 ++ .../kotlin/backend/konan/llvm/IrToBitcode.kt | 30 +++++++- .../backend/konan/llvm/RTTIGenerator.kt | 21 +++--- .../kotlin/backend/konan/llvm/StaticData.kt | 10 +++ .../backend/konan/llvm/StaticObjects.kt | 2 +- .../konan/lower/SpecialCallsLowering.kt | 40 +++++++++- backend.native/tests/build.gradle | 5 ++ .../tests/runtime/collections/array3.kt | 13 ++++ runtime/src/main/cpp/Arrays.cpp | 21 ++++++ runtime/src/main/kotlin/konan/BinaryBlob.kt | 74 +++++++++++++++++++ 12 files changed, 215 insertions(+), 15 deletions(-) create mode 100644 backend.native/tests/runtime/collections/array3.kt create mode 100644 runtime/src/main/kotlin/konan/BinaryBlob.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt index b5ebb5fc910..22eb44be8ab 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt @@ -58,6 +58,10 @@ class KonanBuiltIns(storageManager: StorageManager) : KotlinBuiltIns(storageMana val nativePtrPlusLong by lazy { nativePtr.unsubstitutedMemberScope.getContributedFunctions("plus").single() } val nativePtrToLong by lazy { nativePtr.unsubstitutedMemberScope.getContributedFunctions("toLong").single() } val getNativeNullPtr by lazy { packageScope.getContributedFunctions("getNativeNullPtr").single() } + val immutableBinaryBlobOf by lazy { + builtInsModule.getPackage(FqName("konan")).memberScope. + getContributedFunctions("immutableBinaryBlobOf").single() + } private fun MemberScope.getContributedClassifier(name: String) = this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index d27d8ee2248..a44a71f5a2b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -95,7 +95,8 @@ private val arrayTypes = setOf( "kotlin.LongArray", "kotlin.FloatArray", "kotlin.DoubleArray", - "kotlin.BooleanArray" + "kotlin.BooleanArray", + "konan.ImmutableBinaryBlob" ) internal val ClassDescriptor.isIntrinsic: Boolean diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index a44addc6ce1..360a8c39b9c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -83,6 +83,13 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym } }.toMap() + val immutableBinaryBlob = symbolTable.referenceClass( + builtInsPackage("konan").getContributedClassifier( + Name.identifier("ImmutableBinaryBlob"), NoLookupLocation.FROM_BACKEND + ) as ClassDescriptor + ) + + val immutableBinaryBlobOf = symbolTable.referenceSimpleFunction(context.builtIns.immutableBinaryBlobOf) val scheduleImpl = symbolTable.referenceSimpleFunction(context.interopBuiltIns.scheduleImplFunction) 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 a568db717d0..12f1e1e0a33 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 @@ -1383,6 +1383,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map) = + if (this.produceImmutableBinaryBlob) { + context.llvm.staticData.createImmutableBinaryBlob(value) + } else { + context.llvm.staticData.kotlinStringLiteral( + context.builtIns.stringType, value).llvm + } private fun evaluateConst(value: IrConst<*>): LLVMValueRef { context.log{"evaluateConst : ${ir2string(value)}"} @@ -1397,8 +1404,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map return LLVMConstInt(LLVMInt16Type(), (value.value as Short).toLong(), 1)!! IrConstKind.Int -> return LLVMConstInt(LLVMInt32Type(), (value.value as Int).toLong(), 1)!! IrConstKind.Long -> return LLVMConstInt(LLVMInt64Type(), value.value as Long, 1)!! - IrConstKind.String -> return context.llvm.staticData.kotlinStringLiteral( - context.builtIns.stringType, value as IrConst).llvm + IrConstKind.String -> return evaluateStringConst(value as IrConst) IrConstKind.Float -> return LLVMConstRealOfString(LLVMFloatType(), (value.value as Float).toString())!! IrConstKind.Double -> return LLVMConstRealOfString(LLVMDoubleType(), (value.value as Double).toString())!! } @@ -1757,16 +1763,30 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { + // TODO: remove this hack by properly implementing blobs in the frontend. + if (expression.descriptor.original == context.builtIns.immutableBinaryBlobOf) { + // As calls to immutableBinaryBlobOf() cannot be composed, it's OK to + // have simple flag for that purpose. + assert(!produceImmutableBinaryBlob) + produceImmutableBinaryBlob = true + } + val evaluatedArgs = expression.getArguments().map { (param, argExpr) -> param to evaluateExpression(argExpr) }.toMap() + if (produceImmutableBinaryBlob) + produceImmutableBinaryBlob = false + val allValueParameters = expression.descriptor.allParameters return allValueParameters.dropWhile { it !in evaluatedArgs }.map { @@ -1968,6 +1988,12 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { + // LLVM value is already computed when evaluating argument, just use it. + args.single() + } + interop.objCObjectInitFromPtr -> { genObjCObjectInitFromPtr(args) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 765d12cd3a4..33e94bdc87f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -78,16 +78,17 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { } private val arrayClasses = mapOf( - "kotlin.Array" to -LLVMABISizeOfType(llvmTargetData, kObjHeaderPtr).toInt(), - "kotlin.ByteArray" to -1, - "kotlin.CharArray" to -2, - "kotlin.ShortArray" to -2, - "kotlin.IntArray" to -4, - "kotlin.LongArray" to -8, - "kotlin.FloatArray" to -4, - "kotlin.DoubleArray" to -8, - "kotlin.BooleanArray" to -1, - "kotlin.String" to -2 + "kotlin.Array" to -LLVMABISizeOfType(llvmTargetData, kObjHeaderPtr).toInt(), + "kotlin.ByteArray" to -1, + "kotlin.CharArray" to -2, + "kotlin.ShortArray" to -2, + "kotlin.IntArray" to -4, + "kotlin.LongArray" to -8, + "kotlin.FloatArray" to -4, + "kotlin.DoubleArray" to -8, + "kotlin.BooleanArray" to -1, + "kotlin.String" to -2, + "konan.ImmutableBinaryBlob" to -1 ) private fun getInstanceSize(classType: LLVMTypeRef?, className: FqName) : Int { 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 69eb5b23b0b..c1f3a23b3d7 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 @@ -160,3 +160,13 @@ internal class StaticData(override val context: Context): ContextUtils { fun kotlinStringLiteral(type: KotlinType, value: IrConst) = stringLiterals.getOrPut(value.value) { createKotlinStringLiteral(type, value) } } + +/** + * Creates static instance of `konan.ImmutableByteArray` with given values of elements. + * + * @param args data for constant creation. + */ +internal fun StaticData.createImmutableBinaryBlob(value: IrConst): LLVMValueRef { + val args = value.value.map { Int8(it.toByte()).llvm } + return createKotlinArray(context.ir.symbols.immutableBinaryBlob.descriptor.defaultType, args) +} \ No newline at end of file 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 875a226286a..76df0e2eb6b 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 @@ -68,7 +68,6 @@ 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 @@ -143,6 +142,7 @@ internal fun StaticData.createArrayList(elementType: TypeProjection, array: Cons return createKotlinObject(type, body) } + internal fun StaticData.createUnitInstance(descriptor: ClassDescriptor, bodyType: LLVMTypeRef, typeInfo: ConstPointer diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialCallsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialCallsLowering.kt index 65aa7f87241..69ab2a5b495 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialCallsLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/SpecialCallsLowering.kt @@ -1,12 +1,17 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.KonanConfigKeys import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrConstKind import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrSpreadElement +import org.jetbrains.kotlin.ir.expressions.IrVararg import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.types.typeUtil.isUnit @@ -14,6 +19,7 @@ import org.jetbrains.kotlin.types.typeUtil.isUnit /** * This pass runs before inlining and performs the following additional transformations over some calls: * - Assertion call removal. + * - Convert immutableBinaryBlobOf() arguments to special IrConst. */ internal class SpecialCallsLowering(val context: Context) : FileLoweringPass { @@ -32,6 +38,38 @@ internal class SpecialCallsLowering(val context: Context) : FileLoweringPass { return IrCompositeImpl(expression.startOffset, expression.endOffset, expression.type) } + if (expression.symbol == context.ir.symbols.immutableBinaryBlobOf) { + // Convert arguments of the binary blob to special IrConst structure, so that + // vararg lowering will not affect it. + val args = expression.getValueArgument(0) as? IrVararg + if (args == null) throw Error("varargs shall not be lowered yet") + if (args.elements.any { it is IrSpreadElement }) { + context.reportCompilationError("no spread elements allowed here", irFile, args) + } + val builder = StringBuilder() + args.elements.forEach { + if (it !is IrConst<*>) { + context.reportCompilationError( + "all elements of binary blob must be constants", irFile, it) + } + val value = when (it.kind) { + IrConstKind.Short -> (it.value as Short).toInt() + else -> + context.reportCompilationError("incorrect value for binary data: $it.value", irFile, it) + } + if (value < 0 || value > 0xff) + context.reportCompilationError("incorrect value for binary data: $value", irFile, it) + // Luckily, all values in range 0x00 .. 0xff represent valid UTF-16 symbols, + // block 0 (Basic Latin) and block 1 (Latin-1 Supplement) in + // Basic Multilingual Plane, so we could just append data "as is". + builder.append(value.toChar()) + } + expression.putValueArgument(0, IrConstImpl( + expression.startOffset, expression.endOffset, + context.ir.symbols.immutableBinaryBlob.descriptor.defaultType, + IrConstKind.String, builder.toString())) + } + return expression } }) diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 5a4a89e6c7d..878aed5c4f8 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1081,6 +1081,11 @@ task array2(type: RunKonanTest) { source = "runtime/collections/array2.kt" } +task array3(type: RunKonanTest) { + goldValue = "1 2 3 7 8 9 -128 -1 \n1 2 3 7 8 9 -128 -1 \n" + source = "runtime/collections/array3.kt" +} + task sort0(type: RunKonanTest) { goldValue = "[a, b, x]\n[-1, 0, 42, 239, 100500]\n" source = "runtime/collections/sort0.kt" diff --git a/backend.native/tests/runtime/collections/array3.kt b/backend.native/tests/runtime/collections/array3.kt new file mode 100644 index 00000000000..8c94e9b8ef8 --- /dev/null +++ b/backend.native/tests/runtime/collections/array3.kt @@ -0,0 +1,13 @@ +import konan.* + +fun main(args : Array) { + val data = immutableBinaryBlobOf(0x1, 0x2, 0x3, 0x7, 0x8, 0x9, 0x80, 0xff) + for (b in data) { + print("$b ") + } + println() + + val dataClone = data.toByteArray() + dataClone.map { print("$it ") } + println() +} \ No newline at end of file diff --git a/runtime/src/main/cpp/Arrays.cpp b/runtime/src/main/cpp/Arrays.cpp index 419c67e45e4..913e573ea44 100644 --- a/runtime/src/main/cpp/Arrays.cpp +++ b/runtime/src/main/cpp/Arrays.cpp @@ -329,4 +329,25 @@ KInt Kotlin_BooleanArray_getArrayLength(KConstRef thiz) { return array->count_; } +OBJ_GETTER(Kotlin_ImmutableBinaryBlob_toByteArray, KConstRef thiz, KInt start, KInt count) { + const ArrayHeader* array = thiz->array(); + if (start < 0 || count < 0 || start > array->count_ - count) { + ThrowArrayIndexOutOfBoundsException(); + } + ArrayHeader* result = AllocArrayInstance( + theByteArrayTypeInfo, count, OBJ_RESULT)->array(); + memcpy(PrimitiveArrayAddressOfElementAt(result, 0), + PrimitiveArrayAddressOfElementAt(array, start), + count); + RETURN_OBJ(result->obj()); +} + +KNativePtr Kotlin_ImmutableBinaryBlob_asCPointerImpl(KRef thiz, KInt offset) { + ArrayHeader* array = thiz->array(); + if (offset < 0 || offset > array->count_) { + ThrowArrayIndexOutOfBoundsException(); + } + return PrimitiveArrayAddressOfElementAt(array, offset); +} + } // extern "C" diff --git a/runtime/src/main/kotlin/konan/BinaryBlob.kt b/runtime/src/main/kotlin/konan/BinaryBlob.kt new file mode 100644 index 00000000000..b07669038db --- /dev/null +++ b/runtime/src/main/kotlin/konan/BinaryBlob.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2017 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. + */ +package konan + +import konan.internal.* +import kotlinx.cinterop.* + +/** + * An immutable compile-time array of bytes. + */ +@ExportTypeInfo("theImmutableBinaryBlobTypeInfo") +public final class ImmutableBinaryBlob private constructor() { + public val size: Int + get() = getArrayLength() + + // Data layout is the same as for ByteArray, so we can share native functions. + @SymbolName("Kotlin_ByteArray_get") + external public operator fun get(index: Int): Byte + + @SymbolName("Kotlin_ByteArray_getArrayLength") + external private fun getArrayLength(): Int + + /** Creates an iterator over the elements of the array. */ + public operator fun iterator(): ByteIterator { + return ImmutableBinaryBlobIteratorImpl(this) + } +} + +private class ImmutableBinaryBlobIteratorImpl( + val collection: ImmutableBinaryBlob) : ByteIterator() { + var index : Int = 0 + + public override fun nextByte(): Byte { + if (!hasNext()) throw NoSuchElementException("$index") + return collection[index++] + } + + public override operator fun hasNext(): Boolean { + return index < collection.size + } +} + +// Allocates new ByteArray and copies the data. +@SymbolName("Kotlin_ImmutableBinaryBlob_toByteArray") +public external fun ImmutableBinaryBlob.toByteArray(start: Int, count: Int): ByteArray +public fun ImmutableBinaryBlob.toByteArray() = toByteArray(0, size) + +// Returns stable C pointer to data at certain offset, useful as a way to pass resource +// to C API. +public external fun ImmutableBinaryBlob.asCPointer(offset: Int) = + interpretCPointer(asCPointerImpl(offset)) +@SymbolName("Kotlin_ImmutableBinaryBlob_asCPointerImpl") +private external fun ImmutableBinaryBlob.asCPointerImpl(offset: Int): konan.internal.NativePtr + +// Creates ImmutableBinaryBlob out of compile-time constant data. +// This method accepts Short type, so that values in range 0x80 .. 0xff can be +// provided without toByte() cast. One element still represent one byte in the output data. +// This is the only way to create ImmutableBinaryBlob for now. +// TODO: reconsider? +@Intrinsic +public external fun immutableBinaryBlobOf(vararg elements: Short): ImmutableBinaryBlob