[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:
committed by
Space Cloud
parent
e0c931f69d
commit
4f77434ea5
-3
@@ -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()
|
||||
|
||||
-15
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
-12
@@ -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)
|
||||
|
||||
-4
@@ -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")
|
||||
}
|
||||
|
||||
-6
@@ -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>>
|
||||
|
||||
-3
@@ -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)
|
||||
}
|
||||
|
||||
+1
-3
@@ -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}")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -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))
|
||||
|
||||
|
||||
-11
@@ -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)
|
||||
}
|
||||
|
||||
+1
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-5
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
-16
@@ -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)
|
||||
|
||||
+1
-5
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
-95
@@ -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>
|
||||
)
|
||||
-137
@@ -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)
|
||||
}
|
||||
-222
@@ -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
|
||||
}
|
||||
-58
@@ -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)!!
|
||||
}
|
||||
}
|
||||
-76
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
+1
-3
@@ -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 §ion = 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user