From 7cc1ea8801a3fc5ac139378cd2dbd8e1b68b4a59 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Tue, 10 Aug 2021 11:28:48 +0300 Subject: [PATCH] Native: introduce binary options machinery --- .../org/jetbrains/kotlin/cli/bc/K2Native.kt | 72 ++++++++++++++---- .../cli/bc/K2NativeCompilerArguments.kt | 7 ++ .../kotlin/backend/konan/BinaryOptions.kt | 74 +++++++++++++++++++ 3 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt diff --git a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index 28616390ea3..da15158f9de 100644 --- a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -367,6 +367,10 @@ class K2Native : CLICompiler() { } } }) + + parseBinaryOptions(arguments, configuration).forEach { optionWithValue -> + configuration.put(optionWithValue) + } } } } @@ -570,22 +574,64 @@ private fun parseDebugPrefixMap( } }.toMap() +private class BinaryOptionWithValue(val option: BinaryOption, val value: T) + +private fun CompilerConfiguration.put(binaryOptionWithValue: BinaryOptionWithValue) { + this.put(binaryOptionWithValue.option.compilerConfigurationKey, binaryOptionWithValue.value) +} + +private fun parseBinaryOptions( + arguments: K2NativeCompilerArguments, + configuration: CompilerConfiguration +): List> { + val keyValuePairs = parseKeyValuePairs(arguments.binaryOptions, configuration) ?: return emptyList() + + return keyValuePairs.mapNotNull { (key, value) -> + val option = BinaryOptions.getByName(key) + if (option == null) { + configuration.report(STRONG_WARNING, "Unknown binary option '$key'") + null + } else { + parseBinaryOption(option, value, configuration) + } + } +} + +private fun parseBinaryOption( + option: BinaryOption, + valueName: String, + configuration: CompilerConfiguration +): BinaryOptionWithValue? { + val value = option.valueParser.parse(valueName) + return if (value == null) { + configuration.report(STRONG_WARNING, "Unknown value '$valueName' of binary option '${option.name}'. " + + "Possible values are: ${option.valueParser.validValuesHint}") + null + } else { + BinaryOptionWithValue(option, value) + } +} + private fun parseOverrideKonanProperties( arguments: K2NativeCompilerArguments, configuration: CompilerConfiguration -): Map? = - arguments.overrideKonanProperties?.mapNotNull { - val keyValueSeparatorIndex = it.indexOf('=') - if (keyValueSeparatorIndex > 0) { - it.substringBefore('=') to it.substringAfter('=') - } else { - configuration.report( - ERROR, - "incorrect property format: expected '=', got '$it'" - ) - null - } - }?.toMap() +): Map? = parseKeyValuePairs(arguments.overrideKonanProperties, configuration) + +private fun parseKeyValuePairs( + argumentValue: Array?, + configuration: CompilerConfiguration +): Map? = argumentValue?.mapNotNull { + val keyValueSeparatorIndex = it.indexOf('=') + if (keyValueSeparatorIndex > 0) { + it.substringBefore('=') to it.substringAfter('=') + } else { + configuration.report( + ERROR, + "incorrect property format: expected '=', got '$it'" + ) + null + } +}?.toMap() diff --git a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index f90a0214b9e..c3199cfb632 100644 --- a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -344,6 +344,13 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { ) var llvmVariant: String? = null + @Argument( + value = "-Xbinary", + valueDescription = "", + description = "Specify binary option" + ) + var binaryOptions: Array? = null + override fun configureAnalysisFlags(collector: MessageCollector, languageVersion: LanguageVersion): MutableMap, Any> = super.configureAnalysisFlags(collector, languageVersion).also { val useExperimental = it[AnalysisFlags.useExperimental] as List<*> diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt new file mode 100644 index 00000000000..c802058b7a6 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.backend.konan + +import org.jetbrains.kotlin.config.CompilerConfigurationKey +import kotlin.properties.PropertyDelegateProvider +import kotlin.properties.ReadOnlyProperty + +// Note: options defined in this class are a part of user interface, including the names: +// users can pass these options using a -Xbinary=name=value compiler argument or corresponding Gradle DSL. +object BinaryOptions : BinaryOptionRegistry() { +} + +open class BinaryOption( + val name: String, + val valueParser: ValueParser, + val compilerConfigurationKey: CompilerConfigurationKey = CompilerConfigurationKey.create(name) +) { + interface ValueParser { + fun parse(value: String): T? + val validValuesHint: String? + } +} + +open class BinaryOptionRegistry { + private val registeredOptionsByName = mutableMapOf>() + + protected fun register(option: BinaryOption<*>) { + val previousOption = registeredOptionsByName[option.name] + if (previousOption != null) { + error("option '${option.name}' is registered twice") + } + registeredOptionsByName[option.name] = option + } + + fun getByName(name: String): BinaryOption<*>? = registeredOptionsByName[name] + + protected fun booleanOption(): PropertyDelegateProvider>> = + PropertyDelegateProvider { _, property -> + val option = BinaryOption(property.name, BooleanValueParser) + register(option) + ReadOnlyProperty { _, _ -> + option.compilerConfigurationKey + } + } + + protected inline fun > option(): PropertyDelegateProvider>> = + PropertyDelegateProvider { _, property -> + val option = BinaryOption(property.name, EnumValueParser(enumValues().toList())) + register(option) + ReadOnlyProperty { _, _ -> + option.compilerConfigurationKey + } + } +} + +private object BooleanValueParser : BinaryOption.ValueParser { + override fun parse(value: String): Boolean? = value.toBooleanStrictOrNull() + + override val validValuesHint: String? + get() = "true|false" +} + +@PublishedApi +internal class EnumValueParser>(val values: List) : BinaryOption.ValueParser { + // TODO: should we really ignore case here? + override fun parse(value: String): T? = values.firstOrNull { it.name.equals(value, ignoreCase = true) } + + override val validValuesHint: String? + get() = values.joinToString("|") +}