Support sanitizers in runtime tests (#4622)

This commit is contained in:
Alexander Shabalin
2021-01-20 19:42:09 +03:00
committed by Nikolay Krasko
parent 3ee8d98726
commit 6cc60ad6a2
10 changed files with 256 additions and 66 deletions
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ExecClang
import org.jetbrains.kotlin.konan.target.Family import org.jetbrains.kotlin.konan.target.Family
import org.jetbrains.kotlin.konan.target.HostManager import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.SanitizerKind
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@@ -19,7 +20,7 @@ open class CompileToBitcode @Inject constructor(
val srcRoot: File, val srcRoot: File,
val folderName: String, val folderName: String,
val target: String, val target: String,
val outputGroup: String val outputGroup: String,
) : DefaultTask() { ) : DefaultTask() {
enum class Language { enum class Language {
@@ -46,9 +47,22 @@ open class CompileToBitcode @Inject constructor(
@Input @Input
var language = Language.CPP var language = Language.CPP
private val targetDir by lazy { project.buildDir.resolve("bitcode/$outputGroup/$target") } @Input @Optional
var sanitizer: SanitizerKind? = null
val objDir by lazy { File(targetDir, folderName) } private val targetDir: File
get() {
val sanitizerSuffix = when (sanitizer) {
null -> ""
SanitizerKind.ADDRESS -> "-asan"
SanitizerKind.THREAD -> "-tsan"
}
return project.buildDir.resolve("bitcode/$outputGroup/$target$sanitizerSuffix")
}
@get:Input
val objDir
get() = File(targetDir, folderName)
private val KonanTarget.isMINGW private val KonanTarget.isMINGW
get() = this.family == Family.MINGW get() = this.family == Family.MINGW
@@ -63,6 +77,11 @@ open class CompileToBitcode @Inject constructor(
val compilerFlags: List<String> val compilerFlags: List<String>
get() { get() {
val commonFlags = listOf("-c", "-emit-llvm") + headersDirs.map { "-I$it" } val commonFlags = listOf("-c", "-emit-llvm") + headersDirs.map { "-I$it" }
val sanitizerFlags = when (sanitizer) {
null -> listOf()
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
SanitizerKind.THREAD -> listOf("-fsanitize=thread")
}
val languageFlags = when (language) { val languageFlags = when (language) {
Language.C -> Language.C ->
// Used flags provided by original build of allocator C code. // Used flags provided by original build of allocator C code.
@@ -73,7 +92,7 @@ open class CompileToBitcode @Inject constructor(
"-Wno-unused-parameter", // False positives with polymorphic functions. "-Wno-unused-parameter", // False positives with polymorphic functions.
"-fPIC".takeIf { !HostManager().targetByName(target).isMINGW }) "-fPIC".takeIf { !HostManager().targetByName(target).isMINGW })
} }
return commonFlags + languageFlags + compilerArgs return commonFlags + sanitizerFlags + languageFlags + compilerArgs
} }
@get:SkipWhenEmpty @get:SkipWhenEmpty
@@ -127,8 +146,9 @@ open class CompileToBitcode @Inject constructor(
} }
} }
@OutputFile @get:OutputFile
val outFile = File(targetDir, "${folderName}.bc") val outFile: File
get() = File(targetDir, "${folderName}.bc")
@TaskAction @TaskAction
fun compile() { fun compile() {
@@ -9,6 +9,9 @@ import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin import org.gradle.api.plugins.BasePlugin
import org.jetbrains.kotlin.createCompilationDatabasesFromCompileToBitcodeTasks import org.jetbrains.kotlin.createCompilationDatabasesFromCompileToBitcodeTasks
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.SanitizerKind
import org.jetbrains.kotlin.konan.target.supportedSanitizers
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
@@ -45,20 +48,40 @@ open class CompileToBitcodeExtension @Inject constructor(val project: Project) {
configurationBlock: CompileToBitcode.() -> Unit = {} configurationBlock: CompileToBitcode.() -> Unit = {}
) { ) {
targetList.get().forEach { targetName -> targetList.get().forEach { targetName ->
project.tasks.register( val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager
"${targetName}${name.snakeCaseToCamelCase().capitalize()}", val target = platformManager.targetByName(targetName)
CompileToBitcode::class.java, val sanitizers: List<SanitizerKind?> = target.supportedSanitizers() + listOf(null)
srcDir, name, targetName, outputGroup sanitizers.forEach { sanitizer ->
).configure { project.tasks.register(
it.group = BasePlugin.BUILD_GROUP "${targetName}${name.snakeCaseToCamelCase().capitalize()}${suffixForSanitizer(sanitizer)}",
it.description = "Compiles '$name' to bitcode for $targetName" CompileToBitcode::class.java,
it.configurationBlock() srcDir, name, targetName, outputGroup
).configure {
it.sanitizer = sanitizer
it.group = BasePlugin.BUILD_GROUP
val sanitizerDescription = when (sanitizer) {
null -> ""
SanitizerKind.ADDRESS -> " with ASAN"
SanitizerKind.THREAD -> " with TSAN"
}
it.description = "Compiles '$name' to bitcode for $targetName$sanitizerDescription"
it.configurationBlock()
}
} }
} }
} }
companion object { companion object {
private fun String.snakeCaseToCamelCase() = private fun String.snakeCaseToCamelCase() =
split('_').joinToString(separator = "") { it.capitalize() } split('_').joinToString(separator = "") { it.capitalize() }
fun suffixForSanitizer(sanitizer: SanitizerKind?) =
when (sanitizer) {
null -> ""
SanitizerKind.ADDRESS -> "_ASAN"
SanitizerKind.THREAD -> "_TSAN"
}
} }
} }
@@ -13,6 +13,7 @@ import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.* import org.gradle.api.tasks.*
import org.jetbrains.kotlin.ExecClang import org.jetbrains.kotlin.ExecClang
import org.jetbrains.kotlin.bitcode.CompileToBitcode import org.jetbrains.kotlin.bitcode.CompileToBitcode
import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension
import org.jetbrains.kotlin.konan.target.* import org.jetbrains.kotlin.konan.target.*
open class CompileNativeTest @Inject constructor( open class CompileNativeTest @Inject constructor(
@@ -25,18 +26,29 @@ open class CompileNativeTest @Inject constructor(
@Input @Input
val clangArgs = mutableListOf<String>() val clangArgs = mutableListOf<String>()
@Input @Optional
var sanitizer: SanitizerKind? = null
@Input
private val sanitizerFlags = when (sanitizer) {
null -> listOf()
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
SanitizerKind.THREAD -> listOf("-fsanitize=thread")
}
@TaskAction @TaskAction
fun compile() { fun compile() {
val plugin = project.convention.getPlugin(ExecClang::class.java) val plugin = project.convention.getPlugin(ExecClang::class.java)
val args = clangArgs + sanitizerFlags + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath)
if (target.family.isAppleFamily) { if (target.family.isAppleFamily) {
plugin.execToolchainClang(target) { plugin.execToolchainClang(target) {
it.executable = "clang++" it.executable = "clang++"
it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) it.args = args
} }
} else { } else {
plugin.execBareClang { plugin.execBareClang {
it.executable = "clang++" it.executable = "clang++"
it.args = clangArgs + listOf(inputFile.absolutePath, "-o", outputFile.absolutePath) it.args = args
} }
} }
} }
@@ -92,7 +104,7 @@ open class LinkNativeTest @Inject constructor(
@Internal val target: String, @Internal val target: String,
@Internal val linkerArgs: List<String>, @Internal val linkerArgs: List<String>,
private val platformManager: PlatformManager, private val platformManager: PlatformManager,
private val mimallocEnabled: Boolean private val mimallocEnabled: Boolean,
) : DefaultTask () { ) : DefaultTask () {
companion object { companion object {
fun create( fun create(
@@ -103,7 +115,7 @@ open class LinkNativeTest @Inject constructor(
target: String, target: String,
outputFile: File, outputFile: File,
linkerArgs: List<String>, linkerArgs: List<String>,
mimallocEnabled: Boolean mimallocEnabled: Boolean,
): LinkNativeTest = project.tasks.create( ): LinkNativeTest = project.tasks.create(
taskName, taskName,
LinkNativeTest::class.java, LinkNativeTest::class.java,
@@ -133,6 +145,9 @@ open class LinkNativeTest @Inject constructor(
linkerArgs, mimallocEnabled) linkerArgs, mimallocEnabled)
} }
@Input @Optional
var sanitizer: SanitizerKind? = null
@get:Input @get:Input
val commands: List<List<String>> val commands: List<List<String>>
get() { get() {
@@ -149,7 +164,8 @@ open class LinkNativeTest @Inject constructor(
kind = LinkerOutputKind.EXECUTABLE, kind = LinkerOutputKind.EXECUTABLE,
outputDsymBundle = "", outputDsymBundle = "",
needsProfileLibrary = false, needsProfileLibrary = false,
mimallocEnabled = mimallocEnabled mimallocEnabled = mimallocEnabled,
sanitizer = sanitizer,
).map { it.argsWithExecutable } ).map { it.argsWithExecutable }
} }
@@ -163,11 +179,11 @@ open class LinkNativeTest @Inject constructor(
} }
} }
fun createTestTask( private fun createTestTask(
project: Project, project: Project,
testName: String, testName: String,
testTaskName: String,
testedTaskNames: List<String>, testedTaskNames: List<String>,
sanitizer: SanitizerKind?,
configureCompileToBitcode: CompileToBitcode.() -> Unit = {}, configureCompileToBitcode: CompileToBitcode.() -> Unit = {},
): Task { ): Task {
val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager
@@ -188,6 +204,7 @@ fun createTestTask(
"${it.folderName}Tests", "${it.folderName}Tests",
target, "test" target, "test"
).apply { ).apply {
this.sanitizer = sanitizer
excludeFiles = emptyList() excludeFiles = emptyList()
includeFiles = listOf("**/*Test.cpp", "**/*Test.mm") includeFiles = listOf("**/*Test.cpp", "**/*Test.mm")
dependsOn(it) dependsOn(it)
@@ -201,18 +218,19 @@ fun createTestTask(
else else
task task
} }
// TODO: Consider using sanitized versions.
val testFrameworkTasks = listOf( val testFrameworkTasks = listOf(
project.tasks.getByName("${target}Googletest") as CompileToBitcode, project.tasks.getByName("${target}Googletest") as CompileToBitcode,
project.tasks.getByName("${target}Googlemock") as CompileToBitcode project.tasks.getByName("${target}Googlemock") as CompileToBitcode
) )
val testSupportTask = project.tasks.getByName("${target}TestSupport") as CompileToBitcode val testSupportTask = project.tasks.getByName("${target}TestSupport${CompileToBitcodeExtension.suffixForSanitizer(sanitizer)}") as CompileToBitcode
// TODO: It may make sense to merge llvm-link, compile and link to a single task. // TODO: It may make sense to merge llvm-link, compile and link to a single task.
val llvmLinkTask = project.tasks.create( val llvmLinkTask = project.tasks.create(
"${testTaskName}LlvmLink", "${testName}LlvmLink",
LlvmLinkNativeTest::class.java, LlvmLinkNativeTest::class.java,
testTaskName, target, testSupportTask.outFile testName, target, testSupportTask.outFile
).apply { ).apply {
val tasksToLink = (compileToBitcodeTasks + testedTasks + testFrameworkTasks) val tasksToLink = (compileToBitcodeTasks + testedTasks + testFrameworkTasks)
inputFiles = project.files(tasksToLink.map { it.outFile }) inputFiles = project.files(tasksToLink.map { it.outFile })
@@ -222,11 +240,12 @@ fun createTestTask(
val clangFlags = platformManager.platform(konanTarget).configurables as ClangFlags val clangFlags = platformManager.platform(konanTarget).configurables as ClangFlags
val compileTask = project.tasks.create( val compileTask = project.tasks.create(
"${testTaskName}Compile", "${testName}Compile",
CompileNativeTest::class.java, CompileNativeTest::class.java,
llvmLinkTask.outputFile, llvmLinkTask.outputFile,
konanTarget, konanTarget,
).apply { ).apply {
this.sanitizer = sanitizer
dependsOn(llvmLinkTask) dependsOn(llvmLinkTask)
clangArgs.addAll(clangFlags.clangFlags) clangArgs.addAll(clangFlags.clangFlags)
clangArgs.addAll(clangFlags.clangNooptFlags) clangArgs.addAll(clangFlags.clangNooptFlags)
@@ -236,19 +255,20 @@ fun createTestTask(
val linkTask = LinkNativeTest.create( val linkTask = LinkNativeTest.create(
project, project,
platformManager, platformManager,
"${testTaskName}Link", "${testName}Link${CompileToBitcodeExtension.suffixForSanitizer(sanitizer)}",
listOf(compileTask.outputFile), listOf(compileTask.outputFile),
target, target,
testTaskName, testName,
mimallocEnabled mimallocEnabled,
).apply { ).apply {
this.sanitizer = sanitizer
dependsOn(compileTask) dependsOn(compileTask)
} }
return project.tasks.create(testTaskName, Exec::class.java).apply { return project.tasks.create(testName, Exec::class.java).apply {
dependsOn(linkTask) dependsOn(linkTask)
workingDir = project.buildDir.resolve("testReports/$testTaskName") workingDir = project.buildDir.resolve("testReports/$testName")
val xmlReport = workingDir.resolve("report.xml") val xmlReport = workingDir.resolve("report.xml")
executable(linkTask.outputFile) executable(linkTask.outputFile)
args("--gtest_output=xml:${xmlReport.absoluteFile}") args("--gtest_output=xml:${xmlReport.absoluteFile}")
@@ -267,3 +287,24 @@ fun createTestTask(
} }
} }
} }
// TODO: These tests should be created by `CompileToBitcodeExtension`
fun createTestTasks(
project: Project,
targetName: String,
testTaskName: String,
testedTaskNames: List<String>,
configureCompileToBitcode: CompileToBitcode.() -> Unit = {},
): List<Task> {
val platformManager = project.rootProject.findProperty("platformManager") as PlatformManager
val target = platformManager.targetByName(targetName)
val sanitizers: List<SanitizerKind?> = target.supportedSanitizers() + listOf(null)
return sanitizers.map { sanitizer ->
val suffix = CompileToBitcodeExtension.suffixForSanitizer(sanitizer)
val name = testTaskName + suffix
val testedNames = testedTaskNames.map {
it + suffix
}
createTestTask(project, name, testedNames, sanitizer, configureCompileToBitcode)
}
}
+19 -16
View File
@@ -5,6 +5,8 @@
import org.jetbrains.kotlin.* import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.testing.native.* import org.jetbrains.kotlin.testing.native.*
import org.jetbrains.kotlin.bitcode.CompileToBitcode import org.jetbrains.kotlin.bitcode.CompileToBitcode
import org.jetbrains.kotlin.bitcode.CompileToBitcodeExtension
import org.jetbrains.kotlin.konan.target.*
plugins { plugins {
id("compile-to-bitcode") id("compile-to-bitcode")
@@ -42,6 +44,7 @@ bitcode {
"${target}ExperimentalMemoryManager" "${target}ExperimentalMemoryManager"
) )
includeRuntime() includeRuntime()
// TODO: Should depend on the sanitizer.
linkerArgs.add(project.file("../common/build/bitcode/main/$target/hash.bc").path) linkerArgs.add(project.file("../common/build/bitcode/main/$target/hash.bc").path)
} }
@@ -107,9 +110,11 @@ bitcode {
} }
targetList.forEach { targetName -> targetList.forEach { targetName ->
createTestTask( val allTests = mutableListOf<Task>()
allTests.addAll(createTestTasks(
project, project,
"StdAlloc", targetName,
"${targetName}StdAllocRuntimeTests", "${targetName}StdAllocRuntimeTests",
listOf( listOf(
"${targetName}Runtime", "${targetName}Runtime",
@@ -120,11 +125,11 @@ targetList.forEach { targetName ->
) )
) { ) {
includeRuntime() includeRuntime()
} })
createTestTask( allTests.addAll(createTestTasks(
project, project,
"Mimalloc", targetName,
"${targetName}MimallocRuntimeTests", "${targetName}MimallocRuntimeTests",
listOf( listOf(
"${targetName}Runtime", "${targetName}Runtime",
@@ -136,11 +141,11 @@ targetList.forEach { targetName ->
) )
) { ) {
includeRuntime() includeRuntime()
} })
createTestTask( allTests.addAll(createTestTasks(
project, project,
"ExperimentalMMMimalloc", targetName,
"${targetName}ExperimentalMMMimallocRuntimeTests", "${targetName}ExperimentalMMMimallocRuntimeTests",
listOf( listOf(
"${targetName}Runtime", "${targetName}Runtime",
@@ -151,11 +156,11 @@ targetList.forEach { targetName ->
) )
) { ) {
includeRuntime() includeRuntime()
} })
createTestTask( allTests.addAll(createTestTasks(
project, project,
"ExperimentalMMStdAlloc", targetName,
"${targetName}ExperimentalMMStdAllocRuntimeTests", "${targetName}ExperimentalMMStdAllocRuntimeTests",
listOf( listOf(
"${targetName}Runtime", "${targetName}Runtime",
@@ -165,13 +170,11 @@ targetList.forEach { targetName ->
) )
) { ) {
includeRuntime() includeRuntime()
} })
// TODO: This "all tests" tasks should be provided by `CompileToBitcodeExtension`
tasks.register("${targetName}RuntimeTests") { tasks.register("${targetName}RuntimeTests") {
dependsOn("${targetName}StdAllocRuntimeTests") dependsOn(allTests)
dependsOn("${targetName}MimallocRuntimeTests")
dependsOn("${targetName}ExperimentalMMStdAllocRuntimeTests")
dependsOn("${targetName}ExperimentalMMMimallocRuntimeTests")
} }
} }
@@ -112,7 +112,9 @@ TEST_F(KonanAllocatorAwareTest, PlacementAllocated) {
TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) { TEST_F(KonanAllocatorAwareTest, PlacementConstructedArray) {
constexpr size_t kCount = 5; constexpr size_t kCount = 5;
std::array<uint8_t, sizeof(A) * kCount> buffer; // TODO: Consider removing support for placement new[] altogether, since there's no
// portable way to know needed storage size ahead of time.
alignas(A) std::array<uint8_t, sizeof(A) * kCount + sizeof(size_t)> buffer;
A* as = new (buffer.data()) A[kCount]; A* as = new (buffer.data()) A[kCount];
std::vector<int> actual; std::vector<int> actual;
@@ -8,6 +8,9 @@ namespace kotlin {
#if KONAN_WINDOWS #if KONAN_WINDOWS
// TODO: Figure out why creating many threads on windows is so slow. // TODO: Figure out why creating many threads on windows is so slow.
constexpr int kDefaultThreadCount = 10; constexpr int kDefaultThreadCount = 10;
#elif __has_feature(thread_sanitizer)
// TSAN has a huge overhead.
constexpr int kDefaultThreadCount = 10;
#else #else
constexpr int kDefaultThreadCount = 100; constexpr int kDefaultThreadCount = 100;
#endif #endif
@@ -14,9 +14,27 @@
using namespace kotlin; using namespace kotlin;
namespace {
template <typename T>
ALWAYS_INLINE T UnsafeRead(T* location) noexcept {
#if __has_feature(thread_sanitizer)
// Make TSAN think that this load is fine.
return __atomic_load_n(location, __ATOMIC_ACQUIRE);
#else
return *location;
#endif
}
} // namespace
// static // static
mm::ExtraObjectData& mm::ExtraObjectData::Install(ObjHeader* object) noexcept { mm::ExtraObjectData& mm::ExtraObjectData::Install(ObjHeader* object) noexcept {
TypeInfo* typeInfo = object->typeInfoOrMeta_; // TODO: Consider extracting initialization scheme with speculative load.
// `object->typeInfoOrMeta_` is assigned at most once. If we read some old value (i.e. not a meta object),
// we will fail at CAS below. If we read the new value, we will immediately return it.
TypeInfo* typeInfo = UnsafeRead(&object->typeInfoOrMeta_);
if (auto* metaObject = ObjHeader::AsMetaObject(typeInfo)) { if (auto* metaObject = ObjHeader::AsMetaObject(typeInfo)) {
return mm::ExtraObjectData::FromMetaObjHeader(metaObject); return mm::ExtraObjectData::FromMetaObjHeader(metaObject);
} }
@@ -30,3 +30,14 @@ fun KonanTarget.supportsThreads(): Boolean =
is KonanTarget.ZEPHYR -> false is KonanTarget.ZEPHYR -> false
else -> true else -> true
} }
fun KonanTarget.supportedSanitizers(): List<SanitizerKind> =
when(this) {
is KonanTarget.LINUX_X64 -> listOf(SanitizerKind.ADDRESS)
is KonanTarget.MACOS_X64 -> listOf(SanitizerKind.THREAD)
// TODO: Enable ASAN on macOS. Currently there's an incompatibility between clang frontend version and clang_rt.asan version.
// TODO: Enable TSAN on linux. Currently there's a link error between clang_rt.tsan and libstdc++.
// TODO: Consider supporting mingw.
// TODO: Support macOS arm64
else -> listOf()
}
@@ -76,7 +76,8 @@ abstract class LinkerFlags(val configurables: Configurables) {
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind? = null): List<Command>
/** /**
* Returns list of commands that link object files into a single one. * Returns list of commands that link object files into a single one.
@@ -93,7 +94,7 @@ abstract class LinkerFlags(val configurables: Configurables) {
return libraries return libraries
} }
protected open fun provideCompilerRtLibrary(libraryName: String): String? { protected open fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean = false): String? {
System.err.println("Can't provide $libraryName.") System.err.println("Can't provide $libraryName.")
return null return null
} }
@@ -123,7 +124,11 @@ class AndroidLinker(targetProperties: AndroidConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
if (kind == LinkerOutputKind.STATIC_LIBRARY) if (kind == LinkerOutputKind.STATIC_LIBRARY)
return staticGnuArCommands(ar, executable, objectFiles, libraries) return staticGnuArCommands(ar, executable, objectFiles, libraries)
@@ -170,7 +175,12 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
get() = this == KonanTarget.TVOS_X64 || this == KonanTarget.IOS_X64 || get() = this == KonanTarget.TVOS_X64 || this == KonanTarget.IOS_X64 ||
this == KonanTarget.WATCHOS_X86 || this == KonanTarget.WATCHOS_X64 this == KonanTarget.WATCHOS_X86 || this == KonanTarget.WATCHOS_X64
override fun provideCompilerRtLibrary(libraryName: String): String? { private val compilerRtDir: String? by lazy {
val dir = File("$absoluteTargetToolchain/usr/lib/clang/").listFiles.firstOrNull()?.absolutePath
if (dir != null) "$dir/lib/darwin/" else null
}
override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
val prefix = when (target.family) { val prefix = when (target.family) {
Family.IOS -> "ios" Family.IOS -> "ios"
Family.WATCHOS -> "watchos" Family.WATCHOS -> "watchos"
@@ -184,10 +194,11 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
"" ""
} }
val dir = File("$absoluteTargetToolchain/usr/lib/clang/").listFiles.firstOrNull()?.absolutePath val dir = compilerRtDir
val mangledLibraryName = if (libraryName.isEmpty()) "" else "${libraryName}_" val mangledLibraryName = if (libraryName.isEmpty()) "" else "${libraryName}_"
val extension = if (isDynamic) "_dynamic.dylib" else ".a"
return if (dir != null) "$dir/lib/darwin/libclang_rt.$mangledLibraryName$prefix$suffix.a" else null return if (dir != null) "$dir/libclang_rt.$mangledLibraryName$prefix$suffix$extension" else null
} }
private val osVersionMinFlags: List<String> by lazy { private val osVersionMinFlags: List<String> by lazy {
@@ -210,14 +221,19 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, kind: LinkerOutputKind, optimize: Boolean, debug: Boolean, kind: LinkerOutputKind,
outputDsymBundle: String, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
if (kind == LinkerOutputKind.STATIC_LIBRARY) sanitizer: SanitizerKind?): List<Command> {
if (kind == LinkerOutputKind.STATIC_LIBRARY) {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return listOf(Command(libtool).apply { return listOf(Command(libtool).apply {
+"-static" +"-static"
+listOf("-o", executable) +listOf("-o", executable)
+objectFiles +objectFiles
+libraries +libraries
}) })
}
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
val result = mutableListOf<Command>() val result = mutableListOf<Command>()
@@ -237,7 +253,12 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
if (needsProfileLibrary) +profileLibrary!! if (needsProfileLibrary) +profileLibrary!!
+libraries +libraries
+linkerArgs +linkerArgs
+rpath(dynamic) +rpath(dynamic, sanitizer)
when (sanitizer) {
null -> {}
SanitizerKind.ADDRESS -> +provideCompilerRtLibrary("asan", isDynamic=true)!!
SanitizerKind.THREAD -> +provideCompilerRtLibrary("tsan", isDynamic=true)!!
}
} }
// TODO: revise debug information handling. // TODO: revise debug information handling.
@@ -255,7 +276,7 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
provideCompilerRtLibrary("") provideCompilerRtLibrary("")
} }
private fun rpath(dynamic: Boolean): List<String> = listOfNotNull( private fun rpath(dynamic: Boolean, sanitizer: SanitizerKind?): List<String> = listOfNotNull(
when (target.family) { when (target.family) {
Family.OSX -> "@executable_path/../Frameworks" Family.OSX -> "@executable_path/../Frameworks"
Family.IOS, Family.IOS,
@@ -263,7 +284,8 @@ class MacOSBasedLinker(targetProperties: AppleConfigurables)
Family.TVOS -> "@executable_path/Frameworks" Family.TVOS -> "@executable_path/Frameworks"
else -> error(target) else -> error(target)
}, },
"@loader_path/Frameworks".takeIf { dynamic } "@loader_path/Frameworks".takeIf { dynamic },
compilerRtDir.takeIf { sanitizer != null },
).flatMap { listOf("-rpath", it) } ).flatMap { listOf("-rpath", it) }
fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) = fun dsymUtilCommand(executable: ExecutableFile, outputDsymBundle: String) =
@@ -319,7 +341,10 @@ class GccBasedLinker(targetProperties: GccConfigurables)
private val specificLibs = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" } private val specificLibs = abiSpecificLibraries.map { "-L${absoluteTargetSysRoot}/$it" }
override fun provideCompilerRtLibrary(libraryName: String): String? { override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
require(!isDynamic) {
"Dynamic compiler rt librares are unsupported"
}
val targetSuffix = when (target) { val targetSuffix = when (target) {
KonanTarget.LINUX_X64 -> "x86_64" KonanTarget.LINUX_X64 -> "x86_64"
else -> error("$target is not supported.") else -> error("$target is not supported.")
@@ -334,9 +359,14 @@ class GccBasedLinker(targetProperties: GccConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
if (kind == LinkerOutputKind.STATIC_LIBRARY) sanitizer: SanitizerKind?): List<Command> {
if (kind == LinkerOutputKind.STATIC_LIBRARY) {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return staticGnuArCommands(ar, executable, objectFiles, libraries) return staticGnuArCommands(ar, executable, objectFiles, libraries)
}
val isMips = target == KonanTarget.LINUX_MIPS32 || target == KonanTarget.LINUX_MIPSEL32 val isMips = target == KonanTarget.LINUX_MIPS32 || target == KonanTarget.LINUX_MIPSEL32
val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY val dynamic = kind == LinkerOutputKind.DYNAMIC_LIBRARY
val crtPrefix = "$absoluteTargetSysRoot/$crtFilesLocation" val crtPrefix = "$absoluteTargetSysRoot/$crtFilesLocation"
@@ -373,6 +403,19 @@ class GccBasedLinker(targetProperties: GccConfigurables)
+linkerGccFlags +linkerGccFlags
+if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o" +if (dynamic) "$libGcc/crtendS.o" else "$libGcc/crtend.o"
+"$crtPrefix/crtn.o" +"$crtPrefix/crtn.o"
when (sanitizer) {
null -> {}
SanitizerKind.ADDRESS -> {
+"-lrt"
+provideCompilerRtLibrary("asan")!!
+provideCompilerRtLibrary("asan_cxx")!!
}
SanitizerKind.THREAD -> {
+"-lrt"
+provideCompilerRtLibrary("tsan")!!
+provideCompilerRtLibrary("tsan_cxx")!!
}
}
}) })
} }
} }
@@ -387,7 +430,10 @@ class MingwLinker(targetProperties: MingwConfigurables)
override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib } override fun filterStaticLibraries(binaries: List<String>) = binaries.filter { it.isWindowsStaticLib || it.isUnixStaticLib }
override fun provideCompilerRtLibrary(libraryName: String): String? { override fun provideCompilerRtLibrary(libraryName: String, isDynamic: Boolean): String? {
require(!isDynamic) {
"Dynamic compiler rt librares are unsupported"
}
val targetSuffix = when (target) { val targetSuffix = when (target) {
KonanTarget.MINGW_X64 -> "x86_64" KonanTarget.MINGW_X64 -> "x86_64"
else -> error("$target is not supported.") else -> error("$target is not supported.")
@@ -400,7 +446,11 @@ class MingwLinker(targetProperties: MingwConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
require(sanitizer == null) {
"Sanitizers are unsupported"
}
if (kind == LinkerOutputKind.STATIC_LIBRARY) if (kind == LinkerOutputKind.STATIC_LIBRARY)
return staticGnuArCommands(ar, executable, objectFiles, libraries) return staticGnuArCommands(ar, executable, objectFiles, libraries)
@@ -437,8 +487,12 @@ class WasmLinker(targetProperties: WasmConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind") if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind")
require(sanitizer == null) {
"Sanitizers are unsupported"
}
val linkage = Command("$llvmBin/wasm-ld").apply { val linkage = Command("$llvmBin/wasm-ld").apply {
+objectFiles +objectFiles
@@ -489,8 +543,12 @@ open class ZephyrLinker(targetProperties: ZephyrConfigurables)
libraries: List<String>, linkerArgs: List<String>, libraries: List<String>, linkerArgs: List<String>,
optimize: Boolean, debug: Boolean, optimize: Boolean, debug: Boolean,
kind: LinkerOutputKind, outputDsymBundle: String, kind: LinkerOutputKind, outputDsymBundle: String,
needsProfileLibrary: Boolean, mimallocEnabled: Boolean): List<Command> { needsProfileLibrary: Boolean, mimallocEnabled: Boolean,
sanitizer: SanitizerKind?): List<Command> {
if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind: $kind") if (kind != LinkerOutputKind.EXECUTABLE) throw Error("Unsupported linker output kind: $kind")
require(sanitizer == null) {
"Sanitizers are unsupported"
}
return listOf(Command(linker).apply { return listOf(Command(linker).apply {
+listOf("-r", "--gc-sections", "--entry", "main") +listOf("-r", "--gc-sections", "--entry", "main")
+listOf("-o", executable) +listOf("-o", executable)
@@ -0,0 +1,11 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan.target
enum class SanitizerKind {
ADDRESS,
THREAD,
}