diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt index 98bb349a7aa..ca4546c47b7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/OptimizationPipeline.kt @@ -5,6 +5,8 @@ import kotlinx.cinterop.memScoped import kotlinx.cinterop.ptr import kotlinx.cinterop.value import llvm.* +import org.jetbrains.kotlin.backend.konan.llvm.makeVisibilityHiddenLikeLlvmInternalizePass +import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.konan.target.Configurables import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.ZephyrConfigurables @@ -175,6 +177,12 @@ internal fun runLlvmOptimizationPipeline(context: Context) { // Since we are in a "closed world" internalization can be safely used // to reduce size of a bitcode with global dce. LLVMAddInternalizePass(modulePasses, 0) + } else if (context.config.produce == CompilerOutputKind.STATIC_CACHE) { + // Hidden visibility makes symbols internal when linking the binary. + // When producing dynamic library, this enables stripping unused symbols from binary with -dead_strip flag, + // similar to DCE enabled by internalize but later: + makeVisibilityHiddenLikeLlvmInternalizePass(llvmModule) + // Important for binary size, workarounds references to undefined symbols from interop libraries. } LLVMAddGlobalDCEPass(modulePasses) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 6bd90da7537..b485f431bb2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -419,6 +419,18 @@ internal fun node(vararg it:LLVMValueRef) = LLVMMDNodeInContext(llvmContext, it. internal fun LLVMValueRef.setUnaligned() = apply { LLVMSetAlignment(this, 1) } +internal fun getOperands(value: LLVMValueRef) = + (0 until LLVMGetNumOperands(value)).map { LLVMGetOperand(value, it)!! } + +internal fun getGlobalAliases(module: LLVMModuleRef) = + generateSequence(LLVMGetFirstGlobalAlias(module), { LLVMGetNextGlobalAlias(it) }) + +internal fun getFunctions(module: LLVMModuleRef) = + generateSequence(LLVMGetFirstFunction(module), { LLVMGetNextFunction(it) }) + +internal fun getGlobals(module: LLVMModuleRef) = + generateSequence(LLVMGetFirstGlobal(module), { LLVMGetNextGlobal(it) }) + fun LLVMTypeRef.isFloatingPoint(): Boolean = when (llvm.LLVMGetTypeKind(this)) { LLVMTypeKind.LLVMFloatTypeKind, LLVMTypeKind.LLVMDoubleTypeKind -> true else -> false diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/visibility.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/visibility.kt new file mode 100644 index 00000000000..9f23ad0cbc1 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/visibility.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +package org.jetbrains.kotlin.backend.konan.llvm + +import llvm.* +import org.jetbrains.kotlin.utils.DFS + +/** + * Applies hidden visibility to symbols similarly to LLVM's internalize pass: + * it makes hidden the symbols that are made internal by internalize. + */ +fun makeVisibilityHiddenLikeLlvmInternalizePass(module: LLVMModuleRef) { + // Note: the implementation below generally follows InternalizePass::internalizeModule, + // but omits some details for simplicity. + + // TODO: LLVM handles some additional cases. + val alwaysPreserved = getLlvmUsed(module) + + (getFunctions(module) + getGlobals(module) + getGlobalAliases(module)) + .filter { + when (LLVMGetLinkage(it)) { + LLVMLinkage.LLVMInternalLinkage, LLVMLinkage.LLVMPrivateLinkage -> false + else -> true + } + } + .filter { LLVMIsDeclaration(it) == 0 } + .minus(alwaysPreserved) + .forEach { + LLVMSetVisibility(it, LLVMVisibility.LLVMHiddenVisibility) + } +} + +private fun getLlvmUsed(module: LLVMModuleRef): Set { + val llvmUsed = LLVMGetNamedGlobal(module, "llvm.used") ?: return emptySet() + val llvmUsedValue = LLVMGetInitializer(llvmUsed) ?: return emptySet() + + // Note: llvm.used value is an array of globals, wrapped into bitcasts, GEPs and other instructions; + // see llvm::collectUsedGlobalVariables. + // Conservatively extract all involved globals for simplicity: + return DFS.dfs( + /* nodes = */ listOf(llvmUsedValue), + /* neighbors = */ { value -> getOperands(value) }, + object : DFS.CollectingNodeHandler>(mutableSetOf()) { + override fun beforeChildren(current: LLVMValueRef): Boolean = when (LLVMGetValueKind(current)) { + LLVMValueKind.LLVMGlobalAliasValueKind, + LLVMValueKind.LLVMGlobalVariableValueKind, + LLVMValueKind.LLVMFunctionValueKind -> { + result.add(current) + false // Skip children. + } + + else -> true + } + } + ) +}