[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
@@ -22,9 +22,6 @@ fun copyK2NativeCompilerArguments(from: K2NativeCompilerArguments, to: K2NativeC
to.checkExternalCalls = from.checkExternalCalls
to.clangOptions = from.clangOptions?.copyOf()
to.compileFromBitcode = from.compileFromBitcode
to.coverage = from.coverage
to.coverageFile = from.coverageFile
to.coveredLibraries = from.coveredLibraries?.copyOf()
to.debug = from.debug
to.debugInfoFormatVersion = from.debugInfoFormatVersion
to.debugPrefixMap = from.debugPrefixMap?.copyOf()
@@ -353,21 +353,6 @@ The default value is 1."""
@Argument(value = "-Xdebug-info-version", description = "Generate debug info of the given version (1, 2).")
var debugInfoFormatVersion: String = "1" /* command line parser doesn't accept kotlin.Int type */
@Argument(value = "-Xcoverage", description = "Emit code coverage information.")
var coverage: Boolean = false
@Argument(
value = "-Xlibrary-to-cover",
valueDescription = "<path>",
description = """Emit code coverage information for the given library.
The library must be one of the ones passed with '-library'.""",
delimiter = Argument.Delimiters.none
)
var coveredLibraries: Array<String>? = null
@Argument(value = "-Xcoverage-file", valueDescription = "<path>", description = "Save coverage information to the given file.")
var coverageFile: String? = null
@Argument(value = "-Xno-objc-generics", description = "Disable generics support for framework header.")
var noObjcGenerics: Boolean = false
-66
View File
@@ -1,66 +0,0 @@
# Code Coverage
Kotlin/Native has a code coverage support that is based on Clang's
[Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html).
## **Please note**:
1. Currently, support for code coverage in Kotlin/Native is broken and not working out of the box.
If you would like to fix it and need guidance,
please contact us on the [public Slack](https://slack.kotlinlang.org/) on `#kotlin-native` channel.
2. Most of the described functionality might be incorporated into Gradle plugin some day.
### Usage
#### TL;DR
```bash
kotlinc-native main.kt -Xcoverage
./program.kexe
llvm-profdata merge program.kexe.profraw -o program.profdata
llvm-cov report program.kexe -instr-profile program.profdata
```
#### Compiling with coverage enabled
There are 2 compiler flags that allows to generate coverage information:
* `-Xcoverage`. Generate coverage for immediate sources.
* `-Xlibrary-to-cover=<path>`. Generate coverage for specified `klib`.
Note that library also should be either linked via `-library/-l` compiler option or be a transitive dependency.
#### Running covered executable
After the execution of the compiled binary (ex. `program.kexe`) `program.kexe.profraw` will be generated.
By default it will be generated in the same location where binary was created. The are two ways to override this behavior:
* `-Xcoverage-file=<path>` compiler flag.
* `LLVM_PROFILE_FILE` environment variable. So if you run your program like this:
```
LLVM_PROFILE_FILE=build/program.profraw ./program.kexe
```
Then the coverage information will be stored to the `build` dir as `program.profraw`.
#### Parsing `*.profraw`
Generated file can be parsed with `llvm-profdata` utility. Basic usage:
```
llvm-profdata merge default.profraw -o program.profdata
```
See [command guide](http://llvm.org/docs/CommandGuide/llvm-profdata.html) for more options.
#### Creating reports
The last step is to create a report from the `program.profdata` file.
It can be done with `llvm-cov` utility (refer to [command guide](http://llvm.org/docs/CommandGuide/llvm-cov.html) for detailed usage).
For example, we can see a basic report using:
```
llvm-cov report program.kexe -instr-profile program.profdata
```
Or show a line-by-line coverage information in html:
```
llvm-cov show program.kexe -instr-profile program.profdata -format=html > report.html
```
### Sample
Usually coverage information is collected during running of the tests.
Please refer to `samples/coverage` to see how it can be done.
### Useful links
* [LLVM Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html)
@@ -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) {
@@ -1,145 +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
import groovy.lang.Closure
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.Internal
import org.jetbrains.kotlin.konan.target.AppleConfigurables
/**
* Test task for -Xcoverage and -Xlibraries-to-cover flags. Requires a binary to be built by the Konan plugin
* with
* konanArtifacts {
* program([binaryName], targets: [testTarget]) {
* ...
* extraOpts "-Xcoverage"/"-Xlibrary-to-cover=...", "-Xcoverage-file=$[profrawFile]"
* }
* }
* and a dependency set according to a pattern "run${binaryName}".
*
* @property numberOfCoveredFunctions Expected number of covered functions
* @property numberOfCoveredLines Expected number of covered lines in all functions
* @property binaryName Name of the produced binary
*/
open class CoverageTest : DefaultTask() {
private val target = project.testTarget
private val platform = project.platformManager.platform(target)
private val configurables = platform.configurables
// Use the same LLVM version as compiler when producing machine code:
private val llvmToolsDir = if (configurables is AppleConfigurables) {
"${configurables.absoluteTargetToolchain}/usr/bin"
} else {
"${configurables.absoluteLlvmHome}/bin"
}
@Input
lateinit var binaryName: String
// TODO: Consider better metric.
@Input
var numberOfCoveredFunctions: Int? = null
@Input
var numberOfCoveredLines: Int? = null
@get:Internal
val profrawFile: String by lazy {
"${project.buildDir.absolutePath}/$binaryName.profraw"
}
private val profdataFile: String by lazy {
"${project.buildDir.absolutePath}/$binaryName.profdata"
}
private val outputDir: String by lazy {
project.file(project.property("testOutputCoverage")!!).absolutePath
}
override fun configure(closure: Closure<Any>): Task {
super.configure(closure)
dependsOnDist()
val compileBinaryTask = project.tasks.named("compileKonan$binaryName").configure {
konanOldPluginTaskDependenciesWalker {
dependsOnDist()
}
}
dependsOn(compileBinaryTask)
return this
}
@TaskAction
fun run() {
val suffix = target.family.exeSuffix
val pathToBinary = "$outputDir/$binaryName/$target/$binaryName.$suffix"
runProcess({ project.executor.execute(it) }, pathToBinary)
.ensureSuccessful(pathToBinary)
exec("llvm-profdata", "merge", profrawFile, "-o", profdataFile)
val llvmCovResult = exec("llvm-cov", "export", pathToBinary, "-instr-profile", profdataFile)
val jsonReport = llvmCovResult.stdOut
val llvmCovReport = parseLlvmCovReport(jsonReport)
try {
CoverageValidator(numberOfCoveredFunctions, numberOfCoveredLines).validateReport(llvmCovReport)
} catch (e: TestFailedException) {
// Show report in message to make debug easier.
val show = exec("llvm-cov", "show", pathToBinary, "-instr-profile", profdataFile).stdOut
// llvm-cov output contains '|' so another symbol is used as margin prefix.
throw TestFailedException("""
>${e.message}
>$show
""".trimMargin(">"))
}
}
private fun exec(llvmTool: String, vararg args: String): ProcessOutput {
val executable = "$llvmToolsDir/$llvmTool"
val result = runProcess(localExecutor(project), executable, args.toList())
result.ensureSuccessful(llvmTool)
return result
}
private fun ProcessOutput.ensureSuccessful(executable: String) {
if (exitCode != 0) {
println("""
$executable failed.
exitCode: $exitCode
stdout:
$stdOut
stderr:
$stdErr
""".trimIndent())
error("$executable failed")
}
}
}
private class CoverageValidator(
val numberOfCoveredFunctions: Int?,
val numberOfCoveredLines: Int?
) {
fun validateReport(report: LlvmCovReport) {
val data = report.data
if (data.isEmpty()) {
failTest("Report data should not be empty!")
}
compareNumbers(numberOfCoveredFunctions, data[0].totals.functions.covered, "Number of covered functions")
compareNumbers(numberOfCoveredLines, data[0].totals.lines.covered, "Number of covered lines")
}
private fun compareNumbers(expected: Int?, actual: Int, description: String) {
if (expected != null && actual != expected) {
failTest("$description differs from expected! Expected: $expected. Got: $actual")
}
}
private fun failTest(message: String) {
throw TestFailedException(message)
}
}
@@ -115,9 +115,6 @@ val Project.globalBuildArgs: List<String>
val Project.globalTestArgs: List<String>
get() = project.groovyPropertyArrayToList("globalTestArgs")
val Project.testTargetSupportsCodeCoverage: Boolean
get() = this.testTarget.supportsCodeCoverage()
fun projectOrFiles(proj: Project, notation: String): Any? {
val propertyMapper = proj.findProperty("notationMapping") ?: return proj.project(notation)
val mapping = (propertyMapper as? Map<*, *>)?.get(notation) as? String ?: return proj.project(notation)
@@ -130,10 +130,8 @@ abstract class KonanCompileTask: KonanBuildingTask(), KonanCompileSpec {
return result
}
// Don't include coverage flags into the first stage because they are not supported when compiling a klib.
private fun firstStageExtraOpts() = extraOpts
.excludeFlags("-Xcoverage")
.excludeArguments("-Xcoverage-file", "-Xlibrary-to-cover", "-Xpartial-linkage", "-Xpartial-linkage-loglevel")
.excludeArguments("-Xpartial-linkage", "-Xpartial-linkage-loglevel")
// Don't include the -Xemit-lazy-objc-header and -language-version flags into
// the second stage because this stage have no sources.
@@ -5,6 +5,7 @@
#include <CAPIExtensions.h>
#include <llvm/ProfileData/Coverage/CoverageMapping.h>
#include <llvm/ADT/Triple.h>
#include <llvm/Analysis/TargetLibraryInfo.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/LegacyPassManager.h>
@@ -71,3 +72,8 @@ void LLVMPrintAllTimersToStdOut() {
void LLVMClearAllTimers() {
llvm::TimerGroup::clearAll();
}
void LLVMKotlinAddTargetLibraryInfoWrapperPass(LLVMPassManagerRef passManagerRef, const char* targetTriple) {
legacy::PassManagerBase *passManager = unwrap(passManagerRef);
passManager->add(new TargetLibraryInfoWrapperPass(Triple(targetTriple)));
}
@@ -1,233 +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.
*/
#include "CoverageMappingC.h"
#include <llvm/ProfileData/Coverage/CoverageMapping.h>
#include <llvm/ProfileData/Coverage/CoverageMappingWriter.h>
#include <llvm/IR/GlobalVariable.h>
#include <llvm/Support/raw_ostream.h>
#include <llvm/ADT/Triple.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/Type.h>
#include <llvm/IR/DerivedTypes.h>
#include <llvm/IR/Constants.h>
#include <llvm/IR/Intrinsics.h>
#include <llvm/IR/Instructions.h>
#include <llvm/IR/LegacyPassManager.h>
#include <llvm/Analysis/TargetLibraryInfo.h>
#include <llvm/Transforms/Instrumentation.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/Path.h>
#include <utility>
#include <string>
#include <vector>
#include <iostream>
#include <iomanip>
using namespace llvm;
using namespace llvm::coverage;
namespace llvm {
DEFINE_SIMPLE_CONVERSION_FUNCTIONS(TargetLibraryInfoImpl, LLVMTargetLibraryInfoRef)
}
struct LLVMFunctionCoverage {
explicit LLVMFunctionCoverage(std::string coverageData) : coverageData(std::move(coverageData)) {}
std::string coverageData;
};
static coverage::CounterMappingRegion::RegionKind determineRegionKind(const struct LLVMCoverageRegion& region) {
switch (region.kind) {
case LLVMCoverageRegionKind::CODE:
return coverage::CounterMappingRegion::RegionKind::CodeRegion;
case LLVMCoverageRegionKind::GAP:
return coverage::CounterMappingRegion::RegionKind::GapRegion;
case LLVMCoverageRegionKind::EXPANSION:
return coverage::CounterMappingRegion::RegionKind::ExpansionRegion;
}
}
static coverage::CounterMappingRegion createCounterMappingRegion(struct LLVMCoverageRegion& region) {
auto regionKind = determineRegionKind(region);
int expandedFileId = 0;
if (regionKind == coverage::CounterMappingRegion::RegionKind::ExpansionRegion) {
expandedFileId = region.expandedFileId;
}
const Counter &counter = coverage::Counter::getCounter(region.counterId);
return coverage::CounterMappingRegion(counter, region.fileId, expandedFileId, region.lineStart,
region.columnStart, region.lineEnd, region.columnEnd, regionKind);
}
LLVMFunctionCoverage* LLVMWriteCoverageRegionMapping(unsigned int *fileIdMapping, size_t fileIdMappingSize,
struct LLVMCoverageRegion **mappingRegions, size_t mappingRegionsSize) {
std::vector<coverage::CounterMappingRegion> counterMappingRegions;
for (size_t i = 0; i < mappingRegionsSize; ++i) {
struct LLVMCoverageRegion region = *mappingRegions[i];
counterMappingRegions.emplace_back(createCounterMappingRegion(region));
}
CoverageMappingWriter writer(ArrayRef<unsigned int>(fileIdMapping, fileIdMappingSize), None, counterMappingRegions);
std::string CoverageMapping;
raw_string_ostream OS(CoverageMapping);
writer.write(OS);
OS.flush();
// Should be disposed with `LLVMFunctionCoverageDispose`.
return new LLVMFunctionCoverage(CoverageMapping);
}
void LLVMFunctionCoverageDispose(struct LLVMFunctionCoverage* functionCoverage) {
delete functionCoverage;
}
static StructType *getFunctionRecordTy(LLVMContext &Ctx) {
#define COVMAP_FUNC_RECORD(Type, LLVMType, Name, Init) LLVMType,
Type *FunctionRecordTypes[] = {
#include "llvm/ProfileData/InstrProfData.inc"
};
StructType *FunctionRecordTy = StructType::get(Ctx, makeArrayRef(FunctionRecordTypes), true);
return FunctionRecordTy;
}
static llvm::Constant *addFunctionMappingRecord(llvm::LLVMContext &Ctx, StringRef NameValue, uint64_t FuncHash,
const std::string &CoverageMapping) {
llvm::StructType *FunctionRecordTy = getFunctionRecordTy(Ctx);
#define COVMAP_FUNC_RECORD(Type, LLVMType, Name, Init) Init,
llvm::Constant *FunctionRecordVals[] = {
#include "llvm/ProfileData/InstrProfData.inc"
};
return llvm::ConstantStruct::get(FunctionRecordTy, makeArrayRef(FunctionRecordVals));
}
// See https://github.com/llvm/llvm-project/blob/fa8fa044ec46b94e64971efa8852df0d58114062/clang/lib/CodeGen/CoverageMappingGen.cpp#L1284.
LLVMValueRef LLVMAddFunctionMappingRecord(LLVMContextRef context, const char *name, uint64_t hash,
struct LLVMFunctionCoverage *coverageMapping) {
return llvm::wrap(addFunctionMappingRecord(*llvm::unwrap(context), name, hash, coverageMapping->coverageData));
}
// See https://github.com/llvm/llvm-project/blob/fa8fa044ec46b94e64971efa8852df0d58114062/clang/lib/CodeGen/CoverageMappingGen.cpp#L1335.
// Please note that llvm/ProfileData/InstrProfData.inc refers to variable names of the function that includes it. So be careful with renaming.
static llvm::GlobalVariable* emitCoverageGlobal(
llvm::LLVMContext &Ctx,
llvm::Module &module,
std::vector<llvm::Constant *> &FunctionRecords,
const llvm::SmallVector<StringRef, 16> &FilenameRefs,
const std::string &RawCoverageMappings) {
auto *Int32Ty = llvm::Type::getInt32Ty(Ctx);
std::string FilenamesAndCoverageMappings;
llvm::raw_string_ostream outputStream(FilenamesAndCoverageMappings);
CoverageFilenamesSectionWriter(FilenameRefs).write(outputStream);
outputStream << RawCoverageMappings;
size_t CoverageMappingSize = RawCoverageMappings.size();
size_t FilenamesSize = outputStream.str().size() - CoverageMappingSize;
// See https://llvm.org/docs/CoverageMappingFormat.html#llvm-ir-representation
//
// > Coverage mapping data which is an array of bytes. Zero paddings are added at the end to force 8 byte alignment.
//
if (size_t rem = outputStream.str().size() % 8) {
CoverageMappingSize += 8 - rem;
for (size_t i = 0; i < 8 - rem; ++i) {
outputStream << '\0';
}
}
StructType *functionRecordTy = getFunctionRecordTy(Ctx);
// Create the deferred function records array
auto functionRecordsTy = llvm::ArrayType::get(functionRecordTy, FunctionRecords.size());
auto functionRecordsVal = llvm::ConstantArray::get(functionRecordsTy, FunctionRecords);
const unsigned NRecords = 0;
llvm::Type *CovDataHeaderTypes[] = {
#define COVMAP_HEADER(Type, LLVMType, Name, Init) LLVMType,
#include "llvm/ProfileData/InstrProfData.inc"
};
auto CovDataHeaderTy = llvm::StructType::get(Ctx, makeArrayRef(CovDataHeaderTypes));
llvm::Constant *CovDataHeaderVals[] = {
#define COVMAP_HEADER(Type, LLVMType, Name, Init) Init,
#include "llvm/ProfileData/InstrProfData.inc"
};
auto covDataHeaderVal = llvm::ConstantStruct::get(CovDataHeaderTy, makeArrayRef(CovDataHeaderVals));
auto *filenamesAndMappingsVal = llvm::ConstantDataArray::getString(Ctx, outputStream.str(), false);
// Create the coverage data record
llvm::Type *covDataTypes[] = {CovDataHeaderTy, functionRecordsTy, filenamesAndMappingsVal->getType()};
auto covDataTy = llvm::StructType::get(Ctx, makeArrayRef(covDataTypes));
llvm::Constant *TUDataVals[] = {covDataHeaderVal, functionRecordsVal, filenamesAndMappingsVal};
auto covDataVal = llvm::ConstantStruct::get(covDataTy, makeArrayRef(TUDataVals));
// Will be deleted when module is disposed.
return new llvm::GlobalVariable(module, covDataTy, true, llvm::GlobalValue::InternalLinkage,
covDataVal, llvm::getCoverageMappingVarName());
}
static std::string createRawCoverageMapping(struct LLVMFunctionCoverage **functionCoverages, size_t functionCoveragesSize) {
std::vector<std::string> coverageMappings;
for (size_t i = 0; i < functionCoveragesSize; ++i) {
coverageMappings.emplace_back(functionCoverages[i]->coverageData);
}
return llvm::join(coverageMappings.begin(), coverageMappings.end(), "");
}
LLVMValueRef LLVMCoverageEmit(LLVMModuleRef moduleRef,
LLVMValueRef *records, size_t recordsSize,
const char **filenames, int *filenamesIndices, size_t filenamesSize,
struct LLVMFunctionCoverage **functionCoverages, size_t functionCoveragesSize) {
LLVMContext &ctx = *unwrap(LLVMGetModuleContext(moduleRef));
Module &module = *unwrap(moduleRef);
std::vector<Constant *> functionRecords;
for (size_t i = 0; i < recordsSize; ++i) {
functionRecords.push_back(dyn_cast<Constant>(unwrap(records[i])));
}
llvm::SmallVector<StringRef, 16> filenameRefs;
filenameRefs.resize(filenamesSize);
for (size_t i = 0; i < filenamesSize; ++i) {
if (sys::path::is_absolute(filenames[i])) {
filenameRefs[filenamesIndices[i]] = filenames[i];
} else {
SmallString<256> path(filenames[i]);
sys::fs::make_absolute(path);
sys::path::remove_dots(path, true);
filenameRefs[filenamesIndices[i]] = path;
}
}
const std::string &rawCoverageMappings = createRawCoverageMapping(functionCoverages, functionCoveragesSize);
GlobalVariable *coverageGlobal = emitCoverageGlobal(ctx, module, functionRecords, filenameRefs, rawCoverageMappings);
const std::string &section = getInstrProfSectionName(IPSK_covmap, Triple(module.getTargetTriple()).getObjectFormat());
coverageGlobal->setSection(section);
coverageGlobal->setAlignment(llvm::Align(8));
return wrap(coverageGlobal);
}
LLVMValueRef LLVMInstrProfIncrement(LLVMModuleRef moduleRef) {
Module &module = *unwrap(moduleRef);
return wrap(Intrinsic::getDeclaration(&module, Intrinsic::instrprof_increment, None));
}
LLVMValueRef LLVMCreatePGOFunctionNameVar(LLVMValueRef llvmFunction, const char *pgoFunctionName) {
auto *fnPtr = cast<llvm::Function>(unwrap(llvmFunction));
return wrap(createPGOFuncNameVar(*fnPtr, pgoFunctionName));
}
void LLVMAddInstrProfPass(LLVMPassManagerRef passManagerRef, const char* outputFileName) {
legacy::PassManagerBase *passManager = unwrap(passManagerRef);
InstrProfOptions options;
options.InstrProfileOutput = outputFileName;
passManager->add(createInstrProfilingLegacyPass(options));
}
void LLVMKotlinAddTargetLibraryInfoWrapperPass(LLVMPassManagerRef passManagerRef, const char* targetTriple) {
legacy::PassManagerBase *passManager = unwrap(passManagerRef);
passManager->add(new TargetLibraryInfoWrapperPass(Triple(targetTriple)));
}
@@ -1,86 +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.
*/
#ifndef __COVERAGE_MAPPING_C_H__
# define __COVERAGE_MAPPING_C_H__
#include <llvm-c/Core.h>
#include <llvm-c/Target.h>
# ifdef __cplusplus
extern "C" {
# endif
/**
* See org.jetbrains.kotlin.backend.konan.llvm.coverage.RegionKind.
*/
enum LLVMCoverageRegionKind {
CODE,
GAP,
EXPANSION
};
/**
* See org.jetbrains.kotlin.backend.konan.llvm.coverage.Region.
*/
struct LLVMCoverageRegion {
int fileId;
int lineStart;
int columnStart;
int lineEnd;
int columnEnd;
int counterId;
int expandedFileId;
enum LLVMCoverageRegionKind kind;
};
struct LLVMFunctionCoverage;
/**
* Add record in the following format: https://llvm.org/docs/CoverageMappingFormat.html#function-record.
*/
LLVMValueRef
LLVMAddFunctionMappingRecord(LLVMContextRef context, const char *name, uint64_t hash, struct LLVMFunctionCoverage* coverageMapping);
/**
* Wraps creation of coverage::CoverageMappingWriter and call to coverage::CoverageMappingWriter::write.
*/
struct LLVMFunctionCoverage* LLVMWriteCoverageRegionMapping(unsigned int *fileIdMapping, size_t fileIdMappingSize,
struct LLVMCoverageRegion **mappingRegions, size_t mappingRegionsSize);
void LLVMFunctionCoverageDispose(struct LLVMFunctionCoverage* functionCoverage);
/**
* Create __llvm_coverage_mapping global.
*/
LLVMValueRef LLVMCoverageEmit(LLVMModuleRef moduleRef, LLVMValueRef *records, size_t recordsSize,
const char **filenames, int *filenamesIndices, size_t filenamesSize,
struct LLVMFunctionCoverage** functionCoverages, size_t functionCoveragesSize);
/**
* Wrapper for `llvm.instrprof.increment` declaration.
*/
LLVMValueRef LLVMInstrProfIncrement(LLVMModuleRef moduleRef);
/**
* Wrapper for llvm::createPGOFuncNameVar.
*/
LLVMValueRef LLVMCreatePGOFunctionNameVar(LLVMValueRef llvmFunction, const char *pgoFunctionName);
void LLVMAddInstrProfPass(LLVMPassManagerRef passManagerRef, const char* outputFileName);
void LLVMKotlinAddTargetLibraryInfoWrapperPass(LLVMPassManagerRef passManagerRef, const char* targetTriple);
void LLVMAddObjCARCContractPass(LLVMPassManagerRef passManagerRef);
void LLVMKotlinInitializeTargets();
void LLVMSetNoTailCall(LLVMValueRef Call);
# ifdef __cplusplus
}
# endif
#endif
-7
View File
@@ -229,13 +229,6 @@ bitcode {
onlyIf { target.supportsLibBacktrace() }
}
module("profileRuntime") {
srcRoot.set(layout.projectDirectory.dir("src/profile_runtime"))
sourceSets {
main {}
}
}
module("objc") {
headersDirs.from(files("src/main/cpp"))
sourceSets {
@@ -1,28 +0,0 @@
// Define symbols that are required for code coverage but missing in compiler-RT for MinGW.
// See https://reviews.llvm.org/D58106/ for details.
#ifdef KONAN_WINDOWS
#include <windows.h>
extern "C" {
__attribute__((used))
int lprofGetHostName(char *hostName, int length) {
const int maxHostNameLength = 128;
WCHAR buffer[maxHostNameLength];
DWORD bufferSize = sizeof(buffer);
COMPUTER_NAME_FORMAT nameType = ComputerNameDnsFullyQualified;
if (!GetComputerNameExW(nameType, buffer, &bufferSize)) {
return -1;
}
int bytesWritten = WideCharToMultiByte(CP_UTF8, 0, buffer, -1, hostName, length, nullptr, nullptr);
if (bytesWritten == 0) {
return -1;
} else {
return 0;
}
}
}
#endif
@@ -32,16 +32,6 @@ fun KonanTarget.pointerBits() = when (architecture) {
Architecture.WASM32 -> 32
}
fun KonanTarget.supportsCodeCoverage(): Boolean =
// TODO: Disabled for now, because we don't support
// coverage format from LLVM 11.
false
// this == KonanTarget.MINGW_X64 ||
// this == KonanTarget.LINUX_X64 ||
// this == KonanTarget.MACOS_X64 ||
// this == KonanTarget.IOS_X64
fun KonanTarget.supportsMimallocAllocator(): Boolean =
when(this) {
is KonanTarget.LINUX_X64 -> true
@@ -64,7 +64,6 @@ private fun staticGnuArCommands(ar: String, executable: ExecutableFile,
abstract class LinkerFlags(val configurables: Configurables) {
protected val llvmBin = "${configurables.absoluteLlvmHome}/bin"
protected val llvmLib = "${configurables.absoluteLlvmHome}/lib"
open val useCompilerDriverAsLinker: Boolean get() = false // TODO: refactor.
@@ -76,7 +75,7 @@ abstract class LinkerFlags(val configurables: Configurables) {
libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
needsProfileLibrary: Boolean = false, mimallocEnabled: Boolean,
sanitizer: SanitizerKind? = null): List<Command>
/**
@@ -98,11 +97,6 @@ abstract class LinkerFlags(val configurables: Configurables) {
System.err.println("Can't provide $libraryName.")
return null
}
// Code coverage requires this library.
protected val profileLibrary: String? by lazy {
provideCompilerRtLibrary("profile")
}
}
class AndroidLinker(targetProperties: AndroidConfigurables)
@@ -269,7 +263,6 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
+linkerKonanFlags
if (mimallocEnabled) +mimallocLinkerDependencies
if (compilerRtLibrary != null) +compilerRtLibrary!!
if (needsProfileLibrary) +profileLibrary!!
+libraries
+linkerArgs
+rpath(dynamic, sanitizer)
@@ -420,7 +413,6 @@ class GccBasedLinker(targetProperties: GccConfigurables)
if (mimallocEnabled) +mimallocLinkerDependencies
// See explanation about `-u__llvm_profile_runtime` here:
// https://github.com/llvm/llvm-project/blob/21e270a479a24738d641e641115bce6af6ed360a/llvm/lib/Transforms/Instrumentation/InstrProfiling.cpp#L930
if (needsProfileLibrary) +listOf("-u__llvm_profile_runtime", profileLibrary!!)
+linkerKonanFlags
+linkerGccFlags
+if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o"
@@ -494,7 +486,7 @@ class MingwLinker(targetProperties: MingwConfigurables)
// --gc-sections flag may affect profiling.
// See https://clang.llvm.org/docs/SourceBasedCodeCoverage.html#drawbacks-and-limitations.
// TODO: switching to lld may help.
if (optimize && !needsProfileLibrary) {
if (optimize) {
// TODO: Can be removed after LLD update.
// See KT-48085.
if (!dynamic) {
@@ -504,7 +496,6 @@ class MingwLinker(targetProperties: MingwConfigurables)
if (!debug) +linkerNoDebugFlags
if (dynamic) +linkerDynamicFlags
+libraries
if (needsProfileLibrary) +profileLibrary!!
+linkerArgs
+linkerKonanFlags.filterNot { it in skipDefaultArguments }
if (mimallocEnabled) +mimallocLinkerDependencies