From 2f9bde44821475e6ea6d079ca8e0e9351042593a Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Fri, 3 Feb 2017 19:41:34 +0500 Subject: [PATCH 01/13] Local objects (#215) * bug fix * bug fix with nested inner classes * - fixed FixMeInner - support of inner classes in local classes - support of object expressions in initializers * comments * merged modification * - review fixes - tests * review fixes * review fixes --- .../AbstractClosureAnnotator.kt} | 71 +++++----- .../common/lower/LocalDeclarationsLowering.kt | 125 ++++++++++++------ .../kotlin/backend/konan/KonanLower.kt | 8 +- .../backend/konan/lower/InnerClassLowering.kt | 4 +- backend.native/tests/build.gradle | 35 +++++ .../tests/codegen/innerClass/doubleInner.kt | 21 +++ .../tests/codegen/innerClass/superOuter.kt | 13 ++ .../localClass/innerTakesCapturedFromOuter.kt | 18 +++ .../codegen/localClass/innerWithCapture.kt | 13 ++ .../codegen/localClass/localHierarchy.kt | 15 +++ .../objectExpressionInInitializer.kt | 25 ++++ .../localClass/objectExpressionInProperty.kt | 21 +++ .../superConstructorCall/innerExtendsOuter.kt | 2 +- runtime/src/main/kotlin/konan/Annotations.kt | 7 - .../kotlin/kotlin/collections/AbstractList.kt | 16 +-- .../kotlin/collections/ReversedViews.kt | 8 +- 16 files changed, 299 insertions(+), 103 deletions(-) rename backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/{AbstractClosureRecorder.kt => lower/AbstractClosureAnnotator.kt} (57%) create mode 100644 backend.native/tests/codegen/innerClass/doubleInner.kt create mode 100644 backend.native/tests/codegen/innerClass/superOuter.kt create mode 100644 backend.native/tests/codegen/localClass/innerTakesCapturedFromOuter.kt create mode 100644 backend.native/tests/codegen/localClass/innerWithCapture.kt create mode 100644 backend.native/tests/codegen/localClass/localHierarchy.kt create mode 100644 backend.native/tests/codegen/localClass/objectExpressionInInitializer.kt create mode 100644 backend.native/tests/codegen/localClass/objectExpressionInProperty.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractClosureRecorder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/AbstractClosureAnnotator.kt similarity index 57% rename from backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractClosureRecorder.kt rename to backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/AbstractClosureAnnotator.kt index 7ba8096e676..251b1746b4d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/AbstractClosureRecorder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/AbstractClosureAnnotator.kt @@ -1,38 +1,22 @@ -/* - * Copyright 2010-2016 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 org.jetbrains.kotlin.backend.common +package org.jetbrains.kotlin.backend.common.lower import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.IrElement -import org.jetbrains.kotlin.ir.declarations.IrClass -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrLocalDelegatedProperty -import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrValueAccessExpression import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.resolve.DescriptorUtils -import java.util.* -abstract class AbstractClosureRecorder : IrElementVisitorVoid { +// TODO: synchronize with JVM BE +class Closure(val capturedValues: List) + +abstract class AbstractClosureAnnotator : IrElementVisitorVoid { protected abstract fun recordFunctionClosure(functionDescriptor: FunctionDescriptor, closure: Closure) protected abstract fun recordClassClosure(classDescriptor: ClassDescriptor, closure: Closure) - private class ClosureBuilder(val owner: DeclarationDescriptor) { + private abstract class ClosureBuilder(open val owner: DeclarationDescriptor) { val capturedValues = mutableSetOf() fun buildClosure() = Closure(capturedValues.toList()) @@ -43,12 +27,39 @@ abstract class AbstractClosureRecorder : IrElementVisitorVoid { private fun fillInNestedClosure(destination: MutableSet, nested: List) { nested.filterTo(destination) { - it.containingDeclaration != owner + isExternal(it) } } + + abstract fun isExternal(valueDescriptor: T): Boolean } - private val closuresStack = ArrayDeque() + private class FunctionClosureBuilder(override val owner: FunctionDescriptor) : ClosureBuilder(owner) { + + override fun isExternal(valueDescriptor: T): Boolean = + valueDescriptor.containingDeclaration != owner && valueDescriptor != owner.dispatchReceiverParameter + } + + private class ClassClosureBuilder(override val owner: ClassDescriptor) : ClosureBuilder(owner) { + + override fun isExternal(valueDescriptor: T): Boolean { + // TODO: replace with 'return valueDescriptor.containingDeclaration != owner' after constructors lowering. + var declaration: DeclarationDescriptor? = valueDescriptor.containingDeclaration + while (declaration != null && declaration != owner) { + declaration = declaration.containingDeclaration + } + return declaration != owner + } + + } + + private val closuresStack = mutableListOf() + + private fun MutableList.push(element: E) = this.add(element) + + private fun MutableList.pop() = this.removeAt(size - 1) + + private fun MutableList.peek(): E? = if (size == 0) null else this[size - 1] override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) @@ -56,7 +67,7 @@ abstract class AbstractClosureRecorder : IrElementVisitorVoid { override fun visitClass(declaration: IrClass) { val classDescriptor = declaration.descriptor - val closureBuilder = ClosureBuilder(classDescriptor) + val closureBuilder = ClassClosureBuilder(classDescriptor) closuresStack.push(closureBuilder) declaration.acceptChildrenVoid(this) @@ -73,7 +84,7 @@ abstract class AbstractClosureRecorder : IrElementVisitorVoid { override fun visitFunction(declaration: IrFunction) { val functionDescriptor = declaration.descriptor - val closureBuilder = ClosureBuilder(functionDescriptor) + val closureBuilder = FunctionClosureBuilder(functionDescriptor) closuresStack.push(closureBuilder) declaration.acceptChildrenVoid(this) @@ -98,7 +109,7 @@ abstract class AbstractClosureRecorder : IrElementVisitorVoid { if (closureBuilder != null) { val variableDescriptor = expression.descriptor - if (variableDescriptor.containingDeclaration != closureBuilder.owner) { + if (closureBuilder.isExternal(variableDescriptor)) { closureBuilder.capturedValues.add(variableDescriptor) } } @@ -106,4 +117,4 @@ abstract class AbstractClosureRecorder : IrElementVisitorVoid { expression.acceptChildrenVoid(this) } -} +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt index 9c9ff1d29a9..a089e6b5fc2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/lower/LocalDeclarationsLowering.kt @@ -16,28 +16,32 @@ package org.jetbrains.kotlin.backend.common.lower -import org.jetbrains.kotlin.backend.common.AbstractClosureRecorder import org.jetbrains.kotlin.backend.common.BackendContext -import org.jetbrains.kotlin.backend.common.Closure import org.jetbrains.kotlin.backend.common.DeclarationContainerLoweringPass import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations -import org.jetbrains.kotlin.descriptors.impl.* +import org.jetbrains.kotlin.descriptors.impl.ClassConstructorDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.* -import org.jetbrains.kotlin.ir.declarations.impl.* +import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFieldImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.util.transformFlat import org.jetbrains.kotlin.ir.visitors.* import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny import org.jetbrains.kotlin.resolve.descriptorUtil.parents import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf import org.jetbrains.kotlin.types.KotlinType import java.util.* -class LocalDeclarationsLowering(val context: BackendContext): DeclarationContainerLoweringPass { +class LocalDeclarationsLowering(val context: BackendContext) : DeclarationContainerLoweringPass { override fun lower(irDeclarationContainer: IrDeclarationContainer) { if (irDeclarationContainer is IrDeclaration && irDeclarationContainer.descriptor.parents.any { it is CallableDescriptor }) { @@ -51,10 +55,13 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain lambdasCount = 0 irDeclarationContainer.declarations.transformFlat { memberDeclaration -> - if (memberDeclaration is IrFunction) - LocalDeclarationsTransformer(memberDeclaration).lowerLocalDeclarations() - else - null + // TODO: may be do the opposite - specify the list of IR elements which need not to be transformed + when (memberDeclaration) { + is IrFunction -> LocalDeclarationsTransformer(memberDeclaration).lowerLocalDeclarations() + is IrProperty -> LocalDeclarationsTransformer(memberDeclaration).lowerLocalDeclarations() + is IrAnonymousInitializer -> LocalDeclarationsTransformer(memberDeclaration).lowerLocalDeclarations() + else -> null + } } } @@ -117,7 +124,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain val fieldDescriptor = capturedValueToField[descriptor] ?: return null return IrGetFieldImpl(startOffset, endOffset, fieldDescriptor, - receiver = IrGetValueImpl(startOffset, endOffset, this.descriptor.thisAsReceiverParameter) + receiver = IrGetValueImpl(startOffset, endOffset, this.descriptor.thisAsReceiverParameter) ) } @@ -125,7 +132,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain "LocalClassContext for ${descriptor}" } - private inner class LocalDeclarationsTransformer(val memberFunction: IrFunction) { + private inner class LocalDeclarationsTransformer(val memberDeclaration: IrDeclaration) { val localFunctions: MutableMap = LinkedHashMap() val localClasses: MutableMap = LinkedHashMap() val localClassConstructors: MutableMap = LinkedHashMap() @@ -155,7 +162,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain private fun collectRewrittenDeclarations(): ArrayList = ArrayList(localFunctions.size + localClasses.size + 1).apply { - add(memberFunction) + add(memberDeclaration) localFunctions.values.mapTo(this) { val original = it.declaration @@ -174,8 +181,12 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain private inner class FunctionBodiesRewriter(val localContext: LocalContext?) : IrElementTransformerVoid() { override fun visitClass(declaration: IrClass): IrStatement { - // Replace local class definition with an empty composite. - return IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.builtIns.unitType) + if (declaration.descriptor in localClasses) { + // Replace local class definition with an empty composite. + return IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.builtIns.unitType) + } else { + return super.visitClass(declaration) + } } override fun visitFunction(declaration: IrFunction): IrStatement { @@ -183,18 +194,20 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain // Replace local function definition with an empty composite. return IrCompositeImpl(declaration.startOffset, declaration.endOffset, context.builtIns.unitType) } else { - declaration.transformChildrenVoid(this) - return declaration + return super.visitFunction(declaration) } } override fun visitConstructor(declaration: IrConstructor): IrStatement { // Body is transformed separately. - val transformedDescriptor = localClassConstructors[declaration.descriptor]!!.transformedDescriptor - - return IrConstructorImpl(declaration.startOffset, declaration.endOffset, declaration.origin, - transformedDescriptor, declaration.body!!) + val transformedDescriptor = localClassConstructors[declaration.descriptor]?.transformedDescriptor + if (transformedDescriptor != null) { + return IrConstructorImpl(declaration.startOffset, declaration.endOffset, declaration.origin, + transformedDescriptor, declaration.body!!) + } else { + return super.visitConstructor(declaration) + } } override fun visitGetValue(expression: IrGetValue): IrExpression { @@ -222,6 +235,21 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain return newCall } + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { + expression.transformChildrenVoid(this) + + val oldCallee = expression.descriptor.original + val newCallee = transformedDescriptors[oldCallee] as ClassConstructorDescriptor? ?: return expression + + val newExpression = IrDelegatingConstructorCallImpl( + expression.startOffset, expression.endOffset, + newCallee, + remapTypeArguments(expression, newCallee) + ).fillArguments(expression) + + return newExpression + } + private fun T.fillArguments(oldExpression: IrMemberAccessExpression): T { mapValueParameters { newValueParameterDescriptor -> @@ -233,7 +261,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain // The callee expects captured value as argument. val capturedValueDescriptor = newParameterToCaptured[newValueParameterDescriptor] ?: - throw AssertionError("Non-mapped parameter $newValueParameterDescriptor") + throw AssertionError("Non-mapped parameter $newValueParameterDescriptor") localContext?.irGet( oldExpression.startOffset, oldExpression.endOffset, @@ -292,8 +320,8 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain } } - private fun rewriteFunctionBody(irFunction: IrFunction, localContext: LocalContext?) { - irFunction.transformChildrenVoid(FunctionBodiesRewriter(localContext)) + private fun rewriteFunctionBody(irDeclaration: IrDeclaration, localContext: LocalContext?) { + irDeclaration.transformChildrenVoid(FunctionBodiesRewriter(localContext)) } private object DECLARATION_ORIGIN_FIELD_FOR_CAPTURED_VALUE : @@ -339,7 +367,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain rewriteClassMembers(it.declaration, it) } - rewriteFunctionBody(memberFunction, null) + rewriteFunctionBody(memberDeclaration, null) } private fun createNewCall(oldCall: IrCall, newCallee: CallableDescriptor) = @@ -395,12 +423,12 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain .toList().reversed() .map { suggestLocalName(it) } .joinToString(separator = "$") - ) + ) private fun createLiftedDescriptor(localFunctionContext: LocalFunctionContext) { val oldDescriptor = localFunctionContext.descriptor - val memberOwner = memberFunction.descriptor.containingDeclaration + val memberOwner = memberDeclaration.descriptor.containingDeclaration!! val newDescriptor = SimpleFunctionDescriptorImpl.create( memberOwner, oldDescriptor.annotations, @@ -480,7 +508,15 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain // Do not substitute type parameters for now. val newTypeParameters = oldDescriptor.typeParameters - val capturedValues = localClassContext.closure.capturedValues + val capturedValues = mutableListOf() + var classDescriptor = oldDescriptor.containingDeclaration + while (true) { + // Capture all values from the hierarchy since we need to call constructor of super class + // with his captured values. + val context = localClasses[classDescriptor] ?: break + capturedValues.addAll(context.closure.capturedValues) + classDescriptor = classDescriptor.getSuperClassOrAny() + } val newValueParameters = createTransformedValueParameters(localFunctionContext, capturedValues) @@ -560,8 +596,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain if (valueDescriptor.name.isSpecial) { val oldNameStr = valueDescriptor.name.asString() Name.identifier("$" + oldNameStr.substring(1, oldNameStr.length - 1)) - } - else + } else valueDescriptor.name private fun createUnsubstitutedCapturedValueParameter( @@ -586,7 +621,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain private fun collectClosures() { - memberFunction.acceptChildrenVoid(object : AbstractClosureRecorder() { + memberDeclaration.acceptChildrenVoid(object : AbstractClosureAnnotator() { override fun recordFunctionClosure(functionDescriptor: FunctionDescriptor, closure: Closure) { localFunctions[functionDescriptor]?.closure = closure } @@ -598,16 +633,17 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain } private fun collectLocalDeclarations() { - memberFunction.acceptChildrenVoid(object : IrElementVisitorVoid { + memberDeclaration.acceptChildrenVoid(object : IrElementVisitorVoid { override fun visitElement(element: IrElement) { element.acceptChildrenVoid(this) } - private fun DeclarationDescriptor.isClassMember() = when (this.containingDeclaration) { - is CallableDescriptor -> false - is ClassDescriptor -> true - else -> TODO(this.toString()) + private fun DeclarationDescriptor.declaredInFunction() = when (this.containingDeclaration) { + is CallableDescriptor -> true + is ClassDescriptor -> false + is PackageFragmentDescriptor -> false + else -> TODO(this.toString() + "\n" + this.containingDeclaration.toString()) } override fun visitFunction(declaration: IrFunction) { @@ -615,7 +651,7 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain val descriptor = declaration.descriptor - if (!descriptor.isClassMember()) { + if (descriptor.declaredInFunction()) { val localFunctionContext = LocalFunctionContext(declaration) localFunctions[descriptor] = localFunctionContext @@ -631,7 +667,9 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain declaration.acceptChildrenVoid(this) val descriptor = declaration.descriptor - assert (descriptor.isClassMember()) + assert(!descriptor.declaredInFunction()) + + if (descriptor.constructedClass.isInner) return localClassConstructors[descriptor] = LocalClassConstructorContext(declaration) } @@ -641,12 +679,13 @@ class LocalDeclarationsLowering(val context: BackendContext): DeclarationContain val descriptor = declaration.descriptor - if (descriptor.isClassMember()) { - assert (descriptor.isInner) - } else { - val localClassContext = LocalClassContext(declaration) - localClasses[descriptor] = localClassContext - } + if (descriptor.isInner) return + + // Local nested classes can only be inner. + assert(descriptor.declaredInFunction()) + + val localClassContext = LocalClassContext(declaration) + localClasses[descriptor] = localClassContext } }) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index e714e9ed2de..922283287cb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -20,11 +20,6 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_ENUMS) { EnumClassLowering(context).run(irFile) } - - phaser.phase(KonanPhase.LOWER_INNER_CLASSES) { - InnerClassLowering(context).runOnFilePostfix(irFile) - } - phaser.phase(KonanPhase.LOWER_VARARG) { VarargInjectionLowering(context).runOnFilePostfix(irFile) } @@ -44,6 +39,9 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_LOCAL_FUNCTIONS) { LocalDeclarationsLowering(context).runOnFilePostfix(irFile) } + phaser.phase(KonanPhase.LOWER_INNER_CLASSES) { + InnerClassLowering(context).runOnFilePostfix(irFile) + } phaser.phase(KonanPhase.LOWER_CALLABLES) { CallableReferenceLowering(context).runOnFilePostfix(irFile) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InnerClassLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InnerClassLowering.kt index fad21920b5f..7163609e1c4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InnerClassLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InnerClassLowering.kt @@ -69,7 +69,7 @@ internal class InnerClassLowering(val context: Context) : ClassLoweringPass { if (instanceInitializerIndex >= 0) { // Initializing constructor: initialize 'this.this$0' with '$outer'. blockBody.statements.add( - instanceInitializerIndex, + 0, IrSetFieldImpl( startOffset, endOffset, outerThisFieldDescriptor, IrGetValueImpl(startOffset, endOffset, classDescriptor.thisAsReceiverParameter), @@ -117,7 +117,7 @@ internal class InnerClassLowering(val context: Context) : ClassLoweringPass { val outerThisField = context.specialDescriptorsFactory.getOuterThisFieldDescriptor(innerClass) irThis = IrGetFieldImpl(startOffset, endOffset, outerThisField, irThis, origin) - val outer = classDescriptor.containingDeclaration + val outer = innerClass.containingDeclaration innerClass = outer as? ClassDescriptor ?: throw AssertionError("Unexpected containing declaration for inner class $innerClass: $outer") } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 1d4794486dc..2a8091bfb5e 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -357,11 +357,46 @@ task innerClass_generic(type: RunKonanTest) { source = "codegen/innerClass/generic.kt" } +task innerClass_doubleInner(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/innerClass/doubleInner.kt" +} + task innerClass_qualifiedThis(type: RunKonanTest) { goldValue = "OK\n" source = "codegen/innerClass/qualifiedThis.kt" } +task innerClass_superOuter(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/innerClass/superOuter.kt" +} + +task localClass_localHierarchy(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/localClass/localHierarchy.kt" +} + +task localClass_objectExpressionInProperty(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/localClass/objectExpressionInProperty.kt" +} + +task localClass_objectExpressionInInitializer(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/localClass/objectExpressionInInitializer.kt" +} + +task localClass_innerWithCapture(type: RunKonanTest) { + goldValue = "OK\n" + source = "codegen/localClass/innerWithCapture.kt" +} + +task localClass_innerTakesCapturedFromOuter(type: RunKonanTest) { + goldValue = "0\n1\n" + source = "codegen/localClass/innerTakesCapturedFromOuter.kt" +} + task array0(type: RunKonanTest) { goldValue = "5\n6\n7\n8\n9\n10\n11\n12\n13\n" source = "runtime/collections/array0.kt" diff --git a/backend.native/tests/codegen/innerClass/doubleInner.kt b/backend.native/tests/codegen/innerClass/doubleInner.kt new file mode 100644 index 00000000000..449b3a2e093 --- /dev/null +++ b/backend.native/tests/codegen/innerClass/doubleInner.kt @@ -0,0 +1,21 @@ +open class Father(val param: String) { + abstract inner class InClass { + fun work(): String { + return param + } + } + + inner class Child(p: String) : Father(p) { + inner class Child2 : Father.InClass { + constructor(): super() + } + } +} + +fun box(): String { + return Father("fail").Child("OK").Child2().work() +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/innerClass/superOuter.kt b/backend.native/tests/codegen/innerClass/superOuter.kt new file mode 100644 index 00000000000..4d92568ee23 --- /dev/null +++ b/backend.native/tests/codegen/innerClass/superOuter.kt @@ -0,0 +1,13 @@ +open class Outer(val outer: String) { + open inner class Inner(val inner: String): Outer(inner) { + fun foo() = outer + } + + fun value() = Inner("OK").foo() +} + +fun box() = Outer("Fail").value() + +fun main(args : Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/localClass/innerTakesCapturedFromOuter.kt b/backend.native/tests/codegen/localClass/innerTakesCapturedFromOuter.kt new file mode 100644 index 00000000000..464c3193a41 --- /dev/null +++ b/backend.native/tests/codegen/localClass/innerTakesCapturedFromOuter.kt @@ -0,0 +1,18 @@ +fun box() { + var previous: Any? = null + for (i in 0 .. 2) { + class Outer { + inner class Inner { + override fun toString() = i.toString() + } + + override fun toString() = Inner().toString() + } + if (previous != null) println(previous.toString()) + previous = Outer() + } +} + +fun main(args: Array) { + box() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/localClass/innerWithCapture.kt b/backend.native/tests/codegen/localClass/innerWithCapture.kt new file mode 100644 index 00000000000..b030793a34a --- /dev/null +++ b/backend.native/tests/codegen/localClass/innerWithCapture.kt @@ -0,0 +1,13 @@ +fun box(s: String): String { + class Local { + open inner class Inner() { + open fun result() = s + } + } + + return Local().Inner().result() +} + +fun main(args : Array) { + println(box("OK")) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/localClass/localHierarchy.kt b/backend.native/tests/codegen/localClass/localHierarchy.kt new file mode 100644 index 00000000000..6f77ff78e25 --- /dev/null +++ b/backend.native/tests/codegen/localClass/localHierarchy.kt @@ -0,0 +1,15 @@ +fun foo(s: String): String { + open class Local { + fun f() = s + } + + open class Derived: Local() { + fun g() = f() + } + + return Derived().g() +} + +fun main(args: Array) { + println(foo("OK")) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/localClass/objectExpressionInInitializer.kt b/backend.native/tests/codegen/localClass/objectExpressionInInitializer.kt new file mode 100644 index 00000000000..653df8e98e7 --- /dev/null +++ b/backend.native/tests/codegen/localClass/objectExpressionInInitializer.kt @@ -0,0 +1,25 @@ +abstract class Father { + abstract inner class InClass { + abstract fun work(): String + } +} + +class Child : Father() { + val ChildInClass : InClass + + init { + ChildInClass = object : Father.InClass() { + override fun work(): String { + return "OK" + } + } + } +} + +fun box(): String { + return Child().ChildInClass.work() +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/localClass/objectExpressionInProperty.kt b/backend.native/tests/codegen/localClass/objectExpressionInProperty.kt new file mode 100644 index 00000000000..3683f7b0dd2 --- /dev/null +++ b/backend.native/tests/codegen/localClass/objectExpressionInProperty.kt @@ -0,0 +1,21 @@ +abstract class Father { + abstract inner class InClass { + abstract fun work(): String + } +} + +class Child : Father() { + val ChildInClass = object : Father.InClass() { + override fun work(): String { + return "OK" + } + } +} + +fun box(): String { + return Child().ChildInClass.work() +} + +fun main(args: Array) { + println(box()) +} \ No newline at end of file diff --git a/backend.native/tests/external/codegen/blackbox/innerNested/superConstructorCall/innerExtendsOuter.kt b/backend.native/tests/external/codegen/blackbox/innerNested/superConstructorCall/innerExtendsOuter.kt index 8db3bfa10ca..b368743c9d9 100644 --- a/backend.native/tests/external/codegen/blackbox/innerNested/superConstructorCall/innerExtendsOuter.kt +++ b/backend.native/tests/external/codegen/blackbox/innerNested/superConstructorCall/innerExtendsOuter.kt @@ -6,7 +6,7 @@ open class Outer(vararg val chars: Char) { open inner class Inner(val s: String): Outer(s[0], s[1]) { - fun concat() = java.lang.String.valueOf(chars) + fun concat() = fromCharArrays(chars, 0, chars.size) } fun value() = Inner("OK").concat() diff --git a/runtime/src/main/kotlin/konan/Annotations.kt b/runtime/src/main/kotlin/konan/Annotations.kt index c83a8807deb..c62c7c5649a 100644 --- a/runtime/src/main/kotlin/konan/Annotations.kt +++ b/runtime/src/main/kotlin/konan/Annotations.kt @@ -24,13 +24,6 @@ annotation class ExportTypeInfo(val name: String) public annotation class Used -// Following annotations can be used to mark functions that need to be fixed, -// once certain language feature is implemented. -/** - * Need to be fixed because of inner classes. - */ -public annotation class FixmeInner - /** * Need to be fixed because of reification support. */ diff --git a/runtime/src/main/kotlin/kotlin/collections/AbstractList.kt b/runtime/src/main/kotlin/kotlin/collections/AbstractList.kt index a841f7b174d..3268818e609 100644 --- a/runtime/src/main/kotlin/kotlin/collections/AbstractList.kt +++ b/runtime/src/main/kotlin/kotlin/collections/AbstractList.kt @@ -8,20 +8,15 @@ public abstract class AbstractList protected constructor() : AbstractColl abstract override val size: Int abstract override fun get(index: Int): E - // TODO: fix once have inner classes. - @FixmeInner - override fun iterator(): Iterator = TODO() // IteratorImpl() + override fun iterator(): Iterator = IteratorImpl() override fun indexOf(element: @UnsafeVariance E): Int = indexOfFirst { it == element } override fun lastIndexOf(element: @UnsafeVariance E): Int = indexOfLast { it == element } - // TODO: fix once have inner classes. - @FixmeInner - override fun listIterator(): ListIterator = TODO() // ListIteratorImpl(0) + override fun listIterator(): ListIterator = ListIteratorImpl(0) - @FixmeInner - override fun listIterator(index: Int): ListIterator = TODO() // ListIteratorImpl(index) + override fun listIterator(index: Int): ListIterator = ListIteratorImpl(index) override fun subList(fromIndex: Int, toIndex: Int): List = SubList(this, fromIndex, toIndex) @@ -52,8 +47,7 @@ public abstract class AbstractList protected constructor() : AbstractColl override fun hashCode(): Int = orderedHashCode(this) - // TODO: enable, once have inner classes. -/* + private open inner class IteratorImpl : Iterator { /** the index of the item that will be returned on the next call to [next]`()` */ protected var index = 0 @@ -86,7 +80,7 @@ public abstract class AbstractList protected constructor() : AbstractColl } override fun previousIndex(): Int = index - 1 - } */ + } internal companion object { internal fun checkElementIndex(index: Int, size: Int) { diff --git a/runtime/src/main/kotlin/kotlin/collections/ReversedViews.kt b/runtime/src/main/kotlin/kotlin/collections/ReversedViews.kt index 87eb9eb9d96..23365319b09 100644 --- a/runtime/src/main/kotlin/kotlin/collections/ReversedViews.kt +++ b/runtime/src/main/kotlin/kotlin/collections/ReversedViews.kt @@ -1,11 +1,11 @@ package kotlin.collections -/* + private open class ReversedListReadOnly(private val delegate: List) : AbstractList() { override val size: Int get() = delegate.size override fun get(index: Int): T = delegate[reverseElementIndex(index)] } - +/* private class ReversedList(private val delegate: MutableList) : AbstractMutableList() { override val size: Int get() = delegate.size override fun get(index: Int): T = delegate[reverseElementIndex(index)] @@ -17,7 +17,7 @@ private class ReversedList(private val delegate: MutableList) : AbstractMu override fun add(index: Int, element: T) { delegate.add(reversePositionIndex(index), element) } -} +}*/ private fun List<*>.reverseElementIndex(index: Int) = // TODO: Use AbstractList.checkElementIndex: run { AbstractList.checkElementIndex(index, size); lastIndex - index } if (index in 0..size - 1) size - index - 1 else throw IndexOutOfBoundsException("Index $index should be in range [${0..size - 1}].") @@ -30,7 +30,7 @@ private fun List<*>.reversePositionIndex(index: Int) = * All changes made in the original list will be reflected in the reversed one. */ public fun List.asReversed(): List = ReversedListReadOnly(this) - +/* /** * Returns a reversed mutable view of the original mutable List. * All changes made in the original list will be reflected in the reversed one and vice versa. From 91aab11e01db39cfd99243c3a37a4e1a5e1d771a Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 13 Jan 2017 16:58:55 +0700 Subject: [PATCH 02/13] backend: remove special handling for setter call because it is incorrect Handle it as usual call. --- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 13 ------------- 1 file changed, 13 deletions(-) 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 b8cef7fc9eb..b968b3b1e24 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 @@ -625,7 +625,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid private fun evaluateExpression(value: IrExpression): LLVMValueRef { when (value) { - is IrSetterCallImpl -> return evaluateSetterCall (value) is IrTypeOperatorCall -> return evaluateTypeOperator (value) is IrCall -> return evaluateCall (value) is IrDelegatingConstructorCall -> @@ -1737,18 +1736,6 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid return callDirect(descriptor, args, resultLifetime) } - //-------------------------------------------------------------------------// - - private fun evaluateSetterCall(value: IrSetterCallImpl): LLVMValueRef { - val descriptor = value.descriptor as FunctionDescriptor - val args = mutableListOf() - if (descriptor.dispatchReceiverParameter != null) - args.add(evaluateExpression(value.dispatchReceiver!!)) //add this ptr - args.add(evaluateExpression(value.getValueArgument(0)!!)) - return evaluateSimpleFunctionCall( - descriptor, args, Lifetime.IRRELEVANT, value.superQualifier) - } - //-------------------------------------------------------------------------// private fun resultLifetime(callee: IrMemberAccessExpression): Lifetime { return resultLifetimes.getOrElse(callee) { Lifetime.GLOBAL } From 49815f72e037f591c31469a6cf842f9bf8ecc709 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:13:30 +0700 Subject: [PATCH 03/13] Interop/Runtime: add support for Kotlin Native Also add minor improvements, fix bugs and workaround missing features. --- .../kotlin/kotlinx/cinterop/JvmCallbacks.kt | 3 - .../kotlin/kotlinx/cinterop/JvmNativeMem.kt | 4 +- .../jvm/kotlin/kotlinx/cinterop/JvmTypes.kt | 6 +- .../jvm/kotlin/kotlinx/cinterop/JvmUtils.kt | 4 ++ .../src/main/kotlin/kotlinx/cinterop/Types.kt | 23 +++---- .../src/main/kotlin/kotlinx/cinterop/Utils.kt | 55 ++++++++-------- .../kotlin/kotlinx/cinterop/NativeMem.kt | 66 +++++++++++++++++++ .../kotlin/kotlinx/cinterop/NativeTypes.kt | 26 ++++++++ .../kotlin/kotlinx/cinterop/NativeUtils.kt | 16 +++++ runtime/src/main/kotlin/kotlin/Exceptions.kt | 9 +++ 10 files changed, 164 insertions(+), 48 deletions(-) create mode 100644 Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt index 598fa97700b..402e9a7c732 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmCallbacks.kt @@ -150,9 +150,6 @@ abstract class CAdaptedFunctionTypeImpl> private typealias UserData = (ret: COpaquePointer, args: CArray)->Unit -inline fun > CAdaptedFunctionTypeImpl.Companion.of(): T = - T::class.objectInstance!! - private fun loadCallbacksLibrary() { System.loadLibrary("callbacks") } diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt index ee89f72d0be..17e6ed49875 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmNativeMem.kt @@ -38,12 +38,12 @@ object nativeMemUtils { fun getDouble(mem: NativePointed) = unsafe.getDouble(mem.address) fun putDouble(mem: NativePointed, value: Double) = unsafe.putDouble(mem.address, value) - fun getPtr(mem: NativePointed): NativePtr = when (dataModel) { + fun getNativePtr(mem: NativePointed): NativePtr = when (dataModel) { DataModel._32BIT -> getInt(mem).toLong() DataModel._64BIT -> getLong(mem) } - fun putPtr(mem: NativePointed, value: NativePtr) = when (dataModel) { + fun putNativePtr(mem: NativePointed, value: NativePtr) = when (dataModel) { DataModel._32BIT -> putInt(mem, value.toInt()) DataModel._64BIT -> putLong(mem, value) } diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt index f4665fefe3f..46129773ea1 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt @@ -8,7 +8,7 @@ val nativeNullPtr: NativePtr = 0L // TODO: the functions below should eventually be intrinsified -inline fun CVariable.Type.Companion.of() = T::class.companionObjectInstance as CVariable.Type +inline fun typeOf() = T::class.companionObjectInstance as CVariable.Type /** * Returns interpretation of entity with given pointer. @@ -27,4 +27,6 @@ inline fun interpretPointed(ptr: NativePtr): T { } inline fun > CAdaptedFunctionType.Companion.getInstanceOf(): T = - T::class.objectInstance!! \ No newline at end of file + T::class.objectInstance!! + +internal fun CPointer<*>.cPointerToString() = "CPointer(raw=0x%x)".format(rawValue) \ No newline at end of file diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt new file mode 100644 index 00000000000..5c7bf64a12c --- /dev/null +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmUtils.kt @@ -0,0 +1,4 @@ +package kotlinx.cinterop + +internal fun decodeFromUtf8(bytes: ByteArray) = String(bytes) +internal fun encodeToUtf8(str: String) = str.toByteArray() \ No newline at end of file diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt index 5bcba74cc31..69d367014ad 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt @@ -83,9 +83,7 @@ class CPointer private constructor(val rawValue: NativePtr) { return rawValue.hashCode() } - override fun toString(): String { - return "CPointer(raw=0x%x)".format(rawValue) - } + override fun toString() = this.cPointerToString() } /** @@ -141,18 +139,15 @@ interface CVariable : CPointed { open class Type(val size: Long, val align: Int) { init { - assert (size % align == 0L) + require(size % align == 0L) } - companion object - } - - companion object { - inline fun sizeOf() = Type.of().size - inline fun alignOf() = Type.of().align } } +inline fun sizeOf() = typeOf().size +inline fun alignOf() = typeOf().align + /** * The C data which is composed from several members. */ @@ -177,7 +172,7 @@ abstract class CStructVar : CVariable, CAggregate { */ sealed class CPrimitiveVar : CVariable { // aligning by size is obviously enough - open class Type(size: Int, align: Int = size) : CVariable.Type(size.toLong(), align) + open class Type(size: Int) : CVariable.Type(size.toLong(), align = size) } abstract class CEnumVar : CPrimitiveVar() @@ -256,8 +251,8 @@ typealias CPointerVar = CPointerVarWithValueMappedTo> * The value of this variable. */ inline var > CPointerVarWithValueMappedTo

.value: P? - get() = CPointer.createNullable(nativeMemUtils.getPtr(this)) as P? - set(value) = nativeMemUtils.putPtr(this, value.rawValue) + get() = CPointer.createNullable(nativeMemUtils.getNativePtr(this)) as P? + set(value) = nativeMemUtils.putNativePtr(this, value.rawValue) /** * The code or data pointed by the value of this variable. @@ -275,7 +270,7 @@ class CArray(override val rawPtr: NativePtr) : CAggregate inline fun CArray.elementOffset(index: Long) = if (index == 0L) { 0L // optimization for JVM impl which uses reflection for now. } else { - index * CVariable.sizeOf() + index * sizeOf() } inline operator fun CArray.get(index: Long): T = memberAt(elementOffset(index)) diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index a8e11cf810e..33d27eafcc5 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -20,22 +20,22 @@ object nativeHeap : NativeFreeablePlacement { // TODO: implement optimally class Arena(private val parent: NativeFreeablePlacement = nativeHeap) : NativePlacement { - private val allocatedChunks = mutableListOf() + private val allocatedChunks = ArrayList() override fun alloc(size: Long, align: Int): NativePointed { - val res = nativeHeap.alloc(size, align) + val res = parent.alloc(size, align) try { allocatedChunks.add(res) return res } catch (e: Throwable) { - nativeHeap.free(res) + parent.free(res) throw e } } fun clear() { allocatedChunks.forEach { - nativeHeap.free(it) + parent.free(it) } allocatedChunks.clear() @@ -51,7 +51,7 @@ fun NativePlacement.alloc(size: Int, align: Int) = alloc(size.toLong(), align) * @param T must not be abstract */ inline fun NativePlacement.alloc(): T = - alloc(CVariable.sizeOf(), CVariable.alignOf()).reinterpret() + alloc(sizeOf(), alignOf()).reinterpret() /** * Allocates C array of given elements type and length. @@ -59,7 +59,7 @@ inline fun NativePlacement.alloc(): T = * @param T must not be abstract */ inline fun NativePlacement.allocArray(length: Long): CArray = - alloc(CVariable.sizeOf() * length, CVariable.alignOf()).reinterpret() + alloc(sizeOf() * length, alignOf()).reinterpret() /** * Allocates C array of given elements type and length. @@ -78,7 +78,7 @@ inline fun NativePlacement.allocArray(length: Long, initializer: T.(index: Long)->Unit): CArray { val res = allocArray(length) - (0 until length).forEach { index -> + (0 .. length - 1).forEach { index -> res[index].initializer(index) } @@ -112,7 +112,7 @@ fun NativePlacement.allocArrayOfPointersTo(elements: List): C * Allocates C array of pointers to given elements. */ fun NativePlacement.allocArrayOfPointersTo(vararg elements: T?) = - allocArrayOfPointersTo(elements.toList()) + allocArrayOfPointersTo(listOf(*elements)) /** * Allocates C array of given values. @@ -120,7 +120,7 @@ fun NativePlacement.allocArrayOfPointersTo(vararg elements: T?) = inline fun > NativePlacement.allocArrayOf(vararg elements: T?): CArray> { - return allocArrayOf(elements.toList()) + return allocArrayOf(listOf(*elements)) } /** @@ -139,8 +139,9 @@ inline fun > fun NativePlacement.allocArrayOf(elements: ByteArray): CArray { val res = allocArray(elements.size) - elements.forEachIndexed { i, byte -> - res[i].value = byte + var index = 0 + for (byte in elements) { + res[index++].value = byte } return res } @@ -173,7 +174,7 @@ class CString private constructor(override val rawPtr: NativePtr) : CPointed { val bytes = ByteArray(len) nativeMemUtils.getByteArray(array[0], bytes, len) - return String(bytes) // TODO: encoding + return decodeFromUtf8(bytes) // TODO: encoding } fun asCharPtr() = reinterpret() @@ -184,7 +185,7 @@ fun CString.Companion.fromString(str: String?, placement: NativePlacement): CStr return null } - val bytes = str.toByteArray() // TODO: encoding + val bytes = encodeToUtf8(str) // TODO: encoding val len = bytes.size val nativeBytes = nativeHeap.allocArray(len + 1) @@ -197,20 +198,16 @@ fun CString.Companion.fromString(str: String?, placement: NativePlacement): CStr fun CPointer.asCString() = CString.fromArray(this.reinterpret>().pointed) fun String.toCString(placement: NativePlacement) = CString.fromString(this, placement) -class MemScope private constructor(private val arena: Arena) : NativePlacement by arena { +class MemScope : NativePlacement { + + private val arena = Arena() + + override fun alloc(size: Long, align: Int) = arena.alloc(size, align) + + fun clear() = arena.clear() + val memScope: NativePlacement get() = this - - companion object { - internal inline fun use(block: MemScope.()->R): R { - val memScope = MemScope(Arena()) - try { - return memScope.block() - } finally { - memScope.arena.clear() - } - } - } } /** @@ -218,6 +215,10 @@ class MemScope private constructor(private val arena: Arena) : NativePlacement b * which will be automatically disposed at the end of this scope. */ inline fun memScoped(block: MemScope.()->R): R { - @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") // TODO: it is a hack - return MemScope.use(block) + val memScope = MemScope() + try { + return memScope.block() + } finally { + memScope.clear() + } } diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt new file mode 100644 index 00000000000..488241774b9 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeMem.kt @@ -0,0 +1,66 @@ +package kotlinx.cinterop + +import konan.internal.Intrinsic + +internal inline val pointerSize: Int + get() = getPointerSize() + +@Intrinsic external fun getPointerSize(): Int + +// TODO: do not use singleton because it leads to init-check on any access. +object nativeMemUtils { + @Intrinsic external fun getByte(mem: NativePointed): Byte + @Intrinsic external fun putByte(mem: NativePointed, value: Byte) + + @Intrinsic external fun getShort(mem: NativePointed): Short + @Intrinsic external fun putShort(mem: NativePointed, value: Short) + + @Intrinsic external fun getInt(mem: NativePointed): Int + @Intrinsic external fun putInt(mem: NativePointed, value: Int) + + @Intrinsic external fun getLong(mem: NativePointed): Long + @Intrinsic external fun putLong(mem: NativePointed, value: Long) + + @Intrinsic external fun getFloat(mem: NativePointed): Float + @Intrinsic external fun putFloat(mem: NativePointed, value: Float) + + @Intrinsic external fun getDouble(mem: NativePointed): Double + @Intrinsic external fun putDouble(mem: NativePointed, value: Double) + + @Intrinsic external fun getNativePtr(mem: NativePointed): NativePtr + @Intrinsic external fun putNativePtr(mem: NativePointed, value: NativePtr) + + fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) { + val sourceArray: CArray = source.reinterpret() + for (index in 0 .. length - 1) { + dest[index] = sourceArray[index].value + } + } + + fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) { + val destArray: CArray = dest.reinterpret() + for (index in 0 .. length - 1) { + destArray[index].value = source[index] + } + } + + private class NativeAllocated(override val rawPtr: NativePtr) : NativePointed + + fun alloc(size: Long, align: Int): NativePointed { + val ptr = malloc(size, align) + if (ptr == nativeNullPtr) { + throw OutOfMemoryError("unable to allocate native memory") + } + return NativeAllocated(ptr) + } + + fun free(mem: NativePointed) { + free(mem.rawPtr) + } +} + +@SymbolName("Kotlin_interop_malloc") +private external fun malloc(size: Long, align: Int): NativePtr + +@SymbolName("Kotlin_interop_free") +private external fun free(ptr: NativePtr) \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt new file mode 100644 index 00000000000..dc3a545ed57 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt @@ -0,0 +1,26 @@ +package kotlinx.cinterop + +import konan.internal.Intrinsic + +class NativePtr private constructor() { + @Intrinsic external operator fun plus(offset: Long): NativePtr +} + +inline val nativeNullPtr: NativePtr + get() = getNativeNullPtr() + +@Intrinsic external fun getNativeNullPtr(): NativePtr + +fun typeOf(): CVariable.Type = throw Error("typeOf() is called with erased argument") + +/** + * Returns interpretation of entity with given pointer. + * + * @param T must not be abstract + */ +@Intrinsic external fun interpretPointed(ptr: NativePtr): T + +inline fun > CAdaptedFunctionType.Companion.getInstanceOf(): T = + TODO("CAdaptedFunctionType.getInstanceOf") + +internal fun CPointer<*>.cPointerToString() = "CPointer(raw=$rawValue)" \ No newline at end of file diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt new file mode 100644 index 00000000000..16dcce28e61 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeUtils.kt @@ -0,0 +1,16 @@ +package kotlinx.cinterop + +internal fun decodeFromUtf8(bytes: ByteArray): String = kotlin.text.fromUtf8Array(bytes, 0, bytes.size) + +fun encodeToUtf8(str: String): ByteArray { + val result = ByteArray(str.length) + + for (index in 0 .. str.length - 1) { + val char = str[index] + if (char.toInt() >= 128) { + TODO("non-ASCII char") + } + result[index] = char.toByte() + } + return result +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Exceptions.kt b/runtime/src/main/kotlin/kotlin/Exceptions.kt index 79efffba2f3..50f49c24daf 100644 --- a/runtime/src/main/kotlin/kotlin/Exceptions.kt +++ b/runtime/src/main/kotlin/kotlin/Exceptions.kt @@ -154,3 +154,12 @@ public class NoWhenBranchMatchedException : RuntimeException { constructor(s: String) : super(s) { } } + +public class OutOfMemoryError : Error { + + constructor() : super() { + } + + constructor(s: String) : super(s) { + } +} \ No newline at end of file From 69393985213138482eed39a773ec55a70b8a82ba Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 2 Feb 2017 12:20:56 +0700 Subject: [PATCH 04/13] Interop/Runtime: fix major bug: alloc C strings using correct placement --- Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index 33d27eafcc5..cfd1b49d7a7 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -187,7 +187,7 @@ fun CString.Companion.fromString(str: String?, placement: NativePlacement): CStr val bytes = encodeToUtf8(str) // TODO: encoding val len = bytes.size - val nativeBytes = nativeHeap.allocArray(len + 1) + val nativeBytes = placement.allocArray(len + 1) nativeMemUtils.putByteArray(bytes, nativeBytes[0], len) nativeBytes[len].value = 0 From d02fd5ec1b686dc4025dfe97c28247dc965fc1f2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:30:56 +0700 Subject: [PATCH 05/13] build: add Interop/Runtime to stdlib --- backend.native/build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend.native/build.gradle b/backend.native/build.gradle index 6ae53526e12..cd106cbace0 100644 --- a/backend.native/build.gradle +++ b/backend.native/build.gradle @@ -204,9 +204,13 @@ targetList.each { target -> '-runtime', project(':runtime').file("build/${target}/runtime.bc"), '-properties', project(':backend.native').file('konan.properties'), project(':runtime').file('src/main/kotlin'), + project(':Interop:Runtime').file('src/main/kotlin'), + project(':Interop:Runtime').file('src/native/kotlin'), *project.globalArgs) inputs.dir(project(':runtime').file('src/main/kotlin')) + inputs.dir(project(':Interop:Runtime').file('src/main/kotlin')) + inputs.dir(project(':Interop:Runtime').file('src/native/kotlin')) outputs.file(project(':runtime').file("build/${target}/stdlib.kt.bc")) dependsOn ":runtime:${target}Runtime" From 1bafee0ac3eacb304db62820165542ad3eff6f2f Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:28:43 +0700 Subject: [PATCH 06/13] backend: add utils for interop support --- .../jetbrains/kotlin/backend/konan/Context.kt | 5 + .../kotlin/backend/konan/InteropUtils.kt | 103 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index e984a0c1f5b..c0c7455f12d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.backend.konan import llvm.LLVMDumpModule import llvm.LLVMModuleRef import org.jetbrains.kotlin.backend.jvm.descriptors.initialize +import org.jetbrains.kotlin.backend.konan.InteropBuiltIns import org.jetbrains.kotlin.backend.konan.descriptors.deepPrint import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName import org.jetbrains.kotlin.backend.konan.ir.Ir @@ -57,6 +58,10 @@ internal final class Context(val config: KonanConfig) : KonanBackendContext() { override val irBuiltIns get() = ir.irModule.irBuiltins + val interopBuiltIns by lazy { + InteropBuiltIns(this.builtIns) + } + var llvmModule: LLVMModuleRef? = null set(module: LLVMModuleRef?) { if (field != null) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt new file mode 100644 index 00000000000..9445a8d200f --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -0,0 +1,103 @@ +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.scopes.MemberScope + +private val cPointerName = "CPointer" +private val nativePointedName = "NativePointed" +private val nativePtrName = "NativePtr" + +internal class InteropBuiltIns(builtIns: KonanBuiltIns) { + + object FqNames { + val packageName = FqName("kotlinx.cinterop") + + val nativePtr = packageName.child(Name.identifier(nativePtrName)).toUnsafe() + val cPointer = packageName.child(Name.identifier(cPointerName)).toUnsafe() + val nativePointed = packageName.child(Name.identifier(nativePointedName)).toUnsafe() + } + + private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope + + val getNativeNullPtr = packageScope.getContributedFunctions("getNativeNullPtr").single() + + val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single() + + private val nativePtr = packageScope.getContributedClassifier(nativePtrName) as ClassDescriptor + + private val nativePointed = packageScope.getContributedClassifier(nativePointedName) as ClassDescriptor + + private val cPointer = this.packageScope.getContributedClassifier(cPointerName) as ClassDescriptor + + val cPointerRawValue = cPointer.unsubstitutedMemberScope.getContributedVariables("rawValue").single() + + val nativePointedRawPtrGetter = + nativePointed.unsubstitutedMemberScope.getContributedVariables("rawPtr").single().getter!! + + val memberAt = packageScope.getContributedFunctions("memberAt").single() + + val interpretPointed = packageScope.getContributedFunctions("interpretPointed").single() + + val arrayGetByIntIndex = packageScope.getContributedFunctions("get").single { + KotlinBuiltIns.isInt(it.valueParameters.single().type) + } + + val arrayGetByLongIndex = packageScope.getContributedFunctions("get").single { + KotlinBuiltIns.isLong(it.valueParameters.single().type) + } + + val allocUninitializedArrayWithIntLength = packageScope.getContributedFunctions("allocArray").single { + it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type) + } + + val allocUninitializedArrayWithLongLength = packageScope.getContributedFunctions("allocArray").single { + it.valueParameters.size == 1 && KotlinBuiltIns.isLong(it.valueParameters[0].type) + } + + val allocVariable = packageScope.getContributedFunctions("alloc").single { + it.valueParameters.size == 0 + } + + val typeOf = packageScope.getContributedFunctions("typeOf").single() + + val variableClass = packageScope.getContributedClassifier("CVariable") as ClassDescriptor + + val variableTypeClass = + variableClass.unsubstitutedInnerClassesScope.getContributedClassifier("Type") as ClassDescriptor + + val variableTypeSize = variableTypeClass.unsubstitutedMemberScope.getContributedVariables("size").single() + + val variableTypeAlign = variableTypeClass.unsubstitutedMemberScope.getContributedVariables("align").single() + + val nativeMemUtils = packageScope.getContributedClassifier("nativeMemUtils") as ClassDescriptor + + private val primitives = listOf( + builtIns.byte, builtIns.short, builtIns.int, builtIns.long, + builtIns.float, builtIns.double, + nativePtr + ) + + val readPrimitive = primitives.map { + nativeMemUtils.unsubstitutedMemberScope.getContributedFunctions("get" + it.name).single() + }.toSet() + + val writePrimitive = primitives.map { + nativeMemUtils.unsubstitutedMemberScope.getContributedFunctions("put" + it.name).single() + }.toSet() + + val nativePtrPlusLong = nativePtr.unsubstitutedMemberScope.getContributedFunctions("plus").single() + +} + +private fun MemberScope.getContributedVariables(name: String) = + this.getContributedVariables(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) + +private fun MemberScope.getContributedClassifier(name: String) = + this.getContributedClassifier(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) + +private fun MemberScope.getContributedFunctions(name: String) = + this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BUILTINS) \ No newline at end of file From 7f90ce3d8a84ab9f96d2147195e50271fe5bcb73 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 2 Feb 2017 17:29:31 +0700 Subject: [PATCH 07/13] backend: support native interop value types also improve reliability of `==` lowering --- .../kotlin/backend/konan/InteropUtils.kt | 2 + .../kotlin/backend/konan/ValueTypes.kt | 6 ++- .../kotlin/backend/konan/ir/IrUtils.kt | 2 + .../kotlin/backend/konan/llvm/DataLayout.kt | 4 +- .../kotlin/backend/konan/lower/Autoboxing.kt | 14 +++++ .../konan/lower/BuiltinOperatorLowering.kt | 12 ++++- .../kotlin/backend/konan/util/util.kt | 9 ++++ .../kotlin/konan/internal/InteropBoxing.kt | 53 +++++++++++++++++++ .../main/kotlin/konan/internal/Intrinsics.kt | 10 ++-- 9 files changed, 105 insertions(+), 7 deletions(-) create mode 100644 runtime/src/main/kotlin/konan/internal/InteropBoxing.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index 9445a8d200f..5ccf531c3b8 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -27,6 +27,8 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single() + val nullableInteropValueTypes = listOf(ValueType.C_POINTER, ValueType.NATIVE_POINTED) + private val nativePtr = packageScope.getContributedClassifier(nativePtrName) as ClassDescriptor private val nativePointed = packageScope.getContributedClassifier(nativePointedName) as ClassDescriptor diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt index 8e0d5235965..077b39fa032 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt @@ -24,7 +24,11 @@ enum class ValueType(val classFqName: FqNameUnsafe, val isNullable: Boolean = fa FLOAT(KotlinBuiltIns.FQ_NAMES._float), DOUBLE(KotlinBuiltIns.FQ_NAMES._double), - UNBOUND_CALLABLE_REFERENCE(FqNameUnsafe("konan.internal.UnboundCallableReference")) + UNBOUND_CALLABLE_REFERENCE(FqNameUnsafe("konan.internal.UnboundCallableReference")), + NATIVE_PTR(InteropBuiltIns.FqNames.nativePtr), + + NATIVE_POINTED(InteropBuiltIns.FqNames.nativePointed, isNullable = true), + C_POINTER(InteropBuiltIns.FqNames.cPointer, isNullable = true) } private fun KotlinType.isConstructedFromGivenClass(fqName: FqNameUnsafe) = diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt index ecc7203d33e..41efb6bd32d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/IrUtils.kt @@ -65,6 +65,8 @@ internal fun IrMemberAccessExpression.addArguments(args: Map>) = this.addArguments(args.toMap()) +internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrConstKind.Null + fun ir2string(ir: IrElement?): String = ir2stringWhole(ir).takeWhile { it != '\n' } fun ir2stringWhole(ir: IrElement?): String { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index 7e7be223413..d6707d6af9e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -15,7 +15,9 @@ private val valueTypes = ValueType.values().associate { ValueType.LONG -> LLVMInt64Type() ValueType.FLOAT -> LLVMFloatType() ValueType.DOUBLE -> LLVMDoubleType() - ValueType.UNBOUND_CALLABLE_REFERENCE -> int8TypePtr + + ValueType.UNBOUND_CALLABLE_REFERENCE, + ValueType.NATIVE_PTR, ValueType.NATIVE_POINTED, ValueType.C_POINTER -> int8TypePtr }!! } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index cf619e512f5..4844d205380 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -6,8 +6,10 @@ import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.ValueType import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalClass import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.ir.isNullConst import org.jetbrains.kotlin.backend.konan.notNullableIsRepresentedAs import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.backend.konan.util.atMostOne import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor @@ -103,6 +105,10 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } override fun IrExpression.useAs(type: KotlinType): IrExpression { + val interop = context.interopBuiltIns + if (this.isNullConst() && interop.nullableInteropValueTypes.any { type.isRepresentedAs(it) }) { + return IrCallImpl(startOffset, endOffset, interop.getNativeNullPtr).uncheckedCast(type) + } val actualType = when (this) { is IrCall -> this.descriptor.original.returnType ?: this.type @@ -176,6 +182,14 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } private fun IrExpression.unbox(valueType: ValueType): IrExpression { + val unboxFunctionName = "unbox${valueType.shortName}" + + context.builtIns.getKonanInternalFunctions(unboxFunctionName).atMostOne()?.let { + return IrCallImpl(startOffset, endOffset, it).apply { + putValueArgument(0, this@unbox.uncheckedCast(it.valueParameters[0].type)) + }.uncheckedCast(this.type) + } + val boxGetter = getBoxType(valueType) .memberScope.getContributedDescriptors() .filterIsInstance() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt index 3e0166a2f5b..5d1db61e7f5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt @@ -3,6 +3,8 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.BodyLoweringPass import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.descriptors.getKonanInternalFunctions +import org.jetbrains.kotlin.backend.konan.ir.isNullConst +import org.jetbrains.kotlin.backend.konan.util.atMostOne import org.jetbrains.kotlin.ir.descriptors.IrBuiltinOperatorDescriptor import org.jetbrains.kotlin.ir.expressions.IrBody import org.jetbrains.kotlin.ir.expressions.IrCall @@ -69,9 +71,15 @@ private class BuiltinOperatorTransformer(val context: Context) : IrElementTransf // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: - val equals = builtIns.getKonanInternalFunctions("areEqualByValue").firstOrNull { + val equals = builtIns.getKonanInternalFunctions("areEqualByValue").atMostOne { lhs.type.isSubtypeOf(it.valueParameters[0].type) && rhs.type.isSubtypeOf(it.valueParameters[1].type) - } ?: builtIns.getKonanInternalFunctions("areEqual").single() // or use the general implementation. + } ?: if (lhs.isNullConst() || rhs.isNullConst()) { + // or compare by reference if left or right part is `null`: + irBuiltins.eqeqeq + } else { + // or use the general implementation: + builtIns.getKonanInternalFunctions("areEqual").single() + } return IrCallImpl(startOffset, endOffset, equals).apply { putValueArgument(0, lhs) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt index 9c1eb21a457..bf32b140230 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/util/util.kt @@ -20,3 +20,12 @@ fun nTabs(amount: Int): String { return String.format("%1$-${(amount+1)*4}s", "") } +fun Collection.atMostOne(): T? { + return when (this.size) { + 0 -> null + 1 -> this.iterator().next() + else -> throw IllegalArgumentException("Collection has more than one element.") + } +} + +inline fun Iterable.atMostOne(predicate: (T) -> Boolean): T? = this.filter(predicate).atMostOne() \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt new file mode 100644 index 00000000000..8677bd7e280 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/InteropBoxing.kt @@ -0,0 +1,53 @@ +package konan.internal + +import kotlinx.cinterop.* + +class NativePtrBox(val value: NativePtr) { + override fun equals(other: Any?): Boolean { + if (other !is NativePtrBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxNativePtr(value: NativePtr) = NativePtrBox(value) + +class NativePointedBox(val value: NativePointed) { + override fun equals(other: Any?): Boolean { + if (other !is NativePointedBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxNativePointed(value: NativePointed?) = if (value != null) NativePointedBox(value) else null +fun unboxNativePointed(box: NativePointedBox?) = box?.value + +class CPointerBox(val value: CPointer<*>) { + override fun equals(other: Any?): Boolean { + if (other !is CPointerBox) { + return false + } + + return this.value == other.value + } + + override fun hashCode() = value.hashCode() + + override fun toString() = value.toString() +} + +fun boxCPointer(value: CPointer<*>?) = if (value != null) CPointerBox(value) else null +fun unboxCPointer(box: CPointerBox?) = box?.value \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index a3509e77fc8..cf48c5599de 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -1,5 +1,9 @@ package konan.internal +import kotlinx.cinterop.CPointer +import kotlinx.cinterop.NativePointed +import kotlinx.cinterop.NativePtr + @Intrinsic external fun areEqualByValue(first: Boolean, second: Boolean): Boolean @Intrinsic external fun areEqualByValue(first: Char, second: Char): Boolean @Intrinsic external fun areEqualByValue(first: Byte, second: Byte): Boolean @@ -9,9 +13,9 @@ package konan.internal @Intrinsic external fun areEqualByValue(first: Float, second: Float): Boolean @Intrinsic external fun areEqualByValue(first: Double, second: Double): Boolean -// For comparing with `null`: -@Intrinsic external fun areEqualByValue(first: Nothing?, second: Any?): Boolean -@Intrinsic external fun areEqualByValue(first: Any?, second: Nothing?): Boolean +@Intrinsic external fun areEqualByValue(first: NativePtr, second: NativePtr): Boolean +@Intrinsic external fun areEqualByValue(first: NativePointed?, second: NativePointed?): Boolean +@Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): Boolean inline fun areEqual(first: Any?, second: Any?): Boolean { return if (first == null) second == null else first.equals(second) From 3892820052746adaaca6b955fa4c0709d474ddc9 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:27:26 +0700 Subject: [PATCH 08/13] backend: lower interop --- .../kotlin/backend/konan/KonanLower.kt | 4 + .../kotlin/backend/konan/KonanPhases.kt | 1 + .../backend/konan/lower/InteropLowering.kt | 201 ++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 922283287cb..fc2f3c73d05 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -51,5 +51,9 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_INLINE) { //FunctionInlining(context).inline(irFile) } + + phaser.phase(KonanPhase.LOWER_INTEROP) { + InteropLowering(context).runOnFilePostfix(irFile) + } } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index f5ba9e52da0..8ab4548e3b7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -18,6 +18,7 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_LOCAL_FUNCTIONS("Local Function Lowering"), /* ... ... */ LOWER_CALLABLES("Callable references Lowering"), /* ... ... */ LOWER_INLINE("Functions inlining"), + /* ... ... */ LOWER_INTEROP("Interop lowering"), /* ... ... */ AUTOBOX("Autoboxing of primitive types"), /* ... ... */ LOWER_ENUMS("Enum classes lowering"), /* ... ... */ LOWER_INNER_CLASSES("Inner classes lowering"), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt new file mode 100644 index 00000000000..ee6bd0a9091 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -0,0 +1,201 @@ +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.FunctionLoweringPass +import org.jetbrains.kotlin.backend.common.lower.at +import org.jetbrains.kotlin.backend.common.lower.createFunctionIrBuilder +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.builders.IrBuilder +import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope +import org.jetbrains.kotlin.ir.builders.irCall +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrTypeOperatorCall +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.OverridingUtil +import org.jetbrains.kotlin.resolve.scopes.MemberScope +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf + +/** + * Lowers some interop intrinsic calls. + */ +internal class InteropLowering(val context: Context) : FunctionLoweringPass { + override fun lower(irFunction: IrFunction) { + val transformer = InteropTransformer(context, irFunction.descriptor) + irFunction.transformChildrenVoid(transformer) + } +} + +private class InteropTransformer(val context: Context, val function: FunctionDescriptor) : IrElementTransformerVoid() { + + val builder = context.createFunctionIrBuilder(function) + val interop = context.interopBuiltIns + + private fun MemberScope.getSingleContributedFunction(name: String, + predicate: (SimpleFunctionDescriptor) -> Boolean) = + this.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).single(predicate) + + private fun IrBuilder.irGetObject(descriptor: ClassDescriptor): IrExpression { + return IrGetObjectValueImpl(startOffset, endOffset, descriptor.defaultType, descriptor) + } + + private fun IrBuilder.typeOf(descriptor: ClassDescriptor): IrExpression { + val companionObject = descriptor.companionObjectDescriptor ?: + error("native variable class $descriptor must have the companion object") + // TODO: add more checks and produce the compile error instead of exception. + + return irGetObject(companionObject) + } + + private fun IrBuilder.typeOf(type: KotlinType): IrExpression? { + val descriptor = TypeUtils.getClassDescriptor(type) ?: return null + return typeOf(descriptor) + } + + private fun KotlinType.findOverride(property: PropertyDescriptor): PropertyDescriptor { + val result = this.memberScope.getContributedVariables(property.name, NoLookupLocation.FROM_BACKEND).single() + assert (OverridingUtil.overrides(result, property)) + return result + } + + private fun IrBuilderWithScope.sizeOf(typeObject: IrExpression): IrExpression { + val sizeProperty = typeObject.type.findOverride(interop.variableTypeSize) + return irGet(typeObject, sizeProperty) + } + + private fun IrBuilderWithScope.sizeOf(type: KotlinType): IrExpression? { + val typeObject = typeOf(type) ?: return null + return sizeOf(typeObject) + } + + private fun IrBuilderWithScope.alignOf(typeObject: IrExpression): IrExpression { + val alignProperty = typeObject.type.findOverride(interop.variableTypeAlign) + return irGet(typeObject, alignProperty) + } + + private fun IrBuilderWithScope.alignOf(type: KotlinType): IrExpression? { + val typeObject = typeOf(type) ?: return null + return alignOf(typeObject) + } + + private fun IrBuilderWithScope.arrayGet(array: IrExpression, index: IrExpression): IrExpression? { + val elementSize = sizeOf(array.type.arguments.single().type) ?: return null + + val offset = times(elementSize, index) + + return irCall(interop.memberAt).apply { + extensionReceiver = array + putValueArgument(0, offset) + } + } + + private fun IrBuilderWithScope.times(left: IrExpression, right: IrExpression): IrCall { + val times = left.type.memberScope.getSingleContributedFunction("times") { + right.type.isSubtypeOf(it.valueParameters.single().type) + } + + return irCall(times).apply { + dispatchReceiver = left + putValueArgument(0, right) + } + } + + private fun IrBuilderWithScope.alloc(placement: IrExpression, size: IrExpression, align: IrExpression): IrExpression { + val alloc = placement.type.memberScope.getSingleContributedFunction("alloc") { + size.type.isSubtypeOf(it.valueParameters[0]!!.type) && + align.type.isSubtypeOf(it.valueParameters[1]!!.type) + } + + return irCall(alloc).apply { + dispatchReceiver = placement + putValueArgument(0, size) + putValueArgument(1, align) + } + } + + private fun IrBuilderWithScope.allocArray(placement: IrExpression, + elementType: KotlinType, + length: IrExpression + ): IrExpression? { + + val elementSize = sizeOf(elementType) ?: return null + val size = times(elementSize, length) + val align = alignOf(elementType) ?: return null + + return alloc(placement, size, align) + } + + private fun IrBuilderWithScope.alloc(placement: IrExpression, type: KotlinType): IrExpression? { + val size = sizeOf(type) ?: return null + val align = alignOf(type) ?: return null + + return alloc(placement, size, align) + } + + override fun visitCall(expression: IrCall): IrExpression { + + expression.transformChildrenVoid(this) + builder.at(expression) + val descriptor = expression.descriptor.original + + if (descriptor is ClassConstructorDescriptor) { + val type = descriptor.constructedClass.defaultType + if (type.isRepresentedAs(ValueType.C_POINTER) || type.isRepresentedAs(ValueType.NATIVE_POINTED)) { + return expression.getValueArgument(0)!! + } + } + + if (descriptor == interop.nativePointedRawPtrGetter || + OverridingUtil.overrides(descriptor, interop.nativePointedRawPtrGetter)) { + + return expression.dispatchReceiver!! + } + + return when (descriptor) { + interop.cPointerRawValue.getter -> expression.dispatchReceiver!! + + interop.interpretPointed -> expression.getValueArgument(0)!! + + interop.arrayGetByIntIndex, interop.arrayGetByLongIndex -> { + val array = expression.extensionReceiver!! + val index = expression.getValueArgument(0)!! + builder.arrayGet(array, index) ?: expression + } + + interop.allocUninitializedArrayWithIntLength, interop.allocUninitializedArrayWithLongLength -> { + val placement = expression.extensionReceiver!! + val elementType = expression.type.arguments.single().type + val length = expression.getValueArgument(0)!! + builder.allocArray(placement, elementType, length) ?: expression + } + + interop.allocVariable -> { + val placement = expression.extensionReceiver!! + val type = expression.getSingleTypeArgument() + builder.alloc(placement, type) ?: expression + } + + interop.typeOf -> { + val type = expression.getSingleTypeArgument() + builder.typeOf(type) ?: expression + } + + else -> expression + } + } + + private fun IrCall.getSingleTypeArgument(): KotlinType { + val typeParameter = descriptor.original.typeParameters.single() + return getTypeArgument(typeParameter)!! + } +} \ No newline at end of file From 4b694225c14f22d37fa0d4e045d65e9c1fc23aab Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:32:43 +0700 Subject: [PATCH 09/13] backend/codegen, runtime: support interop intrinsics and natives --- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 28 ++++++++++++++++--- runtime/src/main/cpp/Natives.cpp | 21 ++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) 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 b968b3b1e24..bb27b9f83fb 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 @@ -1761,16 +1761,16 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid //-------------------------------------------------------------------------// private fun evaluateIntrinsicCall(callee: IrCall, args: List): LLVMValueRef { - val descriptor = callee.descriptor + val descriptor = callee.descriptor.original val name = descriptor.fqNameUnsafe.asString() - return when (name) { + when (name) { "konan.internal.areEqualByValue" -> { val arg0 = args[0] val arg1 = args[1] assert (arg0.type == arg1.type) - when (LLVMGetTypeKind(arg0.type)) { + return when (LLVMGetTypeKind(arg0.type)) { LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind -> codegen.fcmpEq(arg0, arg1) @@ -1778,8 +1778,28 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid codegen.icmpEq(arg0, arg1) } } + } - else -> TODO(name) + val interop = context.interopBuiltIns + + return when (descriptor) { + in interop.readPrimitive -> { + val pointerType = pointerType(codegen.getLLVMType(descriptor.returnType!!)) + val rawPointer = args.last() + val pointer = codegen.bitcast(pointerType, rawPointer) + codegen.load(pointer) + } + in interop.writePrimitive -> { + val pointerType = pointerType(codegen.getLLVMType(descriptor.valueParameters.last().type)) + val rawPointer = args[1] + val pointer = codegen.bitcast(pointerType, rawPointer) + codegen.store(args[2], pointer) + codegen.theUnitInstanceRef.llvm + } + interop.nativePtrPlusLong -> codegen.gep(args[0], args[1]) + interop.getNativeNullPtr -> kNullInt8Ptr + interop.getPointerSize -> Int32(LLVMPointerSize(codegen.llvmTargetData)).llvm + else -> TODO(callee.descriptor.original.toString()) } } diff --git a/runtime/src/main/cpp/Natives.cpp b/runtime/src/main/cpp/Natives.cpp index b28137369a2..81dc436da95 100644 --- a/runtime/src/main/cpp/Natives.cpp +++ b/runtime/src/main/cpp/Natives.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include #include @@ -187,4 +190,22 @@ OBJ_GETTER0(Kotlin_konan_internal_undefined) { RETURN_OBJ(nullptr); } +void* Kotlin_interop_malloc(KLong size, KInt align) { + if (size > SIZE_MAX) { + return nullptr; + } + + void* result = malloc(size); + if ((reinterpret_cast(result) & (align - 1)) != 0) { + // Unaligned! + RuntimeAssert(false, "unsupported alignment"); + } + + return result; +} + +void Kotlin_interop_free(void* ptr) { + free(ptr); +} + } // extern "C" From cf3b95fb3d4fe2c3fb8370d0d9c21e4997e45e02 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 31 Jan 2017 13:29:21 +0700 Subject: [PATCH 10/13] Interop/StubGenerator, NativeInteropPlugin: add support for Kotlin Native Also add minor improvements. --- .../native/interop/gen/jvm/StubGenerator.kt | 111 ++++++++++++++---- .../kotlin/native/interop/gen/jvm/main.kt | 62 ++++++---- .../kotlin/NativeInteropPlugin.groovy | 58 +++++---- 3 files changed, 167 insertions(+), 64 deletions(-) diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 02b02fcddc9..8cbd4841acb 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -3,12 +3,20 @@ package org.jetbrains.kotlin.native.interop.gen.jvm import org.jetbrains.kotlin.native.interop.indexer.* import java.lang.IllegalStateException +enum class KotlinPlatform { + JVM, + NATIVE +} + +// TODO: mostly rename 'jni' to 'c'. + class StubGenerator( val nativeIndex: NativeIndex, val pkgName: String, val libName: String, val excludedFunctions: Set, - val dumpShims: Boolean) { + val dumpShims: Boolean, + val platform: KotlinPlatform = KotlinPlatform.JVM) { /** * The names that should not be used for struct classes to prevent name clashes @@ -253,7 +261,7 @@ class StubGenerator( override fun argFromJni(name: String) = "CPointer.createNullable<$pointee>($name)" override val jniType: String - get() = "Long" + get() = "NativePtr" override fun constructPointedType(valueType: String) = "CPointerVarWithValueMappedTo<$valueType>" } @@ -264,7 +272,7 @@ class StubGenerator( override fun argFromJni(name: String) = "interpretPointed<$pointed>($name)" override val jniType: String - get() = "Long" + get() = "NativePtr" override fun constructPointedType(valueType: String): String { // TODO: this method must not exist @@ -390,7 +398,7 @@ class StubGenerator( kotlinType = "String?", kotlinConv = { name -> "$name?.toCString(memScope).rawPtr" }, memScoped = true, - kotlinJniBridgeType = "Long" + kotlinJniBridgeType = "NativePtr" ) } } @@ -573,7 +581,7 @@ class StubGenerator( paramBindings.add(OutValueBinding( kotlinType = "NativePlacement", kotlinConv = { name -> "$name.alloc<${retValMirror.pointedTypeName}>().rawPtr" }, - kotlinJniBridgeType = "Long" + kotlinJniBridgeType = "NativePtr" )) } @@ -614,7 +622,7 @@ class StubGenerator( val header = "fun ${func.name}($args): ${retValBinding.kotlinType}" - fun generateBody() { + fun generateBody(memScoped: Boolean) { val externalParamNames = paramNames.mapIndexed { i: Int, name: String -> val binding = paramBindings[i] val externalParamName: String @@ -631,8 +639,9 @@ class StubGenerator( } out("val res = externals.${func.name}(" + externalParamNames.joinToString(", ") + ")") + val result = retValBinding.conv("res") if (dumpShims) { - val returnValueRepresentation = retValBinding.conv("res") + val returnValueRepresentation = result out("print(\"\${${returnValueRepresentation}}\\t= \")") out("print(\"${func.name}( \")") val argsRepresentation = paramNames.map{"\${${it}}"}.joinToString(", ") @@ -640,17 +649,21 @@ class StubGenerator( out("println(\")\")") } - out("return " + retValBinding.conv("res")) + if (memScoped) { + out(result) + } else { + out("return " + result) + } } block(header) { val memScoped = paramBindings.any { it.memScoped } if (memScoped) { - block("memScoped") { - generateBody() + block("return memScoped") { + generateBody(true) } } else { - generateBody() + generateBody(false) } } } @@ -696,6 +709,11 @@ class StubGenerator( private fun generateFunctionType(type: FunctionType, name: String) { val kotlinFunctionType = getKotlinFunctionType(type) + if (platform == KotlinPlatform.NATIVE) { + out("object $name : CFunctionType {}") + return + } + val constructorArgs = listOf(getRetValFfiType(type.returnType)) + type.parameterTypes.map { getArgFfiType(it) } @@ -727,6 +745,12 @@ class StubGenerator( } } + private val FunctionDecl.stubSymbolName: String + get() { + require(platform == KotlinPlatform.NATIVE) + return "kni_" + pkgName.replace('/', '_') + '_' + this.name + } + /** * Produces to [out] the definition of Kotlin JNI function used in binding for given C function. */ @@ -739,6 +763,9 @@ class StubGenerator( "$name: " + paramBindings[i].kotlinJniBridgeType }.joinToString(", ") + if (platform == KotlinPlatform.NATIVE) { + out("@SymbolName(\"${func.stubSymbolName}\")") + } out("external fun ${func.name}($args): ${retValBinding.kotlinJniBridgeType}") } @@ -797,7 +824,9 @@ class StubGenerator( } block("object externals") { - out("init { System.loadLibrary(\"$libName\") }") + if (platform == KotlinPlatform.JVM) { + out("init { System.loadLibrary(\"$libName\") }") + } functionsToBind.forEach { try { transaction { @@ -811,6 +840,28 @@ class StubGenerator( } } + /** + * Returns the C type to be used for value of given Kotlin type in JNI function implementation. + */ + fun getCBridgeType(kotlinJniBridgeType: String): String { + return when (platform) { + KotlinPlatform.JVM -> getCJniBridgeType(kotlinJniBridgeType) + KotlinPlatform.NATIVE -> getCNativeBridgeType(kotlinJniBridgeType) + } + } + + fun getCNativeBridgeType(kotlinJniBridgeType: String) = when (kotlinJniBridgeType) { + "Unit" -> "void" + "Byte" -> "int8_t" + "Short" -> "int16_t" + "Int" -> "int32_t" + "Long" -> "int64_t" + "Float" -> "float" + "Double" -> "double" // TODO: float32_t, float64_t? + "NativePtr" -> "void*" + else -> TODO(kotlinJniBridgeType) + } + /** * Returns the C type to be used for value of given Kotlin type in JNI function implementation. */ @@ -819,7 +870,9 @@ class StubGenerator( "Byte" -> "jbyte" "Short" -> "jshort" "Int" -> "jint" - "Long" -> "jlong" + "Long", "NativePtr" -> "jlong" + "Float" -> "jfloat" + "Double" -> "jdouble" else -> throw NotImplementedError(kotlinJniBridgeType) } @@ -828,7 +881,9 @@ class StubGenerator( */ fun generateCFile(headerFiles: List) { out("#include ") - out("#include ") + if (platform == KotlinPlatform.JVM) { + out("#include ") + } headerFiles.forEach { out("#include <$it>") } @@ -856,11 +911,11 @@ class StubGenerator( if (paramBindings.isEmpty()) "" else paramBindings - .map { getCJniBridgeType(it.kotlinJniBridgeType) } + .map { getCBridgeType(it.kotlinJniBridgeType) } .mapIndexed { i, type -> "$type ${paramNames[i]}" } .joinToString(separator = ", ", prefix = ", ") - val cReturnType = getCJniBridgeType(retValBinding.kotlinJniBridgeType) + val cReturnType = getCBridgeType(retValBinding.kotlinJniBridgeType) val params = func.parameters.mapIndexed { i, parameter -> val cType = parameter.type.getStringRepresentation() @@ -872,14 +927,26 @@ class StubGenerator( }.joinToString(", ") val callExpr = "${func.name}($params)" - val funcFullName = if (pkgName.isEmpty()) { - "externals.${func.name}" - } else { - "$pkgName.externals.${func.name}" + val jniFuncName = when (platform) { + KotlinPlatform.JVM -> { + val funcFullName = if (pkgName.isEmpty()) { + "externals.${func.name}" + } else { + "$pkgName.externals.${func.name}" + } + "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") + } + KotlinPlatform.NATIVE -> { + func.stubSymbolName + } } - val jniFuncName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024") - block("JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args)") { + val funcDecl = when (platform) { + KotlinPlatform.JVM -> "JNIEXPORT $cReturnType JNICALL $jniFuncName (JNIEnv *env, jobject obj$args)" + KotlinPlatform.NATIVE -> "$cReturnType $jniFuncName (void* obj$args)" + } + + block(funcDecl) { if (cReturnType == "void") { out("$callExpr;") diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index fb86c6ba379..8d327b953c7 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -99,6 +99,10 @@ private fun processLib(ktGenRoot: String, val args = additionalArgs.groupBy ({ getArgPrefix(it)!! }, { dropPrefix(it)!! }) // TODO + val platformName = args["-target"]?.single() ?: "jvm" + + val platform = KotlinPlatform.values().single { it.name.equals(platformName, ignoreCase = true) } + val defFile = args["-def"]?.single()?.let { File(it) } val config = Properties() @@ -133,7 +137,7 @@ private fun processLib(ktGenRoot: String, val nativeIndex = buildNativeIndex(headerFiles, compilerOpts) - val gen = StubGenerator(nativeIndex, outKtPkg, libName, excludedFunctions, generateShims) + val gen = StubGenerator(nativeIndex, outKtPkg, libName, excludedFunctions, generateShims, platform) outKtFile.parentFile.mkdirs() outKtFile.bufferedWriter().use { out -> @@ -151,36 +155,50 @@ private fun processLib(ktGenRoot: String, } } - val outOFile = createTempFile(suffix = ".o") - - val javaHome = System.getProperty("java.home") - val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray() - - val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), - *compilerArgsForJniIncludes, - "-c", outCFile.path, "-o", outOFile.path) + File(nativeLibsDir).mkdirs() val workDir = defFile?.parentFile ?: File(System.getProperty("java.io.tmpdir")) - ProcessBuilder(*compilerCmd) - .directory(workDir) - .inheritIO() - .runExpectingSuccess() + if (platform == KotlinPlatform.JVM) { - File(nativeLibsDir).mkdirs() + val outOFile = createTempFile(suffix = ".o") - val outLib = nativeLibsDir + "/" + System.mapLibraryName(libName) + val javaHome = System.getProperty("java.home") + val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray() - val linkerCmd = arrayOf("$llvmInstallPath/bin/$linker", *linkerOpts, outOFile.path, "-shared", "-o", outLib, - "-Wl,-flat_namespace,-undefined,dynamic_lookup") + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), + *compilerArgsForJniIncludes, + "-c", outCFile.path, "-o", outOFile.path) - ProcessBuilder(*linkerCmd) - .directory(workDir) - .inheritIO() - .runExpectingSuccess() + ProcessBuilder(*compilerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + + val outLib = nativeLibsDir + "/" + System.mapLibraryName(libName) + + val linkerCmd = arrayOf("$llvmInstallPath/bin/$linker", *linkerOpts, outOFile.path, "-shared", "-o", outLib, + "-Wl,-flat_namespace,-undefined,dynamic_lookup") + + ProcessBuilder(*linkerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + + outOFile.delete() + } else if (platform == KotlinPlatform.NATIVE) { + val outBcName = libName + ".bc" + val outLib = nativeLibsDir + "/" + outBcName + val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), + "-emit-llvm", "-c", outCFile.path, "-o", outLib) + + ProcessBuilder(*compilerCmd) + .directory(workDir) + .inheritIO() + .runExpectingSuccess() + } outCFile.delete() - outOFile.delete() } private fun buildNativeIndex(headerFiles: List, compilerOpts: List): NativeIndex { diff --git a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy index 8a0d718cc9d..5e2bcd47ab0 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/kotlin/NativeInteropPlugin.groovy @@ -18,15 +18,19 @@ class NamedNativeInteropConfig implements Named { private final Project project final String name - private final SourceSet interopStubs - private final JavaExec genTask + + private String interopStubsName + private SourceSet interopStubs + final JavaExec genTask + + private String target = "jvm" private String defFile private String pkg; private List compilerOpts = [] - private FileCollection headers; + private List headers; private String linker private List linkerOpts = [] private FileCollection linkFiles; @@ -34,6 +38,10 @@ class NamedNativeInteropConfig implements Named { Configuration configuration + void target(String value) { + target = value + } + void defFile(String value) { defFile = value genTask.inputs.file(project.file(defFile)) @@ -49,7 +57,11 @@ class NamedNativeInteropConfig implements Named { void headers(FileCollection files) { dependsOnFiles(files) - headers = headers + files + headers = headers + files.toSet().collect { it.absolutePath } + } + + void headers(String... values) { + headers = headers + values.toList() } void linker(String value) { @@ -107,11 +119,11 @@ class NamedNativeInteropConfig implements Named { compilerOpts.addAll(values.collect {"-I$it"}) } - private File getNativeLibsDir() { + File getNativeLibsDir() { return new File(project.buildDir, "nativelibs") } - private File getGeneratedSrcDir() { + File getGeneratedSrcDir() { return new File(project.buildDir, "nativeInteropStubs/$name/kotlin") } @@ -119,25 +131,31 @@ class NamedNativeInteropConfig implements Named { this.name = name this.project = project - this.headers = project.files() + this.headers = [] this.linkFiles = project.files() - interopStubs = project.sourceSets.create(name + "InteropStubs") - genTask = project.task(interopStubs.getTaskName("gen", ""), type: JavaExec) - configuration = project.configurations.create(interopStubs.name) + interopStubsName = name + "InteropStubs" + genTask = project.task("gen" + interopStubsName.capitalize(), type: JavaExec) this.configure() } private void configure() { - project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) { - dependsOn genTask - } + if (project.plugins.hasPlugin("kotlin")) { + interopStubs = project.sourceSets.create(interopStubsName) + configuration = project.configurations.create(interopStubs.name) + project.tasks.getByName(interopStubs.getTaskName("compile", "Kotlin")) { + dependsOn genTask + } - interopStubs.kotlin.srcDirs generatedSrcDir + interopStubs.kotlin.srcDirs generatedSrcDir - project.dependencies { - add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime') + project.dependencies { + add interopStubs.getCompileConfigurationName(), project(path: ':Interop:Runtime') + } + + this.configuration.extendsFrom project.configurations[interopStubs.runtimeConfigurationName] + project.dependencies.add(this.configuration.name, interopStubs.output) } genTask.configure { @@ -166,6 +184,9 @@ class NamedNativeInteropConfig implements Named { linkerOpts += linkFiles.files args = [generatedSrcDir, nativeLibsDir] + + args "-target:" + this.target + if (defFile != null) { args "-def:" + project.file(defFile) } @@ -188,7 +209,7 @@ class NamedNativeInteropConfig implements Named { args compilerOpts.collect { "-copt:$it" } args linkerOpts.collect { "-lopt:$it" } - headers.files.each { + headers.each { args "-h:$it" } @@ -198,9 +219,6 @@ class NamedNativeInteropConfig implements Named { } } - - this.configuration.extendsFrom project.configurations[interopStubs.runtimeConfigurationName] - project.dependencies.add(this.configuration.name, interopStubs.output) } } From 97cd2b4d5fa36e274012cfb4c49d2effee1b794b Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 3 Feb 2017 15:10:10 +0700 Subject: [PATCH 11/13] backend: implement '-nativelibrary' command line argument --- .../cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt | 3 +++ .../jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.java | 4 ++++ .../src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt | 3 +++ .../jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt | 2 ++ .../src/org/jetbrains/kotlin/backend/konan/LinkStage.kt | 2 +- 5 files changed, 13 insertions(+), 1 deletion(-) 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 0b906b0c73d..539f458a5d6 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 @@ -62,6 +62,9 @@ class K2Native : CLICompiler() { put(LIBRARY_FILES, arguments.libraries.toNonNullList()) + put(NATIVE_LIBRARY_FILES, + arguments.nativeLibraries.toNonNullList()) + // TODO: Collect all the explicit file names into an object // and teach the compiler to work with temporaries and -save-temps. val bitcodeFile = if (arguments.nolink) { diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.java b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.java index a5904a15c8d..3f5b742207f 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.java +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.java @@ -21,6 +21,10 @@ public class K2NativeCompilerArguments extends CommonCompilerArguments { @ValueDescription("") public String[] libraries; + @Argument(value = "nativelibrary", alias = "nl", description = "Link with the native library") + @ValueDescription("") + public String[] nativeLibraries; + @Argument(value = "nolink", description = "Don't link, just produce a bitcode file") public boolean nolink; diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index 53b8fe550ea..61dfa408d34 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -29,6 +29,9 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration private val loadedDescriptors = loadLibMetadata(libraries) + internal val librariesToLink: List + get() = libraries + configuration.getList(KonanConfigKeys.NATIVE_LIBRARY_FILES) + val moduleId: String get() = configuration.getNotNull(CommonConfigurationKeys.MODULE_NAME) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index 97af9c0eef5..0f8d7982470 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -7,6 +7,8 @@ class KonanConfigKeys { companion object { val LIBRARY_FILES: CompilerConfigurationKey> = CompilerConfigurationKey.create("library file paths") + val NATIVE_LIBRARY_FILES: CompilerConfigurationKey> + = CompilerConfigurationKey.create("native library file paths") val BITCODE_FILE: CompilerConfigurationKey = CompilerConfigurationKey.create("emitted bitcode file path") val EXECUTABLE_FILE: CompilerConfigurationKey diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index dad44117626..e0a13aaab54 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -144,7 +144,7 @@ internal class LinkStage(val context: Context) { val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false val emitted = config.get(KonanConfigKeys.BITCODE_FILE)!! val nostdlib = config.get(KonanConfigKeys.NOSTDLIB) ?: false - val libraries = context.config.libraries + val libraries = context.config.librariesToLink fun llvmLto(files: List): ObjectFile { val tmpCombined = File.createTempFile("combined", ".o") From 5e54c29afbd8b7474cec05b8241af505c3a77ea4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 3 Feb 2017 16:14:34 +0700 Subject: [PATCH 12/13] buildSrc: implement RunInteropKonanTest --- .../org/jetbrains/kotlin/KonanTest.groovy | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/buildSrc/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy b/buildSrc/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy index f7d1cf8181b..b82d033d7c5 100644 --- a/buildSrc/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy +++ b/buildSrc/src/main/groovy/org/jetbrains/kotlin/KonanTest.groovy @@ -158,6 +158,28 @@ class RunKonanTest extends KonanTest { } } +@ParallelizableTask +class RunInteropKonanTest extends KonanTest { + + private String interop + private NamedNativeInteropConfig interopConf + + void setInterop(String value) { + this.interop = value + this.interopConf = project.kotlinNativeInterop[value] + this.dependsOn(this.interopConf.genTask) + } + + void compileTest(List filesToCompile, String exe) { + String interopBc = exe + "-interop.bc" + runCompiler([interopConf.generatedSrcDir.absolutePath], interopBc, ["-nolink"]) + + String interopStubsBc = new File(interopConf.nativeLibsDir, interop + "stubs.bc").absolutePath + + runCompiler(filesToCompile, exe, ["-library", interopBc, "-nativelibrary", interopStubsBc]) + } +} + @ParallelizableTask class LinkKonanTest extends KonanTest { protected String lib From 56bab774f154e29e20985b47d8e007d840582a92 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Fri, 3 Feb 2017 16:10:26 +0700 Subject: [PATCH 13/13] backend/tests: add interop0 --- backend.native/tests/build.gradle | 16 ++++++++++++++++ backend.native/tests/interop/basics/0.kt | 9 +++++++++ 2 files changed, 25 insertions(+) create mode 100644 backend.native/tests/interop/basics/0.kt diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 2a8091bfb5e..508ea4e4235 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1,6 +1,8 @@ import groovy.json.JsonOutput import org.jetbrains.kotlin.* +apply plugin: NativeInteropPlugin + configurations { cli_bc } @@ -1047,3 +1049,17 @@ task inline3(type: RunKonanTest) { task vararg0(type: RunKonanTest) { source = "lower/vararg.kt" } + +kotlinNativeInterop { + sysstat { + pkg 'sysstat' + headers 'sys/stat.h' + target 'native' + } +} + +task interop0(type: RunInteropKonanTest) { + goldValue = "0\n0\n" + source = "interop/basics/0.kt" + interop = 'sysstat' +} \ No newline at end of file diff --git a/backend.native/tests/interop/basics/0.kt b/backend.native/tests/interop/basics/0.kt new file mode 100644 index 00000000000..862a4f30b33 --- /dev/null +++ b/backend.native/tests/interop/basics/0.kt @@ -0,0 +1,9 @@ +import sysstat.* +import kotlinx.cinterop.* + +fun main(args: Array) { + val statBuf = nativeHeap.alloc() + val res = stat("/", statBuf.ptr) + println(res) + println(statBuf.st_uid.value) +} \ No newline at end of file