Native: implement autoreleaseAndRet for ObjCExportCodeGenerator
This operation is equivalent to "autorelease and then return", but it supports Obj-C runtime autorelease optimization for return value: if the caller is optimized (which is usually true), then no autorelease will happen at runtime.
This commit is contained in:
committed by
Space
parent
e00fd6b9b3
commit
75a3070067
+88
-45
@@ -132,7 +132,7 @@ internal inline fun generateFunction(
|
|||||||
|
|
||||||
val isCToKotlinBridge = function.origin == CBridgeOrigin.C_TO_KOTLIN_BRIDGE
|
val isCToKotlinBridge = function.origin == CBridgeOrigin.C_TO_KOTLIN_BRIDGE
|
||||||
|
|
||||||
val functionGenerationContext = FunctionGenerationContext(
|
val functionGenerationContext = DefaultFunctionGenerationContext(
|
||||||
llvmFunction,
|
llvmFunction,
|
||||||
codegen,
|
codegen,
|
||||||
startLocation,
|
startLocation,
|
||||||
@@ -175,7 +175,7 @@ internal inline fun generateFunction(
|
|||||||
switchToRunnable: Boolean = false,
|
switchToRunnable: Boolean = false,
|
||||||
code: FunctionGenerationContext.() -> Unit
|
code: FunctionGenerationContext.() -> Unit
|
||||||
) {
|
) {
|
||||||
val functionGenerationContext = FunctionGenerationContext(
|
val functionGenerationContext = DefaultFunctionGenerationContext(
|
||||||
function,
|
function,
|
||||||
codegen,
|
codegen,
|
||||||
startLocation,
|
startLocation,
|
||||||
@@ -212,7 +212,7 @@ internal inline fun generateFunctionNoRuntime(
|
|||||||
function: LLVMValueRef,
|
function: LLVMValueRef,
|
||||||
code: FunctionGenerationContext.() -> Unit,
|
code: FunctionGenerationContext.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val functionGenerationContext = FunctionGenerationContext(function, codegen, null, null, switchToRunnable = false)
|
val functionGenerationContext = DefaultFunctionGenerationContext(function, codegen, null, null, switchToRunnable = false)
|
||||||
try {
|
try {
|
||||||
functionGenerationContext.forbidRuntime = true
|
functionGenerationContext.forbidRuntime = true
|
||||||
require(!functionGenerationContext.isObjectType(functionGenerationContext.returnType!!)) {
|
require(!functionGenerationContext.isObjectType(functionGenerationContext.returnType!!)) {
|
||||||
@@ -425,7 +425,7 @@ internal abstract class FunctionGenerationContextBuilder<T : FunctionGenerationC
|
|||||||
abstract fun build(): T
|
abstract fun build(): T
|
||||||
}
|
}
|
||||||
|
|
||||||
internal open class FunctionGenerationContext(
|
internal abstract class FunctionGenerationContext(
|
||||||
val function: LLVMValueRef,
|
val function: LLVMValueRef,
|
||||||
val codegen: CodeGenerator,
|
val codegen: CodeGenerator,
|
||||||
private val startLocation: LocationInfo?,
|
private val startLocation: LocationInfo?,
|
||||||
@@ -453,7 +453,6 @@ internal open class FunctionGenerationContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var returnType: LLVMTypeRef? = LLVMGetReturnType(getFunctionType(function))
|
var returnType: LLVMTypeRef? = LLVMGetReturnType(getFunctionType(function))
|
||||||
private val returns: MutableMap<LLVMBasicBlockRef, LLVMValueRef> = mutableMapOf()
|
|
||||||
val constructedClass: IrClass?
|
val constructedClass: IrClass?
|
||||||
get() = (irFunction as? IrConstructor)?.constructedClass
|
get() = (irFunction as? IrConstructor)?.constructedClass
|
||||||
private var returnSlot: LLVMValueRef? = null
|
private var returnSlot: LLVMValueRef? = null
|
||||||
@@ -470,8 +469,7 @@ internal open class FunctionGenerationContext(
|
|||||||
private val localsInitBb = basicBlockInFunction("locals_init", null)
|
private val localsInitBb = basicBlockInFunction("locals_init", null)
|
||||||
private val stackLocalsInitBb = basicBlockInFunction("stack_locals_init", null)
|
private val stackLocalsInitBb = basicBlockInFunction("stack_locals_init", null)
|
||||||
private val entryBb = basicBlockInFunction("entry", startLocation)
|
private val entryBb = basicBlockInFunction("entry", startLocation)
|
||||||
private val epilogueBb = basicBlockInFunction("epilogue", endLocation)
|
protected val cleanupLandingpad = basicBlockInFunction("cleanup_landingpad", endLocation)
|
||||||
private val cleanupLandingpad = basicBlockInFunction("cleanup_landingpad", endLocation)
|
|
||||||
|
|
||||||
val stackLocalsManager = StackLocalsManagerImpl(this, stackLocalsInitBb)
|
val stackLocalsManager = StackLocalsManagerImpl(this, stackLocalsInitBb)
|
||||||
|
|
||||||
@@ -509,7 +507,7 @@ internal open class FunctionGenerationContext(
|
|||||||
currentPositionHolder.dispose()
|
currentPositionHolder.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun basicBlockInFunction(name: String, locationInfo: LocationInfo?): LLVMBasicBlockRef {
|
protected fun basicBlockInFunction(name: String, locationInfo: LocationInfo?): LLVMBasicBlockRef {
|
||||||
val bb = LLVMAppendBasicBlockInContext(llvmContext, function, name)!!
|
val bb = LLVMAppendBasicBlockInContext(llvmContext, function, name)!!
|
||||||
update(bb, locationInfo)
|
update(bb, locationInfo)
|
||||||
return bb
|
return bb
|
||||||
@@ -551,19 +549,7 @@ internal open class FunctionGenerationContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ret(value: LLVMValueRef?): LLVMValueRef {
|
abstract fun ret(value: LLVMValueRef?): LLVMValueRef
|
||||||
val res = LLVMBuildBr(builder, epilogueBb)!!
|
|
||||||
if (returns.containsKey(currentBlock)) {
|
|
||||||
// TODO: enable error throwing.
|
|
||||||
throw Error("ret() in the same basic block twice! in ${function.name}")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value != null)
|
|
||||||
returns[currentBlock] = value
|
|
||||||
|
|
||||||
currentPositionHolder.setAfterTerminator()
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
fun param(index: Int): LLVMValueRef = LLVMGetParam(this.function, index)!!
|
fun param(index: Int): LLVMValueRef = LLVMGetParam(this.function, index)!!
|
||||||
|
|
||||||
@@ -1377,7 +1363,6 @@ internal open class FunctionGenerationContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun prologue() {
|
internal fun prologue() {
|
||||||
assert(returns.isEmpty())
|
|
||||||
if (isObjectType(returnType!!)) {
|
if (isObjectType(returnType!!)) {
|
||||||
returnSlot = LLVMGetParam(function, numParameters(function.type) - 1)
|
returnSlot = LLVMGetParam(function, numParameters(function.type) - 1)
|
||||||
}
|
}
|
||||||
@@ -1447,28 +1432,7 @@ internal open class FunctionGenerationContext(
|
|||||||
br(entryBb)
|
br(entryBb)
|
||||||
}
|
}
|
||||||
|
|
||||||
appendingTo(epilogueBb) {
|
processReturns()
|
||||||
when {
|
|
||||||
returnType == voidType -> {
|
|
||||||
releaseVars()
|
|
||||||
assert(returnSlot == null)
|
|
||||||
handleEpilogueForExperimentalMM(context.llvm.Kotlin_mm_safePointFunctionEpilogue)
|
|
||||||
LLVMBuildRetVoid(builder)
|
|
||||||
}
|
|
||||||
returns.isNotEmpty() -> {
|
|
||||||
val returnPhi = phi(returnType!!)
|
|
||||||
addPhiIncoming(returnPhi, *returns.toList().toTypedArray())
|
|
||||||
if (returnSlot != null) {
|
|
||||||
updateReturnRef(returnPhi, returnSlot!!)
|
|
||||||
}
|
|
||||||
releaseVars()
|
|
||||||
handleEpilogueForExperimentalMM(context.llvm.Kotlin_mm_safePointFunctionEpilogue)
|
|
||||||
LLVMBuildRet(builder, returnPhi)
|
|
||||||
}
|
|
||||||
// Do nothing, all paths throw.
|
|
||||||
else -> LLVMBuildUnreachable(builder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val shouldHaveCleanupLandingpad = forwardingForeignExceptionsTerminatedWith != null ||
|
val shouldHaveCleanupLandingpad = forwardingForeignExceptionsTerminatedWith != null ||
|
||||||
needSlots ||
|
needSlots ||
|
||||||
@@ -1526,12 +1490,38 @@ internal open class FunctionGenerationContext(
|
|||||||
LLVMDeleteBasicBlock(cleanupLandingpad)
|
LLVMDeleteBasicBlock(cleanupLandingpad)
|
||||||
}
|
}
|
||||||
|
|
||||||
returns.clear()
|
|
||||||
vars.clear()
|
vars.clear()
|
||||||
returnSlot = null
|
returnSlot = null
|
||||||
slotsPhi = null
|
slotsPhi = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract fun processReturns()
|
||||||
|
|
||||||
|
protected fun retValue(value: LLVMValueRef): LLVMValueRef {
|
||||||
|
if (returnSlot != null) {
|
||||||
|
updateReturnRef(value, returnSlot!!)
|
||||||
|
}
|
||||||
|
onReturn()
|
||||||
|
return rawRet(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun rawRet(value: LLVMValueRef): LLVMValueRef = LLVMBuildRet(builder, value)!!.also {
|
||||||
|
currentPositionHolder.setAfterTerminator()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun retVoid(): LLVMValueRef {
|
||||||
|
check(returnSlot == null)
|
||||||
|
onReturn()
|
||||||
|
return LLVMBuildRetVoid(builder)!!.also {
|
||||||
|
currentPositionHolder.setAfterTerminator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun onReturn() {
|
||||||
|
releaseVars()
|
||||||
|
handleEpilogueForExperimentalMM(context.llvm.Kotlin_mm_safePointFunctionEpilogue)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleEpilogueForExperimentalMM(safePointFunction: LLVMValueRef) {
|
private fun handleEpilogueForExperimentalMM(safePointFunction: LLVMValueRef) {
|
||||||
if (context.memoryModel == MemoryModel.EXPERIMENTAL) {
|
if (context.memoryModel == MemoryModel.EXPERIMENTAL) {
|
||||||
if (!forbidRuntime) {
|
if (!forbidRuntime) {
|
||||||
@@ -1681,5 +1671,58 @@ internal open class FunctionGenerationContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class DefaultFunctionGenerationContext(
|
||||||
|
function: LLVMValueRef,
|
||||||
|
codegen: CodeGenerator,
|
||||||
|
startLocation: LocationInfo?,
|
||||||
|
endLocation: LocationInfo?,
|
||||||
|
switchToRunnable: Boolean,
|
||||||
|
irFunction: IrFunction? = null
|
||||||
|
) : FunctionGenerationContext(
|
||||||
|
function,
|
||||||
|
codegen,
|
||||||
|
startLocation,
|
||||||
|
endLocation,
|
||||||
|
switchToRunnable,
|
||||||
|
irFunction
|
||||||
|
) {
|
||||||
|
// Note: return handling can be extracted to a separate class.
|
||||||
|
|
||||||
|
private val returns: MutableMap<LLVMBasicBlockRef, LLVMValueRef> = mutableMapOf()
|
||||||
|
|
||||||
|
private val epilogueBb = basicBlockInFunction("epilogue", endLocation).also {
|
||||||
|
LLVMMoveBasicBlockBefore(it, cleanupLandingpad) // Just to make the produced code a bit more readable.
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ret(value: LLVMValueRef?): LLVMValueRef {
|
||||||
|
val res = br(epilogueBb)
|
||||||
|
|
||||||
|
if (returns.containsKey(currentBlock)) {
|
||||||
|
// TODO: enable error throwing.
|
||||||
|
throw Error("ret() in the same basic block twice! in ${function.name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null)
|
||||||
|
returns[currentBlock] = value
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processReturns() {
|
||||||
|
appendingTo(epilogueBb) {
|
||||||
|
when {
|
||||||
|
returnType == voidType -> {
|
||||||
|
retVoid()
|
||||||
|
}
|
||||||
|
returns.isNotEmpty() -> {
|
||||||
|
val returnPhi = phi(returnType!!)
|
||||||
|
addPhiIncoming(returnPhi, *returns.toList().toTypedArray())
|
||||||
|
retValue(returnPhi)
|
||||||
|
}
|
||||||
|
// Do nothing, all paths throw.
|
||||||
|
else -> unreachable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
returns.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+8
@@ -35,6 +35,14 @@ internal open class ObjCCodeGenerator(val codegen: CodeGenerator) {
|
|||||||
context.stdlibModule.llvmSymbolOrigin
|
context.stdlibModule.llvmSymbolOrigin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val objcAutoreleaseReturnValue = context.llvm.externalFunction(
|
||||||
|
"llvm.objc.autoreleaseReturnValue",
|
||||||
|
functionType(int8TypePtr, false, int8TypePtr),
|
||||||
|
context.stdlibModule.llvmSymbolOrigin
|
||||||
|
).also {
|
||||||
|
setFunctionNoUnwind(it)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: this doesn't support stret.
|
// TODO: this doesn't support stret.
|
||||||
fun msgSender(functionType: LLVMTypeRef): LLVMValueRef =
|
fun msgSender(functionType: LLVMTypeRef): LLVMValueRef =
|
||||||
objcMsgSend.bitcast(pointerType(functionType)).llvm
|
objcMsgSend.bitcast(pointerType(functionType)).llvm
|
||||||
|
|||||||
+31
@@ -52,6 +52,37 @@ internal class ObjCExportFunctionGenerationContext(
|
|||||||
) : FunctionGenerationContext(builder) {
|
) : FunctionGenerationContext(builder) {
|
||||||
private val objCExportCodegen = builder.objCExportCodegen
|
private val objCExportCodegen = builder.objCExportCodegen
|
||||||
|
|
||||||
|
// Note: we could generate single "epilogue" and make all [ret]s just branch to it (like [DefaultFunctionGenerationContext]),
|
||||||
|
// but this would be useless for most of the usages, which have only one [ret].
|
||||||
|
// Remaining usages can be optimized ad hoc.
|
||||||
|
|
||||||
|
override fun ret(value: LLVMValueRef?): LLVMValueRef = if (value == null) {
|
||||||
|
retVoid()
|
||||||
|
} else {
|
||||||
|
retValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* autoreleases and returns [value].
|
||||||
|
* It is equivalent to `ret(autorelease(value))`, but optimizes the autorelease out if the caller is prepared for it.
|
||||||
|
*
|
||||||
|
* See the Clang documentation and the Obj-C runtime source code for more details:
|
||||||
|
* https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-autoreleasereturnvalue
|
||||||
|
* https://github.com/opensource-apple/objc4/blob/cd5e62a5597ea7a31dccef089317abb3a661c154/runtime/objc-object.h#L930
|
||||||
|
*/
|
||||||
|
fun autoreleaseAndRet(value: LLVMValueRef) {
|
||||||
|
onReturn()
|
||||||
|
// Note: it is important to make this call tail (otherwise the elimination magic won't work),
|
||||||
|
// so it should go after other "epilogue" instructions, and that's why we couldn't just use
|
||||||
|
// ret(autorelease(value))
|
||||||
|
val result = call(objCExportCodegen.objcAutoreleaseReturnValue, listOf(value))
|
||||||
|
LLVMSetTailCall(result, 1)
|
||||||
|
rawRet(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processReturns() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ObjCExportFunctionGenerationContextBuilder(
|
internal class ObjCExportFunctionGenerationContextBuilder(
|
||||||
|
|||||||
Reference in New Issue
Block a user