From 277835fccb1aff02e8f215c63ba63bd34ffe8eaa Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 29 Sep 2016 17:31:03 +0700 Subject: [PATCH] backend.native: add some dirty code to emit bitcode with RTTI for fields --- backend.native/build.gradle | 1 + .../org/jetbrains/kotlin/cli/bc/K2Native.kt | 2 + .../kotlin/backend/native/llvm/IrToBitcode.kt | 13 ++ .../backend/native/llvm/RTTIGenerator.kt | 168 ++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/IrToBitcode.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/RTTIGenerator.kt diff --git a/backend.native/build.gradle b/backend.native/build.gradle index a8567d74143..d8cd6905166 100644 --- a/backend.native/build.gradle +++ b/backend.native/build.gradle @@ -65,6 +65,7 @@ task run(type: JavaExec) { main 'org.jetbrains.kotlin.cli.bc.K2NativeKt' classpath = sourceSets.main.runtimeClasspath.add(files(['build/classes/main'])) args '../experiments/translator/src/test/kotlin/tests/classfields_1/classfields_1.kt' + args '-output', "$buildDir/out.bc" dependsOn ':runtime:build' diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index e51afe39df8..b8d7a5efb11 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlin.cli.bc import com.intellij.openapi.Disposable import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.backend.native.llvm.emitLLVM import org.jetbrains.kotlin.cli.common.CLICompiler import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.ExitCode @@ -59,6 +60,7 @@ class K2Native : CLICompiler() { analyzerWithCompilerReport.analysisResult.bindingContext) module.accept(DumpIrTreeVisitor(out), "") + emitLLVM(module, arguments.runtimeFile, arguments.outputFile) return ExitCode.OK } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/IrToBitcode.kt new file mode 100644 index 00000000000..cc713b6d847 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/IrToBitcode.kt @@ -0,0 +1,13 @@ +package org.jetbrains.kotlin.backend.native.llvm + +import llvm.* +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment + +fun emitLLVM(module: IrModuleFragment, runtimeFile: String, outFile: String) { + val llvmModule = LLVMModuleCreateWithName("out")!! + val runtime = Runtime(runtimeFile) + LLVMSetDataLayout(llvmModule, runtime.dataLayout) + LLVMSetTarget(llvmModule, runtime.target) + module.accept(RTTIGenerator(llvmModule, runtime), null) + LLVMWriteBitcodeToFile(llvmModule, outFile) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/RTTIGenerator.kt new file mode 100644 index 00000000000..fa99c1ccca1 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/native/llvm/RTTIGenerator.kt @@ -0,0 +1,168 @@ +package org.jetbrains.kotlin.backend.native.llvm + + +import kotlin_native.interop.* +import llvm.* +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrField +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny +import java.util.zip.CRC32 + +private fun crc32(str: String): Long { + val c = CRC32() + c.update(str.toByteArray()) + return c.value +} + +private fun getLLVMType(field: IrField): LLVMOpaqueType { + val type = field.descriptor.returnType!! + return when { + KotlinBuiltIns.isBoolean(type) || KotlinBuiltIns.isByte(type) -> LLVMInt8Type() + KotlinBuiltIns.isShort(type) || KotlinBuiltIns.isChar(type) -> LLVMInt16Type() + KotlinBuiltIns.isInt(type) -> LLVMInt32Type() + KotlinBuiltIns.isLong(type) -> LLVMInt64Type() + else -> throw NotImplementedError() + }!! +} + +class RTTIGenerator(val module: LLVMOpaqueModule, val runtime: Runtime): IrElementVisitorVoid { + + /** + * Represents the value which can be emitted as bitcode const value + */ + private inner abstract class CompileTimeValue { + + abstract fun getLlvmValue(): LLVMOpaqueValue? + + fun getLlvmType(): LLVMOpaqueType? { + return LLVMTypeOf(getLlvmValue()) + } + + } + + private inner class ConstArray(val elemType: LLVMOpaqueType?, val elements: List) : CompileTimeValue() { + + constructor(type: LLVMOpaqueType?, vararg elements: CompileTimeValue) : this(type, elements.toList()) + + override fun getLlvmValue(): LLVMOpaqueValue? { + val values = elements.map { it.getLlvmValue() }.toTypedArray() + val valuesNativeArrayPtr = arena.allocNativeArrayOf(LLVMOpaqueValue, *values)[0] + + return LLVMConstArray(elemType, valuesNativeArrayPtr, values.size) + } + } + + private inner open class Struct(val type: LLVMOpaqueType?, val elements: List) : CompileTimeValue() { + + constructor(type: LLVMOpaqueType?, vararg elements: CompileTimeValue) : this(type, elements.toList()) + + override fun getLlvmValue(): LLVMOpaqueValue? { + val values = elements.map { it.getLlvmValue() }.toTypedArray() + val valuesNativeArrayPtr = arena.allocNativeArrayOf(LLVMOpaqueValue, *values)[0] + return LLVMConstNamedStruct(type, valuesNativeArrayPtr, values.size) + } + } + + private inner class Int32(val value: Int) : CompileTimeValue() { + override fun getLlvmValue() = LLVMConstInt(LLVMInt32Type(), value.toLong(), 1) + } + + private inner class Int64(val value: Long) : CompileTimeValue() { + override fun getLlvmValue() = LLVMConstInt(LLVMInt64Type(), value, 1) + } + + private inner class Zero(val type: LLVMOpaqueType?) : CompileTimeValue() { + override fun getLlvmValue() = LLVMConstNull(type) + } + + private fun compileTimeValue(value: LLVMOpaqueValue?) = object : CompileTimeValue() { + override fun getLlvmValue() = value + } + + private val int32Type = LLVMInt32Type() + + private fun pointer(type: LLVMOpaqueType?) = LLVMPointerType(type, 0) // TODO: why 0? + + + private inner class FieldTableRecord(val nameSignature: Long, val fieldOffset: Int) : + Struct(runtime.fieldTableRecordType, Int64(nameSignature), Int32(fieldOffset)) + + private inner class TypeInfo(val name: Long, val size: Int, val superType: CompileTimeValue, val fields: CompileTimeValue, + val fieldsCount: Int) : + Struct( + runtime.typeInfoType, + Int64(name), + Int32(size), + + superType, + + Zero(pointer(int32Type)), + Int32(0), + + Zero(pointer(pointer(runtime.typeInfoType))), + Int32(0), + + Zero(pointer(runtime.methodTableRecordType)), + Int32(0), + + fields, + Int32(fieldsCount) + ) + + private val builder = LLVMCreateBuilder() // TODO: dispose + private val arena = Arena() // TODO: dispose + + private fun addGlobalConst(name: String, value: CompileTimeValue): LLVMOpaqueValue? { + val global = LLVMAddGlobal(module, value.getLlvmType(), name) + LLVMSetInitializer(global, value.getLlvmValue()) + return global + } + + private fun getGlobalArrayPtr(array: LLVMOpaqueValue?): LLVMOpaqueValue? { + val indices = longArrayOf(0, 0).map { LLVMConstInt(LLVMInt32Type(), it, 0) }.toTypedArray() + val indicesNativeArrayPtr = arena.allocNativeArrayOf(LLVMOpaqueValue, *indices)[0] + + return LLVMBuildGEP(builder, array, indicesNativeArrayPtr, indices.size, "") + } + + private fun typeInfoFor(classDesc: ClassDescriptor): LLVMOpaqueValue? { + val globalName = classDesc.name.toString() + "_type" // FIXME: FQDN + return LLVMGetNamedGlobal(module, globalName) ?: LLVMAddGlobal(module, runtime.typeInfoType, globalName) + } + + override fun visitElement(element: IrElement) { + element.acceptChildren(this, null) + } + + override fun visitClass(declaration: IrClass) { + val className = declaration.descriptor.name // FIXME: FQDN + val fields = declaration.declarations.filterIsInstance().mapNotNull { it.backingField } + val fieldTypes = fields.map { getLLVMType(it) }.toTypedArray() + val classType = LLVMStructCreateNamed(LLVMGetModuleContext(module), "class." + className) + LLVMStructSetBody(classType, mallocNativeArrayOf(LLVMOpaqueType, *fieldTypes)[0], fieldTypes.size, 0) + + val name = crc32(className.toString()) + val size = LLVMStoreSizeOfType(runtime.targetData, classType).toInt() + val superType = compileTimeValue(typeInfoFor(declaration.descriptor.getSuperClassOrAny())) + + val fieldTableRecords = fields.mapIndexed { index, field -> + val nameSignature = crc32(field.descriptor.name.toString()) // FIXME: add signature + val fieldOffset = LLVMOffsetOfElement(runtime.targetData, classType, index) + FieldTableRecord(nameSignature, fieldOffset.toInt()) + }.sortedBy { it.nameSignature } + + val fieldsArray = ConstArray(runtime.fieldTableRecordType, fieldTableRecords) + val fieldsGlobal = addGlobalConst("${className}_fields", fieldsArray) + val fieldsPtr = compileTimeValue(getGlobalArrayPtr(fieldsGlobal)) + + val typeInfo = TypeInfo(name, size, superType, fieldsPtr, fieldTableRecords.size) + val typeInfoGlobal = typeInfoFor(declaration.descriptor) + LLVMSetInitializer(typeInfoGlobal, typeInfo.getLlvmValue()) + } + +} \ No newline at end of file