[K/N] Remove LLVM coverage

The -Xcoverage feature has not worked and has been disabled for a while.
This fix removes it, and all of its uses.


Co-authored-by: Troels Lund <troels@google.com>


Merge-request: KOTLIN-MR-821
Merged-by: Alexander Shabalin <alexander.shabalin@jetbrains.com>
This commit is contained in:
Troels Bjerre Lund
2023-12-06 14:07:16 +00:00
committed by Space Cloud
parent e0c931f69d
commit 4f77434ea5
46 changed files with 24 additions and 1763 deletions
@@ -57,18 +57,6 @@ internal fun getIncludedLibraries(
allowDefaultLibs = false
)
internal fun getCoveredLibraries(
configuration: CompilerConfiguration,
resolvedLibraries: KotlinLibraryResolveResult,
resolver: SearchPathResolver<KonanLibrary>
): List<KonanLibrary> = getFeaturedLibraries(
configuration.getList(KonanConfigKeys.LIBRARIES_TO_COVER),
resolvedLibraries,
resolver,
FeaturedLibrariesReporter.forCoveredLibraries(configuration),
allowDefaultLibs = true
)
private sealed class FeaturedLibrariesReporter {
abstract fun reportIllegalKind(library: KonanLibrary)
@@ -308,9 +308,6 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
return resolvedLibraries.filterRoots { (!it.isDefault && !this.purgeUserLibs) || it.isNeededForLink }.getFullList(TopologicalLibraryOrder).map { it as KonanLibrary }
}
val shouldCoverSources = configuration.getBoolean(KonanConfigKeys.COVERAGE)
private val shouldCoverLibraries = !configuration.getList(KonanConfigKeys.LIBRARIES_TO_COVER).isNullOrEmpty()
private val defaultAllocationMode get() =
if (sanitizer == null)
AllocationMode.CUSTOM
@@ -377,7 +374,6 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
GC.CONCURRENT_MARK_AND_SWEEP -> add("concurrent_ms_gc.bc")
}
}
if (shouldCoverLibraries || shouldCoverSources) add("profileRuntime.bc")
if (target.supportsCoreSymbolication()) {
add("source_info_core_symbolication.bc")
}
@@ -135,12 +135,6 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("verify compiler")
val DEBUG_INFO_VERSION: CompilerConfigurationKey<Int>
= CompilerConfigurationKey.create("debug info format version")
val COVERAGE: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("emit coverage info for sources")
val LIBRARIES_TO_COVER: CompilerConfigurationKey<List<String>>
= CompilerConfigurationKey.create("libraries that should be covered")
val PROFRAW_PATH: CompilerConfigurationKey<String?>
= CompilerConfigurationKey.create("path to *.profraw coverage output")
val OBJC_GENERICS: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("write objc header with generics support")
val DEBUG_PREFIX_MAP: CompilerConfigurationKey<Map<String, String>>
@@ -71,9 +71,6 @@ class KonanLibrariesResolveSupport(
internal val exportedLibraries =
getExportedLibraries(configuration, resolvedLibraries, resolver.searchPathResolver, report = true)
internal val coveredLibraries =
getCoveredLibraries(configuration, resolvedLibraries, resolver.searchPathResolver)
internal val includedLibraries =
getIncludedLibraries(includedLibraryFiles, configuration, resolvedLibraries)
}
@@ -40,7 +40,6 @@ internal fun determineLinkerOutput(context: PhaseContext): LinkerOutputKind =
internal class Linker(
private val config: KonanConfig,
private val linkerOutput: LinkerOutputKind,
private val isCoverageEnabled: Boolean = false,
private val outputFiles: OutputFiles,
) {
private val platform = config.platform
@@ -147,7 +146,6 @@ internal class Linker(
debug = debug,
kind = linkerOutput,
outputDsymBundle = outputFiles.symbolicInfoFile,
needsProfileLibrary = isCoverageEnabled,
mimallocEnabled = config.allocationMode == AllocationMode.MIMALLOC,
sanitizer = config.sanitizer
)
@@ -189,4 +187,4 @@ internal fun runLinkerCommands(context: PhaseContext, commands: List<Command>, c
val extraInfo = listOfNotNull(extraUserInfo, extraUserSetupInfo).joinToString(separator = "\n")
context.reportCompilationError("${e.toolName} invocation reported errors\n$extraInfo\n${e.message}")
}
}
@@ -13,7 +13,6 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
import org.jetbrains.kotlin.backend.konan.driver.utilities.BackendContextHolder
import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.coverage.CoverageManager
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
import org.jetbrains.kotlin.backend.konan.serialization.SerializedClassFields
import org.jetbrains.kotlin.backend.konan.serialization.SerializedEagerInitializedFile
@@ -101,8 +100,6 @@ internal class NativeGenerationState(
val virtualFunctionTrampolines = mutableMapOf<IrSimpleFunction, LlvmCallable>()
val coverage by lazy { CoverageManager(this) }
lateinit var objCExport: ObjCExport
fun hasDebugInfo() = debugInfoDelegate.isInitialized()
@@ -144,4 +141,4 @@ internal class NativeGenerationState(
override val llvmModule: LLVMModuleRef
get() = llvm.module
}
}
@@ -172,9 +172,6 @@ fun CompilerConfiguration.setupFromArguments(arguments: K2NativeCompilerArgument
put(BITCODE_EMBEDDING_MODE, selectBitcodeEmbeddingMode(this@setupFromArguments, arguments))
put(DEBUG_INFO_VERSION, arguments.debugInfoFormatVersion.toInt())
put(COVERAGE, arguments.coverage)
put(LIBRARIES_TO_COVER, arguments.coveredLibraries.toNonNullList())
arguments.coverageFile?.let { put(PROFRAW_PATH, it) }
put(OBJC_GENERICS, !arguments.noObjcGenerics)
put(DEBUG_PREFIX_MAP, parseDebugPrefixMap(arguments, this@setupFromArguments))
@@ -20,7 +20,6 @@ import org.jetbrains.kotlin.backend.konan.driver.PhaseEngine
import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
import org.jetbrains.kotlin.backend.konan.driver.utilities.getDefaultLlvmModuleActions
import org.jetbrains.kotlin.backend.konan.insertAliasToEntryPoint
import org.jetbrains.kotlin.backend.konan.llvm.coverage.runCoveragePass
import org.jetbrains.kotlin.backend.konan.llvm.verifyModule
import org.jetbrains.kotlin.backend.konan.optimizations.RemoveRedundantSafepointsPass
import org.jetbrains.kotlin.backend.konan.optimizations.removeMultipleThreadDataLoads
@@ -102,13 +101,6 @@ internal val ThreadSanitizerPhase = optimizationPipelinePass(
pipeline = ::ThreadSanitizerPipeline,
)
internal val CoveragePhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
name = "Coverage",
description = "Produce coverage information",
postactions = getDefaultLlvmModuleActions(),
op = { context, _ -> runCoveragePass(context) }
)
internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase<BitcodePostProcessingContext, Unit>(
name = "RemoveRedundantSafepoints",
description = "Remove function prologue safepoints inlined to another function",
@@ -172,9 +164,6 @@ internal fun <T : BitcodePostProcessingContext> PhaseEngine<T>.runBitcodePostPro
null -> {}
}
}
if (context is NativeGenerationState && context.coverage.enabled) {
newEngine(context) { it.runPhase(CoveragePhase) }
}
if (context.config.memoryModel == MemoryModel.EXPERIMENTAL) {
runPhase(RemoveRedundantSafepointsPhase)
}
@@ -19,7 +19,6 @@ internal data class LinkerPhaseInput(
val dependenciesTrackingResult: DependenciesTrackingResult,
val outputFiles: OutputFiles,
val resolvedCacheBinaries: ResolvedCacheBinaries,
val isCoverageEnabled: Boolean,
)
internal val LinkerPhase = createSimpleNamedCompilerPhase<PhaseContext, LinkerPhaseInput>(
@@ -29,7 +28,6 @@ internal val LinkerPhase = createSimpleNamedCompilerPhase<PhaseContext, LinkerPh
val linker = Linker(
config = context.config,
linkerOutput = input.outputKind,
isCoverageEnabled = input.isCoverageEnabled,
outputFiles = input.outputFiles
)
val commands = linker.linkCommands(
@@ -54,4 +52,4 @@ internal val PreLinkCachesPhase = createSimpleNamedCompilerPhase<PhaseContext, P
val inputFiles = input.objectFiles.map { it.absoluteFile.normalize().path } + input.caches.static
val commands = context.config.platform.linker.preLinkCommands(inputFiles, input.outputObjectFile.absoluteFile.normalize().path)
runLinkerCommands(context, commands, cachingInvolved = true)
}
}
@@ -110,7 +110,7 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBackend(backendContext: Contex
depsFilePath.File().writeLines(DependenciesTrackingResult.serialize(dependenciesTrackingResult))
}
val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependenciesTrackingResult)
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles)
}
} finally {
tempFiles.dispose()
@@ -163,7 +163,7 @@ internal fun <C : PhaseContext> PhaseEngine<C>.runBitcodeBackend(context: Bitcod
bitcodeEngine.runBitcodePostProcessing()
runPhase(WriteBitcodeFilePhase, WriteBitcodeFileInput(context.llvm.module, bitcodeFile))
val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependencies)
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles)
}
}
@@ -269,7 +269,6 @@ internal fun <C : PhaseContext> PhaseEngine<C>.compileAndLink(
linkerOutputFile: String,
outputFiles: OutputFiles,
temporaryFiles: TempFiles,
isCoverageEnabled: Boolean,
) {
val compilationResult = temporaryFiles.create(File(outputFiles.nativeBinaryFile).name, ".o").javaFile()
runPhase(ObjectFilesPhase, ObjectFilesPhaseInput(moduleCompilationOutput.bitcodeFile, compilationResult))
@@ -298,7 +297,6 @@ internal fun <C : PhaseContext> PhaseEngine<C>.compileAndLink(
moduleCompilationOutput.dependenciesTrackingResult,
outputFiles,
cacheBinaries,
isCoverageEnabled = isCoverageEnabled
)
runPhase(LinkerPhase, linkerPhaseInput)
if (context.config.produce.isCache) {
@@ -388,4 +386,4 @@ private fun PhaseEngine<NativeGenerationState>.findDependenciesToCompile(): List
// This guarantees that libraries initializers are emitted in correct order.
private fun mergeDependencies(targetModule: IrModuleFragment, dependencies: List<IrModuleFragment>) {
targetModule.files.addAll(0, dependencies.flatMap { it.files })
}
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.backend.konan.cexport.CAdapterExportedElements
import org.jetbrains.kotlin.backend.konan.cgen.CBridgeOrigin
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.ir.*
import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation
import org.jetbrains.kotlin.backend.konan.lower.*
import org.jetbrains.kotlin.builtins.UnsignedType
import org.jetbrains.kotlin.descriptors.Modality
@@ -472,8 +471,6 @@ internal class CodeGeneratorVisitor(
override fun visitModuleFragment(declaration: IrModuleFragment) {
context.log{"visitModule : ${ir2string(declaration)}"}
generationState.coverage.collectRegions(declaration)
initializeCachedBoxes(generationState)
declaration.acceptChildrenVoid(this)
@@ -483,7 +480,6 @@ internal class CodeGeneratorVisitor(
codegen.objCDataGenerator?.finishModule()
generationState.coverage.writeRegionInfo()
overrideRuntimeGlobals()
appendLlvmUsed("llvm.used", llvm.usedFunctions.map { it.toConstPointer().llvm } + llvm.usedGlobals)
appendLlvmUsed("llvm.compiler.used", llvm.compilerUsedGlobals)
@@ -744,9 +740,6 @@ internal class CodeGeneratorVisitor(
constructor(llvmFunction: LlvmCallable, functionGenerationContext: FunctionGenerationContext) :
this(functionGenerationContext, null, llvmFunction)
val coverageInstrumentation: LLVMCoverageInstrumentation? =
generationState.coverage.tryGetInstrumentation(declaration) { function, args -> functionGenerationContext.call(function, args) }
override fun genReturn(target: IrSymbolOwner, value: LLVMValueRef?) {
if (declaration == null || target == declaration) {
if ((target as IrFunction).returnsUnit()) {
@@ -884,7 +877,6 @@ internal class CodeGeneratorVisitor(
val parameterScope = ParameterScope(declaration, functionGenerationContext)
using(parameterScope) usingParameterScope@{
using(VariableScope()) usingVariableScope@{
recordCoverage(body)
if (declaration.isReifiedInline) {
callDirect(context.ir.symbols.throwIllegalStateExceptionWithMessage.owner,
listOf(codegen.staticData.kotlinStringLiteral(
@@ -974,18 +966,10 @@ internal class CodeGeneratorVisitor(
}
}
private fun recordCoverage(irElement: IrElement) {
val scope = currentCodeContext.functionScope()
if (scope is FunctionScope) {
scope.coverageInstrumentation?.instrumentIrElement(irElement)
}
}
//-------------------------------------------------------------------------//
private fun evaluateExpression(value: IrExpression, resultSlot: LLVMValueRef? = null): LLVMValueRef {
updateBuilderDebugLocation(value)
recordCoverage(value)
when (value) {
is IrTypeOperatorCall -> return evaluateTypeOperator (value, resultSlot)
is IrCall -> return evaluateCall (value, resultSlot)
@@ -27,10 +27,6 @@ class LlvmCallable(val functionType: LLVMTypeRef, private val llvmValue: LLVMVal
LLVMCountParamTypes(functionType)
}
val pgoFunctionNameVar by lazy {
LLVMCreatePGOFunctionNameVar(llvmValue, name)!!
}
val isConstant by lazy {
LLVMIsConstant(llvmValue) == 1
}
@@ -82,4 +78,4 @@ class LlvmCallable(val functionType: LLVMTypeRef, private val llvmValue: LLVMVal
// these functions are potentially unsafe, as they need to use same attribute provider when converted to callable
internal fun toConstPointer() = constPointer(llvmValue)
internal fun asCallback() = llvmValue
}
}
@@ -1,95 +0,0 @@
/*
* Copyright 2010-2019 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.coverage
import org.jetbrains.kotlin.backend.konan.llvm.computeSymbolName
import org.jetbrains.kotlin.backend.konan.llvm.lineAndColumn
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.name
import org.jetbrains.kotlin.ir.util.ir2string
/**
* The most important class in the coverage package.
* It describes textual region of the code that is associated with IrElement.
* Besides the obvious [file] and line/column borders, it has [RegionKind] which is described later.
*/
class Region(
val startOffset: Int,
val endOffset: Int,
val file: IrFile,
val kind: RegionKind
) {
val startLineAndColumn: Pair<Int, Int>
get() = file.fileEntry.lineAndColumn(startOffset)
val endLineAndColumn: Pair<Int, Int>
get() = file.fileEntry.lineAndColumn(endOffset)
companion object {
fun fromIr(irElement: IrElement, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
fromOffset(irElement.startOffset, irElement.endOffset, irFile, kind)
fun fromOffset(startOffset: Int, endOffset: Int, irFile: IrFile, kind: RegionKind = RegionKind.Code) =
if (startOffset == UNDEFINED_OFFSET || endOffset == UNDEFINED_OFFSET || startOffset == endOffset) null
else Region(startOffset, endOffset, irFile, kind)
}
override fun toString(): String {
val expansion = (kind as? RegionKind.Expansion)?.let { " expand to " + it.expandedFile.name } ?: ""
return "${file.name}$expansion: ${kind::class.simpleName} $startLineAndColumn -> $endLineAndColumn"
}
}
/**
* Describes what is the given code region.
* Based on llvm::coverage::CounterMappingRegion.
* Currently only [RegionKind.Code] is used.
*/
sealed class RegionKind {
/**
* Regular peace of code.
*/
object Code : RegionKind()
/**
* Empty line.
*/
object Gap : RegionKind()
/**
* Region of code that is an expansion of another source file.
* Used for inline function.
*/
class Expansion(val expandedFile: IrFile) : RegionKind()
}
/**
* "Regional" description of the [function].
*/
class FunctionRegions(
val function: IrFunction,
val regions: Map<IrElement, Region>
) {
// Enumeration is required for serialization and instrumentation calls.
val regionEnumeration = regions.values.mapIndexed { index, region -> region to index }.toMap()
// Actually, it should be computed.
// But since we don't support PGO structural hash doesn't really matter for now.
val structuralHash: Long = 0
override fun toString(): String = buildString {
appendLine("${function.computeSymbolName()} regions:")
regions.forEach { (irElem, region) -> appendLine("${ir2string(irElem)} -> ($region)") }
}
}
/**
* Since file is the biggest unit in terms of the code coverage
* we aggregate [FunctionRegions] per [file].
*/
class FileRegionInfo(
val file: IrFile,
val functions: List<FunctionRegions>
)
@@ -1,137 +0,0 @@
/*
* Copyright 2010-2019 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.coverage
import llvm.*
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.llvm.LlvmCallable
import org.jetbrains.kotlin.backend.konan.reportCompilationError
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.supportsCodeCoverage
/**
* "Umbrella" class of all the of the code coverage related logic.
*/
internal class CoverageManager(val generationState: NativeGenerationState) {
private val context = generationState.context
private val config = generationState.config
private val shouldCoverSources: Boolean =
config.shouldCoverSources
private val librariesToCover: Set<String> =
config.resolve.coveredLibraries.map { it.libraryName }.toSet()
private val llvmProfileFilenameGlobal = "__llvm_profile_filename"
private val defaultOutputFilePath: String by lazy {
"${generationState.outputFile}.profraw"
}
private val outputFileName: String =
config.configuration.get(KonanConfigKeys.PROFRAW_PATH)
?.let { File(it).absolutePath }
?: defaultOutputFilePath
val enabled: Boolean =
shouldCoverSources || librariesToCover.isNotEmpty()
init {
if (enabled && !checkRestrictions()) {
generationState.reportCompilationError("Coverage is not supported for ${config.target}.")
}
}
private fun checkRestrictions(): Boolean {
val isKindAllowed = config.produce.involvesBitcodeGeneration
val target = config.target
val isTargetAllowed = target.supportsCodeCoverage()
return isKindAllowed && isTargetAllowed
}
private val filesRegionsInfo = mutableListOf<FileRegionInfo>()
private fun getFunctionRegions(irFunction: IrFunction) =
filesRegionsInfo.flatMap { it.functions }.firstOrNull { it.function == irFunction }
private val coveredModules: Set<ModuleDescriptor> by lazy {
val coveredSources = if (shouldCoverSources) {
context.sourcesModules
} else {
emptySet()
}
val coveredLibs = context.irModules.filter { it.key in librariesToCover }.values
.map { it.descriptor }.toSet()
coveredLibs + coveredSources
}
@OptIn(ObsoleteDescriptorBasedAPI::class)
private fun fileCoverageFilter(file: IrFile) =
file.moduleDescriptor in coveredModules
/**
* Walk [irModuleFragment] subtree and collect [FileRegionInfo] for files that are part of [coveredModules].
*/
fun collectRegions(irModuleFragment: IrModuleFragment) {
if (enabled) {
val regions = CoverageRegionCollector(this::fileCoverageFilter).collectFunctionRegions(irModuleFragment)
filesRegionsInfo += regions
}
}
/**
* @return [LLVMCoverageInstrumentation] instance if [irFunction] should be covered.
*/
fun tryGetInstrumentation(irFunction: IrFunction?, callSitePlacer: (function: LlvmCallable, args: List<LLVMValueRef>) -> Unit) =
if (enabled && irFunction != null) {
getFunctionRegions(irFunction)?.let { LLVMCoverageInstrumentation(generationState, it, callSitePlacer) }
} else {
null
}
/**
* Add __llvm_coverage_mapping to the LLVM module.
*/
fun writeRegionInfo() {
if (enabled) {
LLVMCoverageWriter(generationState, filesRegionsInfo).write()
}
}
/**
* Add passes that should be executed after main LLVM optimization pipeline.
*/
fun addLateLlvmPasses(passManager: LLVMPassManagerRef) {
if (enabled) {
// It's a late pass since DCE can kill __llvm_profile_filename global.
LLVMAddInstrProfPass(passManager, outputFileName)
}
}
/**
* Since we performing instruction profiling before internalization and global dce
* __llvm_profile_filename need to be added to exported symbols.
*/
fun addExportedSymbols(): List<String> =
if (enabled) {
listOf(llvmProfileFilenameGlobal)
} else {
emptyList()
}
}
internal fun runCoveragePass(generationState: NativeGenerationState) {
if (!generationState.coverage.enabled) return
val passManager = LLVMCreatePassManager()!!
LLVMKotlinAddTargetLibraryInfoWrapperPass(passManager, generationState.llvm.targetTriple)
generationState.coverage.addLateLlvmPasses(passManager)
LLVMRunPassManager(passManager, generationState.llvm.module)
LLVMDisposePassManager(passManager)
}
@@ -1,222 +0,0 @@
/*
* Copyright 2010-2019 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.coverage
import org.jetbrains.kotlin.ir.util.sourceFileSymbol
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
/**
* Collect all regions in the module.
* @param fileFilter filters files that should be processed.
*/
internal class CoverageRegionCollector(private val fileFilter: (IrFile) -> Boolean) {
fun collectFunctionRegions(irModuleFragment: IrModuleFragment): List<FileRegionInfo> =
irModuleFragment.files
.filter(fileFilter)
.map { file ->
val collector = FunctionsCollector(file)
collector.visitFile(file)
FileRegionInfo(file, collector.functionRegions)
}
private inner class FunctionsCollector(val file: IrFile) : IrElementVisitorVoid {
val functionRegions = mutableListOf<FunctionRegions>()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitFunction(declaration: IrFunction) {
if (!declaration.isInline && !declaration.isExternal && !declaration.isGeneratedByCompiler) {
val regionsCollector = IrFunctionRegionsCollector(fileFilter, file)
declaration.acceptVoid(regionsCollector)
if (regionsCollector.regions.isNotEmpty()) {
functionRegions += FunctionRegions(declaration, regionsCollector.regions)
}
}
// TODO: Decide how to work with local functions. Should they be process separately?
declaration.acceptChildrenVoid(this)
}
}
}
// User doesn't bother about compiler-generated declarations.
// So lets filter them.
private val IrFunction.isGeneratedByCompiler: Boolean
get() {
return origin != IrDeclarationOrigin.DEFINED || name.asString() == "Konan_start"
}
/**
* Very similar to [org.jetbrains.kotlin.backend.konan.llvm.CodeGeneratorVisitor] but instead of bitcode generation we collect regions.
* [fileFilter]: specify which files should be processed by code coverage. Here it is required
* for checking calls to inline functions from other files.
* TODO: for now it is very inaccurate.
*/
private class IrFunctionRegionsCollector(
val fileFilter: (IrFile) -> Boolean,
val irFile: IrFile
) : IrElementVisitorVoid {
private data class StatementContext(val current: IrStatement, val next: IrStatement?)
val regions = mutableMapOf<IrElement, Region>()
private val irFileStack = mutableListOf(irFile)
private val regionStack = mutableListOf<Region>()
private val irStatementsStack = mutableListOf<StatementContext>()
private val currentFile: IrFile
get() = irFileStack.last()
private val currentRegion: Region
get() = regionStack.last()
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitSimpleFunction(declaration: IrSimpleFunction) {
declaration.body?.let { body ->
visitInRegionContext(recordRegion(body) ?: return) {
body.acceptVoid(this)
}
}
}
override fun visitConstructor(declaration: IrConstructor) {
val statements = declaration.body?.statements ?: return
visitInStatementContext(statements) { statement ->
if (statement is IrDelegatingConstructorCall && !declaration.isPrimary
|| statement !is IrDelegatingConstructorCall && statement !is IrReturn) {
recordRegion(statement)
statement.acceptVoid(this)
}
}
}
override fun visitBody(body: IrBody) = visitInStatementContext(body.statements)
override fun visitContainerExpression(expression: IrContainerExpression) {
val statements = expression.statements
when (expression) {
is IrReturnableBlock -> {
val file = expression.sourceFileSymbol?.owner
if (file != null && file != currentFile && fileFilter(file)) {
recordRegion(expression)
visitInFileContext(file) {
visitInStatementContext(statements)
}
}
}
else -> visitInStatementContext(statements)
}
}
// TODO: The following implementation produces correct region mapping, but something goes wrong later
// override fun visitFieldAccess(expression: IrFieldAccessExpression) {
// expression.receiver?.let { recordRegion(it) }
// expression.acceptChildrenVoid(this)
// }
override fun visitWhen(expression: IrWhen) {
val branches = expression.branches
branches.forEach {
val condition = it.condition
val result = it.result
if (condition is IrConst<*> && condition.value == true && condition.endOffset == result.endOffset) {
// Probably an 'else' branch.
// Note: can't rely on [IrElseBranch], because IR deserializer doesn't emit it.
recordRegion(result)
} else {
recordRegion(condition)
recordRegion(result, condition.endOffset, result.endOffset)
condition.acceptVoid(this)
}
result.acceptVoid(this)
}
}
override fun visitLoop(loop: IrLoop) {
val condition = loop.condition
recordRegion(condition)
condition.acceptVoid(this)
val body = loop.body ?: return
when (loop) {
is IrWhileLoop -> recordRegion(body, condition.endOffset, body.endOffset)
is IrDoWhileLoop -> recordRegion(body, body.startOffset, condition.startOffset)
}
body.acceptVoid(this)
}
override fun visitBreakContinue(jump: IrBreakContinue) {
val (current, next) = irStatementsStack.lastOrNull() ?: return
recordRegion(next ?: return, current.endOffset, jump.loop.endOffset)
}
override fun visitReturn(expression: IrReturn) {
irStatementsStack.subList(0, irStatementsStack.lastIndex)
.filter { (current, next) -> next != null && current.endOffset > expression.endOffset }
.forEach { (current, next) -> recordRegion(next!!, expression.endOffset, current.endOffset) }
val next = irStatementsStack.lastOrNull()?.next ?: return
val nextRegion = recordRegion(next, expression.endOffset, currentRegion.endOffset) ?: return
regionStack.pop()
regionStack.push(nextRegion)
}
private fun visitInFileContext(file: IrFile, visit: () -> Unit) {
irFileStack.push(file)
visit()
irFileStack.pop()
}
private fun visitInRegionContext(region: Region, visit: () -> Unit) {
regionStack.push(region)
visit()
regionStack.pop()
}
private fun visitInStatementContext(
statements: List<IrStatement>,
visit: (IrStatement) -> Unit = { statement -> statement.acceptVoid(this) }
) {
for (i in 0..statements.lastIndex) {
val current = statements[i]
if (!current.hasValidOffsets()) {
continue
}
val nextInContext = irStatementsStack.lastOrNull()?.next
val next = if (i < statements.lastIndex && statements[i + 1].hasValidOffsets()) statements[i + 1] else nextInContext
irStatementsStack.push(StatementContext(current, next))
visit(current)
irStatementsStack.pop()
}
}
private fun recordRegion(irElement: IrElement, kind: RegionKind = RegionKind.Code)
= Region.fromIr(irElement, currentFile, kind)?.also { regions[irElement] = it }
private fun recordRegion(irElement: IrElement, startOffset: Int, endOffset: Int, kind: RegionKind = RegionKind.Code)
= Region.fromOffset(startOffset, endOffset, currentFile, kind)?.also { regions[irElement] = it }
private fun IrElement.hasValidOffsets() = startOffset != UNDEFINED_OFFSET && endOffset != UNDEFINED_OFFSET
&& startOffset != endOffset
}
@@ -1,58 +0,0 @@
/*
* Copyright 2010-2019 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.coverage
import llvm.*
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrFunction
/**
* Places calls to `llvm.instrprof.increment` in the beginning of the each
* region in [functionRegions].
*/
internal class LLVMCoverageInstrumentation(
override val generationState: NativeGenerationState,
private val functionRegions: FunctionRegions,
private val callSitePlacer: (function: LlvmCallable, args: List<LLVMValueRef>) -> Unit
) : ContextUtils {
private val functionNameGlobal = createFunctionNameGlobal(functionRegions.function)
private val functionHash = llvm.int64(functionRegions.structuralHash)
private val instrProfIncrement by lazy {
val incrementFun = LLVMInstrProfIncrement(llvm.module)!!
LlvmCallable(
getGlobalFunctionType(incrementFun),
incrementFun,
LlvmFunctionAttributeProvider.copyFromExternal(incrementFun)
)
}
// TODO: It's a great place for some debug output.
fun instrumentIrElement(element: IrElement) {
functionRegions.regions[element]?.let {
placeRegionIncrement(it)
}
}
/**
* See https://llvm.org/docs/LangRef.html#llvm-instrprof-increment-intrinsic
*/
private fun placeRegionIncrement(region: Region) {
val numberOfRegions = llvm.int32(functionRegions.regions.size)
val regionNumber = llvm.int32(functionRegions.regionEnumeration.getValue(region))
val args = listOf(functionNameGlobal, functionHash, numberOfRegions, regionNumber)
callSitePlacer(instrProfIncrement, args)
}
// Each profiled function should have a global with its name in a specific format.
private fun createFunctionNameGlobal(function: IrFunction): LLVMValueRef {
val pgoFunctionName = function.llvmFunction.pgoFunctionNameVar
return LLVMConstBitCast(pgoFunctionName, llvm.int8PtrType)!!
}
}
@@ -1,76 +0,0 @@
/*
* Copyright 2010-2019 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.coverage
import kotlinx.cinterop.*
import llvm.*
import org.jetbrains.kotlin.backend.konan.NativeGenerationState
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.path
import org.jetbrains.kotlin.konan.file.File
private fun RegionKind.toLLVMCoverageRegionKind(): LLVMCoverageRegionKind = when (this) {
RegionKind.Code -> LLVMCoverageRegionKind.CODE
RegionKind.Gap -> LLVMCoverageRegionKind.GAP
is RegionKind.Expansion -> LLVMCoverageRegionKind.EXPANSION
}
private fun LLVMCoverageRegion.populateFrom(region: Region, regionId: Int, filesIndex: Map<IrFile, Int>) = apply {
val (lineStart, columnStart) = region.startLineAndColumn
val (lineEnd, columnEnd) = region.endLineAndColumn
fileId = filesIndex.getValue(region.file)
this.lineStart = lineStart
this.columnStart = columnStart
this.lineEnd = lineEnd
this.columnEnd = columnEnd
counterId = regionId
kind = region.kind.toLLVMCoverageRegionKind()
expandedFileId = if (region.kind is RegionKind.Expansion) filesIndex.getValue(region.kind.expandedFile) else 0
}
/**
* Writes all of the coverage information to the [org.jetbrains.kotlin.backend.konan.NativeGenerationState.llvm.module].
* See http://llvm.org/docs/CoverageMappingFormat.html for the format description.
*/
internal class LLVMCoverageWriter(
private val generationState: NativeGenerationState,
private val filesRegionsInfo: List<FileRegionInfo>
) {
fun write() {
if (filesRegionsInfo.isEmpty()) return
val module = generationState.llvm.module
val filesIndex = filesRegionsInfo.mapIndexed { index, fileRegionInfo -> fileRegionInfo.file to index }.toMap()
val coverageGlobal = memScoped {
val (functionMappingRecords, functionCoverages) = filesRegionsInfo.flatMap { it.functions }.map { functionRegions ->
val regions = (functionRegions.regions.values).map { region ->
alloc<LLVMCoverageRegion>().populateFrom(region, functionRegions.regionEnumeration.getValue(region), filesIndex).ptr
}
val fileIds = functionRegions.regions.map { filesIndex.getValue(it.value.file) }.toSet().toIntArray()
val functionCoverage = LLVMWriteCoverageRegionMapping(
fileIds.toCValues(), fileIds.size.signExtend(),
regions.toCValues(), regions.size.signExtend())
val functionName = generationState.llvmDeclarations.forFunction(functionRegions.function).name
val functionMappingRecord = LLVMAddFunctionMappingRecord(LLVMGetModuleContext(generationState.llvm.module),
functionName, functionRegions.structuralHash, functionCoverage)!!
Pair(functionMappingRecord, functionCoverage)
}.unzip()
val (filenames, fileIds) = filesIndex.entries.toList().map { File(it.key.path).absolutePath to it.value }.unzip()
val retval = LLVMCoverageEmit(module, functionMappingRecords.toCValues(), functionMappingRecords.size.signExtend(),
filenames.toCStringArray(this), fileIds.toIntArray().toCValues(), fileIds.size.signExtend(),
functionCoverages.map { it }.toCValues(), functionCoverages.size.signExtend())!!
// TODO: Is there a better way to cleanup fields of T* type in `memScoped`?
functionCoverages.forEach { LLVMFunctionCoverageDispose(it) }
retval
}
generationState.llvm.usedGlobals.add(coverageGlobal)
}
}
+8 -8
View File
@@ -1,9 +1,9 @@
headers = llvm-c/Core.h llvm-c/Target.h llvm-c/Analysis.h llvm-c/BitWriter.h \
llvm-c/BitReader.h llvm-c/Transforms/PassManagerBuilder.h llvm-c/Transforms/IPO.h \
llvm-c/TargetMachine.h llvm-c/Target.h llvm-c/Linker.h llvm-c/Initialization.h \
llvm-c/DebugInfo.h DebugInfoC.h CoverageMappingC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
llvm-c/DebugInfo.h DebugInfoC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
headerFilter = llvm-c/* llvm-c/**/* DebugInfoC.h CoverageMappingC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
headerFilter = llvm-c/* llvm-c/**/* DebugInfoC.h CAPIExtensions.h RemoveRedundantSafepoints.h OpaquePointerAPI.h
compilerOpts = -std=c99 \
-Wall -W -Wno-unused-parameter -Wwrite-strings -Wmissing-field-initializers \
@@ -20,7 +20,7 @@ linkerOpts = -fvisibility-inlines-hidden \
-DNDEBUG -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS \
-ldebugInfo -lllvmext
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto objcarcopts arm aarch64 webassembly x86 mips
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto objcarcopts arm aarch64 webassembly x86 mips
linkerOpts.osx = \
-Wl,-search_paths_first -Wl,-headerpad_max_install_names \
-lpthread -lz -lm -lcurses -Wl,-U,_futimens -Wl,-U,_LLVMDumpType \
@@ -31,7 +31,7 @@ linkerOpts.osx = \
-lLLVMAArch64Utils -lLLVMAArch64Info -lLLVMARMDisassembler -lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG \
-lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc -lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO \
-lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen -lLLVMipo -lLLVMInstrumentation -lLLVMVectorize \
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine -lLLVMCoverage \
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine \
-lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMObject -lLLVMTextAPI -lLLVMMCParser \
-lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat \
-lLLVMSupport -lLLVMDemangle
@@ -39,7 +39,7 @@ linkerOpts.osx = \
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto arm aarch64 webassembly x86 mips
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto arm aarch64 webassembly x86 mips
linkerOpts.linux = \
-Wl,-z,noexecstack \
-lrt -ldl -lpthread -lz -lm \
@@ -49,12 +49,12 @@ linkerOpts.linux = \
-lLLVMAArch64Utils -lLLVMAArch64Info -lLLVMARMDisassembler -lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG \
-lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc -lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO \
-lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen -lLLVMipo -lLLVMInstrumentation -lLLVMVectorize \
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine -lLLVMCoverage \
-lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP -lLLVMAggressiveInstCombine \
-lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData -lLLVMObject -lLLVMTextAPI -lLLVMMCParser \
-lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks -lLLVMBitstreamReader -lLLVMBinaryFormat \
-lLLVMSupport -lLLVMDemangle
# ./llvm-config --libs analysis bitreader bitwriter core linker target coverage analysis ipo instrumentation lto arm aarch64 webassembly x86
# ./llvm-config --libs analysis bitreader bitwriter core linker target analysis ipo instrumentation lto arm aarch64 webassembly x86
linkerOpts.mingw = \
-lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMX86Desc -lLLVMX86Info -lLLVMWebAssemblyDisassembler \
-lLLVMWebAssemblyCodeGen -lLLVMWebAssemblyDesc -lLLVMWebAssemblyAsmParser -lLLVMWebAssemblyInfo -lLLVMAArch64Disassembler \
@@ -62,7 +62,7 @@ linkerOpts.mingw = \
-lLLVMARMCodeGen -lLLVMCFGuard -lLLVMGlobalISel -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMDebugInfoDWARF -lLLVMARMAsmParser -lLLVMARMDesc \
-lLLVMMCDisassembler -lLLVMARMUtils -lLLVMARMInfo -lLLVMLTO -lLLVMPasses -lLLVMCoroutines -lLLVMObjCARCOpts -lLLVMExtensions -lLLVMCodeGen \
-lLLVMipo -lLLVMInstrumentation -lLLVMVectorize -lLLVMScalarOpts -lLLVMIRReader -lLLVMAsmParser -lLLVMInstCombine -lLLVMFrontendOpenMP \
-lLLVMAggressiveInstCombine -lLLVMCoverage -lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData \
-lLLVMAggressiveInstCombine -lLLVMTarget -lLLVMLinker -lLLVMTransformUtils -lLLVMBitWriter -lLLVMAnalysis -lLLVMProfileData \
-lLLVMObject -lLLVMTextAPI -lLLVMMCParser -lLLVMMC -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMBitReader -lLLVMCore -lLLVMRemarks \
-lLLVMBitstreamReader -lLLVMBinaryFormat -lLLVMSupport -lLLVMDemangle \
-lpsapi -lshell32 -lole32 -luuid -ladvapi32
@@ -123,9 +123,6 @@ allprojects {
// backend.native/tests/framework
ext.testOutputFramework = rootProject.file("$testOutputRoot/framework")
// backent.native/tests/coverage
ext.testOutputCoverage = rootProject.file("$testOutputRoot/coverage")
ext.testOutputFileCheck = rootProject.file("$testOutputRoot/filecheck")
}
testOutputExternal.mkdirs()
@@ -187,7 +184,6 @@ tasks.named("run") {
dependsOn(tasks.withType(FrameworkTest).matching { it.enabled })
// Add regular gradle test tasks
dependsOn(tasks.withType(Test).matching { it.enabled })
dependsOn(tasks.withType(CoverageTest).matching { it.enabled })
dependsOn(tasks.withType(FileCheckTest).matching { it.enabled })
dependsOn(":kotlin-native:Interop:Indexer:check")
dependsOn(":kotlin-native:Interop:StubGenerator:check")
@@ -5080,112 +5076,6 @@ if (isAppleTarget(project)) {
}
}
if (UtilsKt.getTestTargetSupportsCodeCoverage(project)) {
tasks.register("coverage_basic_program", CoverageTest) {
binaryName = "CoverageBasic"
numberOfCoveredFunctions = 1
numberOfCoveredLines = 3
konanArtifacts {
program(binaryName, targets: [ target ]) {
srcDir 'coverage/basic/program'
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.program.main"
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
extraOpts project.globalTestArgs
}
}
}
tasks.register("coverage_basic_library", CoverageTest) {
binaryName = "CoverageBasicLibrary"
numberOfCoveredFunctions = 1
numberOfCoveredLines = 1
konanArtifacts {
library("lib_to_cover", targets: [target.name]) {
srcFiles 'coverage/basic/library/library.kt'
}
program(binaryName, targets: [ target ]) {
srcFiles 'coverage/basic/library/main.kt'
libraries {
artifact konanArtifacts.lib_to_cover
}
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.library.main"
extraOpts "-Xcoverage-file=$profrawFile", "-Xlibrary-to-cover=${konanArtifacts.lib_to_cover.getArtifactByTarget(target.name)}"
extraOpts project.globalTestArgs
}
}
}
tasks.register("coverage_controlflow", CoverageTest) {
binaryName = "CoverageControlFlow"
numberOfCoveredFunctions = 1
numberOfCoveredLines = 102
konanArtifacts {
program(binaryName, targets: [ target ]) {
srcDir 'coverage/basic/controlflow'
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.controlflow.main"
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
extraOpts project.globalTestArgs
}
}
}
tasks.register("coverage_jumps", CoverageTest) {
binaryName = "CoverageJumps"
numberOfCoveredFunctions = 8
numberOfCoveredLines = 74
konanArtifacts {
program(binaryName, targets: [ target ]) {
srcDir 'coverage/basic/jumps'
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.jumps.main"
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
extraOpts project.globalTestArgs
}
}
}
tasks.register("coverage_smoke0", CoverageTest) {
binaryName = "CoverageSmoke0"
numberOfCoveredFunctions = 2
numberOfCoveredLines = 5
konanArtifacts {
program(binaryName, targets: [ target ]) {
srcDir 'coverage/basic/smoke0'
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.smoke0.main"
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
extraOpts project.globalTestArgs
}
}
}
tasks.register("coverage_smoke1", CoverageTest) {
binaryName = "CoverageSmoke1"
numberOfCoveredFunctions = 9
numberOfCoveredLines = 33
konanArtifacts {
program(binaryName, targets: [ target ]) {
srcDir 'coverage/basic/smoke1'
baseDir "$testOutputCoverage/$binaryName"
entryPoint "coverage.basic.smoke1.main"
extraOpts "-Xcoverage", "-Xcoverage-file=$profrawFile"
extraOpts project.globalTestArgs
}
}
}
}
standaloneTest("local_ea_arraysfieldwrite") {
disabled = (cacheTesting != null) // Cache is not compatible with -opt.
useGoldenData = true
@@ -1,126 +0,0 @@
package coverage.basic.controlflow
fun main() {
// If Expression
var a = 1
var b = 2
if (a < b) println("a < b")
if (a > b) {
println("a > b")
} else if (a == b) {
println("a == b")
} else {
println("a < b")
}
if (a < b) {
println("a < b")
}
else
{
println("a >= b")
}
var max = if (a > b) a else b
max = if (a > b) {
println("Choose a")
a
} else {
println("Choose b")
b
}
if (a < b)
println("a < b")
else
println("a >= b")
if (a > b)
println("a > b")
else
println("a <= b")
// When Expression
when {
a < b -> {
println("a < b")
}
a == b ->
println("a == b")
a > b -> {
println("a > b")
}
else -> {
}
}
var x = 1
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
x = 2
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
x = 3
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // Note the block
print("x is neither 1 nor 2")
}
}
when (x) {
0, 1
->
print("x == 0 or x == 1")
else ->
print("otherwise")
}
when {
else -> println("=)")
}
// While Loops
do {
b++
} while (b < 5)
while (a < 10) {
a++
if (a > 7) {
println(a)
}
}
while (a > 0)
a--
}
@@ -1,90 +0,0 @@
package coverage.basic.jumps
fun simpleReturn(n: Int) {
if (n == 0) return
println(n)
}
fun returnFromIfBranch(n: Int) {
if (n > 0) {
if (n > 10) {
return
}
} else if (n < -10) {
return
} else if (n == 0) {
return
}
println(n)
}
fun returnFromWhenBranch(n: Int) {
when {
n == 0 -> return
n == 1 -> return
n == 2 -> {
println(n)
return
}
else -> println(n)
}
}
fun breakFromWhile() {
var a = 7
while (true) {
if (a < 4) break
println(a)
a--
}
}
fun continueFromDoWhile() {
var a = 0
do {
if (a % 3 == 0) {
a++
continue
}
println(a)
a++
} while (a < 10)
}
fun singleReturn() {
return
}
fun nestedReturn() {
while (true) {
while (true) {
while (true) {
if (1 < 2) {
return
}
println()
}
println()
}
}
println()
}
fun main() {
simpleReturn(0)
simpleReturn(1)
returnFromIfBranch(1)
returnFromIfBranch(11)
returnFromIfBranch(-11)
returnFromIfBranch(0)
returnFromWhenBranch(0)
returnFromWhenBranch(1)
returnFromWhenBranch(2)
breakFromWhile()
continueFromDoWhile()
singleReturn()
nestedReturn()
}
@@ -1,3 +0,0 @@
package coverage_library
fun foo() = "foo"
@@ -1,5 +0,0 @@
package coverage.basic.library
fun main() {
println(coverage_library.foo())
}
@@ -1,5 +0,0 @@
package coverage.basic.program
fun main() {
println("OK")
}
@@ -1,8 +0,0 @@
package coverage.basic.smoke0
data class User(val name: String)
fun main() {
val user = User("Happy Kotlin/Native user")
println(user)
}
@@ -1,48 +0,0 @@
package coverage.basic.smoke1
class A(val prop: Int) {
constructor() : this(1)
fun action1() {
println(prop)
}
fun action2() {
}
}
class B private constructor(val prop: String) {
init {
println("init block")
}
constructor(prop1: String, prop2: String) : this("$prop1, $prop2")
constructor() : this("dummy") {
if (1 > 2) {
println("uncovered")
} else {
println("foo")
}
println("bar")
}
override fun toString() = prop
}
fun main(args: Array<String>) {
val a1 = A(2)
a1.action1()
a1.action2()
val a2 = A()
a2.action1()
a2.action1()
val b1 = B("Hello", "world")
val b2 = B()
println(b1)
println(b2)
}
@@ -58,7 +58,6 @@ val buildSamplesWithPlatformLibs by tasks.creating {
dependsOn(":objc:assemble")
dependsOn(":opengl:assemble")
dependsOn(":uikit:assemble")
dependsOn(":coverage:assemble")
dependsOn(":watchos:assemble")
}
@@ -1,49 +0,0 @@
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
plugins {
kotlin("multiplatform")
}
kotlin {
// Determine host preset.
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
// Create a target for the host platform.
val hostTarget = when {
hostOs == "Mac OS X" -> macosX64("coverage")
hostOs == "Linux" -> linuxX64("coverage")
isMingwX64 -> mingwX64("coverage")
else -> throw GradleException("Host OS '$hostOs' is not supported in Kotlin/Native $project.")
}
hostTarget.apply {
binaries {
executable(listOf(DEBUG))
}
binaries.getTest("DEBUG").apply {
freeCompilerArgs += listOf("-Xlibrary-to-cover=${compilations["main"].output.classesDirs.singleFile.absolutePath}")
}
}
sourceSets {
val coverageMain by getting
val coverageTest by getting
}
}
tasks.create("createCoverageReport") {
dependsOn("coverageTest")
description = "Create coverage report"
doLast {
val testDebugBinary = kotlin.targets["coverage"].let { it as KotlinNativeTarget }.binaries.getTest("DEBUG").outputFile
exec {
commandLine("llvm-profdata", "merge", "$testDebugBinary.profraw", "-o", "$testDebugBinary.profdata")
}
exec {
commandLine("llvm-cov", "show", "$testDebugBinary", "-instr-profile", "$testDebugBinary.profdata")
}
}
}
@@ -1 +0,0 @@
kotlin.code.style=official
@@ -1,17 +0,0 @@
fun baz(args: Array<String>) {
if (args.size > 4) {
println("Too many")
} else {
println("Fine")
}
}
class A {
init {
println("A::init")
}
fun f() {
println("A::f")
}
}
@@ -1,3 +0,0 @@
fun main() {
baz(arrayOf("abc"))
}
@@ -1,15 +0,0 @@
import kotlin.test.Test
import kotlin.test.assertTrue
class CoverageTests {
@Test
fun testHello() {
main()
}
@Test
fun testA() {
val a = A()
a.f()
}
}
@@ -24,7 +24,6 @@ if (isMacos || isLinux || isWindows) {
include(":libcurl")
include(":videoplayer")
include(":workers")
include(":coverage")
}
if (isMacos || isLinux) {