Fix K/N for new linker
(cherry picked from commit ea31937c010da2e0d3a1fb5d8b49625ffc31115e)
This commit is contained in:
committed by
Vasily Levchenko
parent
1f4273c5ec
commit
735013cc42
+1
-1
@@ -58,7 +58,7 @@ import org.jetbrains.kotlin.library.SerializedIrModule
|
||||
* Offset for synthetic elements created by lowerings and not attributable to other places in the source code.
|
||||
*/
|
||||
|
||||
internal class SpecialDeclarationsFactory(val context: Context) : KotlinMangler by KonanManglerForBE {
|
||||
internal class SpecialDeclarationsFactory(val context: Context) {
|
||||
private val enumSpecialDeclarationsFactory by lazy { EnumSpecialDeclarationsFactory(context) }
|
||||
private val outerThisFields = mutableMapOf<ClassDescriptor, IrField>()
|
||||
private val bridgesDescriptors = mutableMapOf<Pair<IrSimpleFunction, BridgeDirections>, IrSimpleFunction>()
|
||||
|
||||
+12
@@ -259,6 +259,7 @@ fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean {
|
||||
|
||||
|
||||
val IrConstructor.isObjCConstructor get() = this.annotations.hasAnnotation(objCConstructorFqName)
|
||||
val ConstructorDescriptor.isObjCConstructor get() = this.annotations.hasAnnotation(objCConstructorFqName)
|
||||
|
||||
// TODO-DCE-OBJC-INIT: Selector should be preserved by DCE.
|
||||
fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? {
|
||||
@@ -270,9 +271,20 @@ fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? {
|
||||
}
|
||||
}
|
||||
|
||||
fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? {
|
||||
return this.annotations.findAnnotation(objCConstructorFqName)?.let {
|
||||
val initSelector = it.getAnnotationStringValue("initSelector")
|
||||
this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
|
||||
.filterIsInstance<FunctionDescriptor>()
|
||||
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
||||
}
|
||||
}
|
||||
|
||||
val IrFunction.hasObjCFactoryAnnotation get() = this.annotations.hasAnnotation(objCFactoryFqName)
|
||||
val FunctionDescriptor.hasObjCFactoryAnnotation get() = this.annotations.hasAnnotation(objCFactoryFqName)
|
||||
|
||||
val IrFunction.hasObjCMethodAnnotation get() = this.annotations.hasAnnotation(objCMethodFqName)
|
||||
val FunctionDescriptor.hasObjCMethodAnnotation get() = this.annotations.hasAnnotation(objCMethodFqName)
|
||||
|
||||
fun FunctionDescriptor.getObjCFactoryInitMethodInfo(): ObjCMethodInfo? {
|
||||
val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null
|
||||
|
||||
+13
-6
@@ -4,7 +4,8 @@ import org.jetbrains.kotlin.backend.common.*
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
|
||||
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
|
||||
import org.jetbrains.kotlin.backend.common.phaser.*
|
||||
import org.jetbrains.kotlin.backend.common.serialization.DescriptorTable
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.ManglerChecker
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.descriptor.Ir2DescriptorManglerAdapter
|
||||
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataMonolithicSerializer
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isForwardDeclarationModule
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary
|
||||
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.ir.util.SymbolTable
|
||||
import org.jetbrains.kotlin.ir.util.addChild
|
||||
import org.jetbrains.kotlin.ir.util.addFile
|
||||
import org.jetbrains.kotlin.ir.util.file
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration
|
||||
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
|
||||
@@ -107,7 +109,7 @@ private var Context.symbolTable: SymbolTable? by Context.nullValue()
|
||||
|
||||
internal val createSymbolTablePhase = konanUnitPhase(
|
||||
op = {
|
||||
this.symbolTable = SymbolTable()
|
||||
this.symbolTable = SymbolTable(KonanIdSignaturer(KonanManglerDesc))
|
||||
},
|
||||
name = "CreateSymbolTable",
|
||||
description = "Create SymbolTable"
|
||||
@@ -143,7 +145,7 @@ internal val psiToIrPhase = konanUnitPhase(
|
||||
val symbolTable = symbolTable!!
|
||||
|
||||
val translator = Psi2IrTranslator(config.configuration.languageVersionSettings,
|
||||
Psi2IrConfiguration(false))
|
||||
Psi2IrConfiguration(false), KonanIdSignaturer(KonanManglerDesc))
|
||||
val generatorContext = translator.createGeneratorContext(moduleDescriptor, bindingContext, symbolTable)
|
||||
|
||||
translator.addPostprocessingStep { module ->
|
||||
@@ -240,6 +242,12 @@ internal val psiToIrPhase = konanUnitPhase(
|
||||
if (this.stdlibModule in modulesWithoutDCE) {
|
||||
functionIrClassFactory.buildAllClasses()
|
||||
}
|
||||
modulesWithoutDCE
|
||||
.filter(ModuleDescriptor::isFromInteropLibrary)
|
||||
.forEach(irProviderForCEnumsAndCStructs::buildAllEnumsAndStructsFrom)
|
||||
|
||||
module.acceptVoid(ManglerChecker(KonanManglerIr, Ir2DescriptorManglerAdapter(KonanManglerDesc)))
|
||||
|
||||
irModule = module
|
||||
irModules = deserializer.modules.filterValues { llvmModuleSpecification.containsModule(it) }
|
||||
ir.symbols = symbols
|
||||
@@ -279,18 +287,17 @@ internal val copyDefaultValuesToActualPhase = konanUnitPhase(
|
||||
internal val serializerPhase = konanUnitPhase(
|
||||
op = {
|
||||
val mppKlibs = config.configuration.get(CommonConfigurationKeys.KLIB_MPP)?:false
|
||||
val descriptorTable = DescriptorTable.createDefault()
|
||||
|
||||
serializedIr = irModule?.let { ir ->
|
||||
KonanIrModuleSerializer(
|
||||
this, ir.irBuiltins, descriptorTable, expectDescriptorToSymbol, skipExpects = !mppKlibs
|
||||
this, ir.irBuiltins, expectDescriptorToSymbol, skipExpects = !mppKlibs
|
||||
).serializedIrModule(ir)
|
||||
}
|
||||
|
||||
val serializer = KlibMetadataMonolithicSerializer(
|
||||
this.config.configuration.languageVersionSettings,
|
||||
config.configuration.get(CommonConfigurationKeys.METADATA_VERSION)!!,
|
||||
descriptorTable, !mppKlibs)
|
||||
!mppKlibs)
|
||||
serializedMetadata = serializer.serializeModule(moduleDescriptor)
|
||||
},
|
||||
name = "Serializer",
|
||||
|
||||
+8
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.isObjCClass
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.longName
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
@@ -20,6 +21,9 @@ import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.isUnit
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
/**
|
||||
* List of all implemented interfaces (including those which implemented by a super class)
|
||||
@@ -267,6 +271,10 @@ fun IrConstructorCall.getAnnotationStringValue(name: String): String {
|
||||
return (getValueArgument(parameter.index) as IrConst<String>).value
|
||||
}
|
||||
|
||||
fun AnnotationDescriptor.getAnnotationStringValue(name: String): String {
|
||||
return argumentValue(name)?.safeAs<StringValue>()?.value ?: error("Expected value $name at annotation $this")
|
||||
}
|
||||
|
||||
fun <T> IrConstructorCall.getAnnotationValueOrNull(name: String): T? {
|
||||
val parameter = symbol.owner.valueParameters.atMostOne { it.name.asString() == name }
|
||||
return parameter?.let { getValueArgument(it.index)?.let { (it as IrConst<T>).value } }
|
||||
|
||||
+2
-1
@@ -71,7 +71,8 @@ internal class IrProviderForCEnumAndCStructStubs(
|
||||
}
|
||||
|
||||
fun canHandleSymbol(symbol: IrSymbol): Boolean {
|
||||
if (!symbol.descriptor.module.isFromInteropLibrary()) return false
|
||||
if (!symbol.isPublicApi) return false
|
||||
if (symbol.signature.run { !IdSignature.Flags.IS_NATIVE_INTEROP_LIBRARY.test() }) return false
|
||||
return symbol.findCEnumDescriptor(interopBuiltIns) != null
|
||||
|| symbol.findCStructDescriptor(interopBuiltIns) != null
|
||||
}
|
||||
|
||||
+2
-1
@@ -33,7 +33,8 @@ class IrProviderForInteropStubs(
|
||||
// So for now we relate on correct behavior of subsequent providers.
|
||||
symbol.isBound -> null
|
||||
isSpecialInteropCase(symbol) -> null
|
||||
symbol.descriptor.module.isFromInteropLibrary() -> provideIrDeclaration(symbol)
|
||||
symbol.isPublicApi && symbol.signature.run { IdSignature.Flags.IS_NATIVE_INTEROP_LIBRARY.test() } ->
|
||||
provideIrDeclaration(symbol)
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
+50
-104
@@ -6,21 +6,21 @@
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.LLVMTypeRef
|
||||
import org.jetbrains.kotlin.backend.common.serialization.KotlinManglerImpl
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.MangleConstant
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.SpecialDeclarationType
|
||||
import org.jetbrains.kotlin.backend.konan.RuntimeNames
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.externalSymbolOrThrow
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getAnnotationStringValue
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
|
||||
import org.jetbrains.kotlin.backend.konan.ir.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
|
||||
import org.jetbrains.kotlin.backend.konan.isObjCClassMethod
|
||||
import org.jetbrains.kotlin.backend.konan.ir.isUnit
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.isExported
|
||||
import org.jetbrains.kotlin.backend.konan.isExternalObjCClass
|
||||
import org.jetbrains.kotlin.backend.konan.serialization.KonanManglerIr
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.backend.konan.isInlinedNative
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.ir.util.findAnnotation
|
||||
import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization
|
||||
import org.jetbrains.kotlin.ir.util.isSuspend
|
||||
import org.jetbrains.kotlin.ir.util.render
|
||||
import org.jetbrains.kotlin.konan.library.KonanLibrary
|
||||
import org.jetbrains.kotlin.library.uniqueName
|
||||
|
||||
@@ -29,106 +29,52 @@ import org.jetbrains.kotlin.library.uniqueName
|
||||
// TODO: revise the naming scheme to ensure it produces unique names.
|
||||
// TODO: do not serialize descriptors of non-exported declarations.
|
||||
|
||||
abstract class AbstractKonanMangler : KotlinManglerImpl() {
|
||||
object KonanBinaryInterface {
|
||||
private val mangler = KonanManglerIr
|
||||
private val exportChecker = mangler.getExportChecker()
|
||||
|
||||
override val IrType.isInlined
|
||||
get() = this.isInlinedNative()
|
||||
val IrFunction.functionName: String get() = mangler.run { signatureString }
|
||||
|
||||
/**
|
||||
* Defines whether the declaration is exported, i.e. visible from other modules.
|
||||
*
|
||||
* Exported declarations must have predictable and stable ABI
|
||||
* that doesn't depend on any internal transformations (e.g. IR lowering),
|
||||
* and so should be computable from the descriptor itself without checking a backend state.
|
||||
*/
|
||||
override fun IrDeclaration.isPlatformSpecificExported(): Boolean {
|
||||
// TODO: revise
|
||||
if (annotations.hasAnnotation(RuntimeNames.symbolNameAnnotation)) {
|
||||
// Treat any `@SymbolName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCppRuntime)) {
|
||||
// Treat any `@ExportForCppRuntime` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.cnameAnnotation)) {
|
||||
// Treat `@CName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCompilerAnnotation)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
val IrFunction.symbolName: String get() = funSymbolNameImpl()
|
||||
val IrField.symbolName: String get() =
|
||||
withPrefix(MangleConstant.FIELD_PREFIX, fieldSymbolNameImpl())
|
||||
val IrClass.typeInfoSymbolName: String get() =
|
||||
withPrefix(MangleConstant.CLASS_PREFIX, typeInfoSymbolNameImpl())
|
||||
fun isExported(declaration: IrDeclaration) = exportChecker.run {
|
||||
check(declaration, SpecialDeclarationType.REGULAR) || declaration.isPlatformSpecificExported()
|
||||
}
|
||||
|
||||
override fun IrFunction.valueParamsPart(typeParameterNamer: (IrTypeParameter) -> String): String {
|
||||
return this.valueParameters.map {
|
||||
private fun withPrefix(prefix: String, mangle: String) = "$prefix:$mangle"
|
||||
|
||||
// TODO: there are clashes originating from ObjectiveC interop.
|
||||
// kotlinx.cinterop.ObjCClassOf<T>.create(format: kotlin.String): T defined in platform.Foundation in file Foundation.kt
|
||||
// and
|
||||
// kotlinx.cinterop.ObjCClassOf<T>.create(string: kotlin.String): T defined in platform.Foundation in file Foundation.kt
|
||||
private fun IrFunction.funSymbolNameImpl(): String {
|
||||
if (!isExported(this)) {
|
||||
throw AssertionError(render())
|
||||
}
|
||||
|
||||
val argName =
|
||||
if (this.hasObjCMethodAnnotation || this.hasObjCFactoryAnnotation || this.isObjCClassMethod()) "${it.name}:" else ""
|
||||
"$argName${typeToHashString(it.type, typeParameterNamer)}${if (it.isVararg) "_VarArg" else ""}"
|
||||
}.joinToString(";")
|
||||
if (isExternal) {
|
||||
this.externalSymbolOrThrow()?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
this.annotations.findAnnotation(RuntimeNames.exportForCppRuntime)?.let {
|
||||
val name = it.getAnnotationStringValue() ?: this.name.asString()
|
||||
return name // no wrapping currently required
|
||||
}
|
||||
|
||||
return withPrefix(MangleConstant.FUN_PREFIX, mangler.run { mangleString })
|
||||
}
|
||||
|
||||
override val IrFunction.platformSpecificFunctionName: String?
|
||||
get() {
|
||||
(if (this is IrConstructor && this.isObjCConstructor) this.getObjCInitMethod() else this)?.getObjCMethodInfo()
|
||||
?.let {
|
||||
return buildString {
|
||||
if (extensionReceiverParameter != null) {
|
||||
append(extensionReceiverParameter!!.type.getClass()!!.name)
|
||||
append(".")
|
||||
}
|
||||
|
||||
append("objc:")
|
||||
append(it.selector)
|
||||
if (this@platformSpecificFunctionName is IrConstructor && this@platformSpecificFunctionName.isObjCConstructor) append("#Constructor")
|
||||
|
||||
if ((this@platformSpecificFunctionName as? IrSimpleFunction)?.correspondingPropertySymbol != null) {
|
||||
append("#Accessor")
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
private fun IrField.fieldSymbolNameImpl(): String {
|
||||
val containingDeclarationPart = parent.fqNameForIrSerialization.let {
|
||||
if (it.isRoot) "" else "$it."
|
||||
}
|
||||
return "$containingDeclarationPart$name"
|
||||
}
|
||||
|
||||
internal val IrFunction.symbolName: String
|
||||
get() {
|
||||
if (!this.isExported()) {
|
||||
throw AssertionError(this.descriptor.toString())
|
||||
}
|
||||
|
||||
if (isExternal) {
|
||||
this.externalSymbolOrThrow()?.let {
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
this.annotations.findAnnotation(RuntimeNames.exportForCppRuntime)?.let {
|
||||
val name = it.getAnnotationStringValue() ?: this.name.asString()
|
||||
return name // no wrapping currently required
|
||||
}
|
||||
|
||||
val parent = this.parent
|
||||
|
||||
val containingDeclarationPart = parent.fqNameForIrSerialization.let {
|
||||
if (it.isRoot) "" else "$it."
|
||||
}
|
||||
return "kfun:$containingDeclarationPart$functionName"
|
||||
}
|
||||
}
|
||||
|
||||
object KonanMangler : AbstractKonanMangler()
|
||||
|
||||
object KonanManglerForBE : AbstractKonanMangler() {
|
||||
override fun mangleTypeParameter(typeParameter: IrTypeParameter, typeParameterNamer: (IrTypeParameter) -> String): String =
|
||||
typeParameter.name.asString()
|
||||
private fun IrClass.typeInfoSymbolNameImpl(): String {
|
||||
return this.fqNameForIrSerialization.toString()
|
||||
}
|
||||
}
|
||||
|
||||
internal val IrClass.writableTypeInfoSymbolName: String
|
||||
@@ -146,15 +92,15 @@ internal val IrClass.objectInstanceGetterSymbolName: String
|
||||
return "kobjget:$fqNameForIrSerialization"
|
||||
}
|
||||
|
||||
val IrFunction.functionName get() = with(KonanManglerForBE) { functionName }
|
||||
val IrFunction.functionName get() = with(KonanBinaryInterface) { functionName }
|
||||
|
||||
val IrFunction.symbolName get() = with(KonanManglerForBE) { symbolName }
|
||||
val IrFunction.symbolName get() = with(KonanBinaryInterface) { symbolName }
|
||||
|
||||
val IrField.symbolName get() = with(KonanManglerForBE) { symbolName }
|
||||
val IrField.symbolName get() = with(KonanBinaryInterface) { symbolName }
|
||||
|
||||
val IrClass.typeInfoSymbolName get() = with(KonanManglerForBE) { typeInfoSymbolName }
|
||||
val IrClass.typeInfoSymbolName get() = with(KonanBinaryInterface) { typeInfoSymbolName }
|
||||
|
||||
fun IrDeclaration.isExported() = with(KonanManglerForBE) { isExported() }
|
||||
fun IrDeclaration.isExported() = KonanBinaryInterface.isExported(this)
|
||||
|
||||
// TODO: bring here dependencies of this method?
|
||||
internal fun RuntimeAware.getLlvmFunctionType(function: IrFunction): LLVMTypeRef {
|
||||
|
||||
-1
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.target
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler.symbolName
|
||||
import org.jetbrains.kotlin.backend.konan.lower.DECLARATION_ORIGIN_BRIDGE_METHOD
|
||||
import org.jetbrains.kotlin.backend.konan.lower.bridgeTarget
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
|
||||
+2
-3
@@ -1,12 +1,11 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.GlobalDeclarationTable
|
||||
import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureSerializer
|
||||
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
|
||||
class KonanGlobalDeclarationTable(builtIns: IrBuiltIns) : GlobalDeclarationTable(KonanMangler) {
|
||||
class KonanGlobalDeclarationTable(signatureSerializer: IdSignatureSerializer, builtIns: IrBuiltIns) : GlobalDeclarationTable(signatureSerializer, KonanManglerIr) {
|
||||
init {
|
||||
loadKnownBuiltins(builtIns)
|
||||
}
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.*
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.util.KotlinMangler
|
||||
import org.jetbrains.kotlin.ir.util.UniqId
|
||||
|
||||
class KonanDescriptorReferenceDeserializer(
|
||||
currentModule: ModuleDescriptor,
|
||||
mangler: KotlinMangler,
|
||||
builtIns: IrBuiltIns,
|
||||
resolvedForwardDeclarations: MutableMap<UniqId, UniqId>
|
||||
): DescriptorReferenceDeserializer(currentModule, mangler, builtIns, resolvedForwardDeclarations),
|
||||
DescriptorUniqIdAware by DeserializedDescriptorUniqIdAware
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureDescriptor
|
||||
import org.jetbrains.kotlin.ir.util.KotlinMangler
|
||||
|
||||
class KonanIdSignaturer(mangler: KotlinMangler.DescriptorMangler) : IdSignatureDescriptor(mangler) {
|
||||
|
||||
}
|
||||
+4
-30
@@ -1,49 +1,23 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.LoggingContext
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.propertyIfAccessor
|
||||
import org.jetbrains.kotlin.backend.common.serialization.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary
|
||||
import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureSerializer
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSymbol
|
||||
import org.jetbrains.kotlin.ir.util.UniqId
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
|
||||
private class KonanDeclarationTable(
|
||||
descriptorTable: DescriptorTable,
|
||||
globalDeclarationTable: GlobalDeclarationTable,
|
||||
startIndex: Long
|
||||
) : DeclarationTable(descriptorTable, globalDeclarationTable, startIndex),
|
||||
DescriptorUniqIdAware by DeserializedDescriptorUniqIdAware {
|
||||
|
||||
/**
|
||||
* It is incorrect to compute UniqId for declarations from metadata-based libraries.
|
||||
* Instead we should get precomputed value from metadata.
|
||||
*/
|
||||
override fun tryComputeBackendSpecificUniqId(declaration: IrDeclaration): UniqId? {
|
||||
return if (declaration.descriptor.module.isFromInteropLibrary()) {
|
||||
// Property accessor doesn't provide UniqId so we need to get it from the property itself.
|
||||
UniqId(declaration.descriptor.propertyIfAccessor.getUniqId() ?: error("No uniq id found for ${declaration.descriptor}"))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class KonanIrModuleSerializer(
|
||||
logger: LoggingContext,
|
||||
irBuiltIns: IrBuiltIns,
|
||||
private val descriptorTable: DescriptorTable,
|
||||
private val expectDescriptorToSymbol: MutableMap<DeclarationDescriptor, IrSymbol>,
|
||||
val skipExpects: Boolean
|
||||
) : IrModuleSerializer<KonanIrFileSerializer>(logger) {
|
||||
|
||||
|
||||
private val globalDeclarationTable = KonanGlobalDeclarationTable(irBuiltIns)
|
||||
private val signaturer = IdSignatureSerializer(KonanManglerIr)
|
||||
private val globalDeclarationTable = KonanGlobalDeclarationTable(signaturer, irBuiltIns)
|
||||
|
||||
override fun createSerializerForFile(file: IrFile): KonanIrFileSerializer =
|
||||
KonanIrFileSerializer(logger, KonanDeclarationTable(descriptorTable, globalDeclarationTable, 0), expectDescriptorToSymbol, skipExpects = skipExpects)
|
||||
KonanIrFileSerializer(logger, DeclarationTable(globalDeclarationTable), expectDescriptorToSymbol, skipExpects = skipExpects)
|
||||
}
|
||||
|
||||
+73
-22
@@ -20,38 +20,43 @@ import org.jetbrains.kotlin.backend.common.LoggingContext
|
||||
import org.jetbrains.kotlin.backend.common.serialization.*
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isFromInteropLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.konanLibrary
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.KonanMangler
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrFileSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||
import org.jetbrains.kotlin.ir.util.NaiveSourceBasedFileEntryImpl
|
||||
import org.jetbrains.kotlin.ir.util.SymbolTable
|
||||
import org.jetbrains.kotlin.ir.util.UniqId
|
||||
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
|
||||
class KonanIrLinker(
|
||||
currentModule: ModuleDescriptor,
|
||||
private val currentModule: ModuleDescriptor,
|
||||
logger: LoggingContext,
|
||||
builtIns: IrBuiltIns,
|
||||
symbolTable: SymbolTable,
|
||||
forwardModuleDescriptor: ModuleDescriptor?,
|
||||
private val forwardModuleDescriptor: ModuleDescriptor?,
|
||||
exportedDependencies: List<ModuleDescriptor>
|
||||
) : KotlinIrLinker(logger, builtIns, symbolTable, exportedDependencies, forwardModuleDescriptor, KonanMangler),
|
||||
DescriptorUniqIdAware by DeserializedDescriptorUniqIdAware {
|
||||
) : KotlinIrLinker(logger, builtIns, symbolTable, exportedDependencies, forwardModuleDescriptor) {
|
||||
|
||||
override val descriptorReferenceDeserializer =
|
||||
KonanDescriptorReferenceDeserializer(currentModule, KonanMangler, builtIns, resolvedForwardDeclarations)
|
||||
|
||||
override fun reader(moduleDescriptor: ModuleDescriptor, fileIndex: Int, uniqId: UniqId) =
|
||||
moduleDescriptor.konanLibrary!!.irDeclaration(uniqId.index, fileIndex)
|
||||
|
||||
override fun readSymbol(moduleDescriptor: ModuleDescriptor, fileIndex: Int, symbolIndex: Int) =
|
||||
moduleDescriptor.konanLibrary!!.symbol(symbolIndex, fileIndex)
|
||||
override fun reader(moduleDescriptor: ModuleDescriptor, fileIndex: Int, idSigIndex: Int) =
|
||||
moduleDescriptor.konanLibrary!!.irDeclaration(idSigIndex, fileIndex)
|
||||
|
||||
override fun readType(moduleDescriptor: ModuleDescriptor, fileIndex: Int, typeIndex: Int) =
|
||||
moduleDescriptor.konanLibrary!!.type(typeIndex, fileIndex)
|
||||
|
||||
override fun readSignature(moduleDescriptor: ModuleDescriptor, fileIndex: Int, signatureIndex: Int) =
|
||||
moduleDescriptor.konanLibrary!!.signature(signatureIndex, fileIndex)
|
||||
|
||||
override fun readString(moduleDescriptor: ModuleDescriptor, fileIndex: Int, stringIndex: Int) =
|
||||
moduleDescriptor.konanLibrary!!.string(stringIndex, fileIndex)
|
||||
|
||||
@@ -62,19 +67,65 @@ class KonanIrLinker(
|
||||
moduleDescriptor.konanLibrary!!.file(fileIndex)
|
||||
|
||||
override fun readFileCount(moduleDescriptor: ModuleDescriptor) =
|
||||
moduleDescriptor.run { if (isForwardDeclarationModule || !shouldBeDeserialized()) 0 else konanLibrary!!.fileCount() }
|
||||
moduleDescriptor.run { if (this === forwardModuleDescriptor || moduleDescriptor.isFromInteropLibrary()) 0 else konanLibrary!!.fileCount() }
|
||||
|
||||
private val ModuleDescriptor.userName get() = konanLibrary!!.libraryFile.absolutePath
|
||||
|
||||
override fun handleNoModuleDeserializerFound(key: UniqId): DeserializationState<*> {
|
||||
override fun handleNoModuleDeserializerFound(idSignature: IdSignature): DeserializationState<*> {
|
||||
return globalDeserializationState
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val C_NAMES_NAME = Name.identifier("cnames")
|
||||
private val OBJC_NAMES_NAME = Name.identifier("objcnames")
|
||||
}
|
||||
|
||||
override fun isSpecialPlatformSignature(idSig: IdSignature): Boolean = idSig.isForwardDeclarationSignature()
|
||||
|
||||
override fun postProcessPlatformSpecificDeclaration(idSig: IdSignature, descriptor: DeclarationDescriptor?, block: (IdSignature) -> Unit) {
|
||||
if (descriptor == null) return
|
||||
|
||||
if (!idSig.isForwardDeclarationSignature()) return
|
||||
|
||||
val fqn = descriptor.fqNameSafe
|
||||
if (!fqn.startsWith(C_NAMES_NAME) && !fqn.startsWith(OBJC_NAMES_NAME)) {
|
||||
val signature = IdSignature.PublicSignature(fqn.parent(), FqName(fqn.shortName().asString()), null, 0)
|
||||
block(signature)
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolvePlatformDescriptor(idSig: IdSignature): DeclarationDescriptor? {
|
||||
if (!idSig.isForwardDeclarationSignature()) return null
|
||||
|
||||
val fwdModule = forwardModuleDescriptor ?: error("Forward declaration module should not be null")
|
||||
|
||||
return with(idSig as IdSignature.PublicSignature) {
|
||||
val classId = ClassId(packageFqn, classFqn, false)
|
||||
fwdModule.findClassAcrossModuleDependencies(classId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IdSignature.isInteropSignature(): Boolean = IdSignature.Flags.IS_NATIVE_INTEROP_LIBRARY.test()
|
||||
|
||||
private fun IdSignature.isForwardDeclarationSignature(): Boolean {
|
||||
if (isPublic) {
|
||||
return packageFqName().run {
|
||||
startsWith(C_NAMES_NAME) || startsWith(OBJC_NAMES_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun createModuleDeserializer(moduleDescriptor: ModuleDescriptor, strategy: DeserializationStrategy): IrModuleDeserializer {
|
||||
return KonanModuleDeserializer(moduleDescriptor, strategy)
|
||||
}
|
||||
|
||||
private inner class KonanModuleDeserializer(moduleDescriptor: ModuleDescriptor, strategy: DeserializationStrategy):
|
||||
IrModuleDeserializer(moduleDescriptor, strategy)
|
||||
|
||||
/**
|
||||
* If declaration is from interop library then IR for it is generated by IrProviderForInteropStubs.
|
||||
*/
|
||||
override fun DeclarationDescriptor.shouldBeDeserialized(): Boolean =
|
||||
!module.isFromInteropLibrary()
|
||||
override fun IdSignature.shouldBeDeserialized(): Boolean = !isInteropSignature() && !isForwardDeclarationSignature()
|
||||
|
||||
val modules: Map<String, IrModuleFragment> get() = mutableMapOf<String, IrModuleFragment>().apply {
|
||||
deserializersForModules.filter { !it.key.isForwardDeclarationModule }.forEach {
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package org.jetbrains.kotlin.backend.konan.serialization
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.MangleMode
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.descriptor.DescriptorBasedKotlinManglerImpl
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.descriptor.DescriptorExportCheckerVisitor
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.descriptor.DescriptorMangleComputer
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.ir.IrBasedKotlinManglerImpl
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.ir.IrExportCheckerVisitor
|
||||
import org.jetbrains.kotlin.backend.common.serialization.mangle.ir.IrMangleComputer
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.types.getClass
|
||||
import org.jetbrains.kotlin.ir.util.hasAnnotation
|
||||
|
||||
abstract class AbstractKonanIrMangler : IrBasedKotlinManglerImpl() {
|
||||
override fun getExportChecker(): IrExportCheckerVisitor = KonanIrExportChecker()
|
||||
|
||||
override fun getMangleComputer(mode: MangleMode): IrMangleComputer = KonanIrManglerComputer(StringBuilder(256), mode)
|
||||
|
||||
private class KonanIrExportChecker : IrExportCheckerVisitor() {
|
||||
override fun IrDeclaration.isPlatformSpecificExported(): Boolean {
|
||||
if (this is IrSimpleFunction) if (isFakeOverride) return false
|
||||
|
||||
// TODO: revise
|
||||
if (annotations.hasAnnotation(RuntimeNames.symbolNameAnnotation)) {
|
||||
// Treat any `@SymbolName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCppRuntime)) {
|
||||
// Treat any `@ExportForCppRuntime` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.cnameAnnotation)) {
|
||||
// Treat `@CName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCompilerAnnotation)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class KonanIrManglerComputer(builder: StringBuilder, mode: MangleMode) : IrMangleComputer(builder, mode) {
|
||||
override fun copy(newMode: MangleMode): IrMangleComputer = KonanIrManglerComputer(builder, newMode)
|
||||
|
||||
override fun IrFunction.platformSpecificFunctionName(): String? {
|
||||
(if (this is IrConstructor && this.isObjCConstructor) this.getObjCInitMethod() else this)?.getObjCMethodInfo()
|
||||
?.let {
|
||||
return buildString {
|
||||
if (extensionReceiverParameter != null) {
|
||||
append(extensionReceiverParameter!!.type.getClass()!!.name)
|
||||
append(".")
|
||||
}
|
||||
|
||||
append("objc:")
|
||||
append(it.selector)
|
||||
if (this@platformSpecificFunctionName is IrConstructor && this@platformSpecificFunctionName.isObjCConstructor) append("#Constructor")
|
||||
|
||||
if ((this@platformSpecificFunctionName as? IrSimpleFunction)?.correspondingPropertySymbol != null) {
|
||||
append("#Accessor")
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun IrFunction.specialValueParamPrefix(param: IrValueParameter): String {
|
||||
// TODO: there are clashes originating from ObjectiveC interop.
|
||||
// kotlinx.cinterop.ObjCClassOf<T>.create(format: kotlin.String): T defined in platform.Foundation in file Foundation.kt
|
||||
// and
|
||||
// kotlinx.cinterop.ObjCClassOf<T>.create(string: kotlin.String): T defined in platform.Foundation in file Foundation.kt
|
||||
|
||||
return if (this.hasObjCMethodAnnotation || this.hasObjCFactoryAnnotation || this.isObjCClassMethod()) "${param.name}:" else ""
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object KonanManglerIr : AbstractKonanIrMangler()
|
||||
|
||||
abstract class AbstractKonanDescriptorMangler : DescriptorBasedKotlinManglerImpl() {
|
||||
override fun getExportChecker(): DescriptorExportCheckerVisitor = KonanDescriptorExportChecker()
|
||||
|
||||
override fun getMangleComputer(mode: MangleMode): DescriptorMangleComputer =
|
||||
KonanDescriptorMangleComputer(StringBuilder(256), mode)
|
||||
|
||||
private class KonanDescriptorExportChecker : DescriptorExportCheckerVisitor() {
|
||||
override fun DeclarationDescriptor.isPlatformSpecificExported(): Boolean {
|
||||
if (this is SimpleFunctionDescriptor) {
|
||||
if (kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) return false
|
||||
}
|
||||
// TODO: revise
|
||||
if (annotations.hasAnnotation(RuntimeNames.symbolNameAnnotation)) {
|
||||
// Treat any `@SymbolName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCppRuntime)) {
|
||||
// Treat any `@ExportForCppRuntime` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.cnameAnnotation)) {
|
||||
// Treat `@CName` declaration as exported.
|
||||
return true
|
||||
}
|
||||
if (annotations.hasAnnotation(RuntimeNames.exportForCompilerAnnotation)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private class KonanDescriptorMangleComputer(builder: StringBuilder, mode: MangleMode) : DescriptorMangleComputer(builder, mode) {
|
||||
override fun copy(newMode: MangleMode): DescriptorMangleComputer = KonanDescriptorMangleComputer(builder, newMode)
|
||||
|
||||
override fun FunctionDescriptor.platformSpecificFunctionName(): String? {
|
||||
(if (this is ConstructorDescriptor && this.isObjCConstructor) this.getObjCInitMethod() else this)?.getObjCMethodInfo()
|
||||
?.let {
|
||||
return buildString {
|
||||
if (extensionReceiverParameter != null) {
|
||||
append(extensionReceiverParameter!!.type.constructor.declarationDescriptor!!.name)
|
||||
append(".")
|
||||
}
|
||||
|
||||
append("objc:")
|
||||
append(it.selector)
|
||||
if (this@platformSpecificFunctionName is ConstructorDescriptor && this@platformSpecificFunctionName.isObjCConstructor) append("#Constructor")
|
||||
|
||||
if (this@platformSpecificFunctionName is PropertyAccessorDescriptor) {
|
||||
append("#Accessor")
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun FunctionDescriptor.specialValueParamPrefix(param: ValueParameterDescriptor): String {
|
||||
return if (this.hasObjCMethodAnnotation || this.hasObjCFactoryAnnotation || this.isObjCClassMethod()) "${param.name}:" else ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object KonanManglerDesc : AbstractKonanDescriptorMangler()
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
package org.jetbrains.build
|
||||
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
data class Build(val buildNumber: String, val startTime: String, val finishTime: String, val branch: String,
|
||||
val commits: String, val buildType: String, val failuresNumber: Int, val executionTime: String,
|
||||
val compileTime: String, val codeSize: String, val bundleSize: String?) {
|
||||
|
||||
companion object: EntityFromJsonFactory<Build> {
|
||||
override fun create(data: JsonElement): Build {
|
||||
if (data is JsonObject) {
|
||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber")
|
||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime")
|
||||
val finishTime = elementToString(data.getRequiredField("finishTime"), "finishTime")
|
||||
val branch = elementToString(data.getRequiredField("branch"), "branch")
|
||||
val commits = elementToString(data.getRequiredField("commits"), "commits")
|
||||
val buildType = elementToString(data.getRequiredField("buildType"), "buildType")
|
||||
val failuresNumber = elementToInt(data.getRequiredField("failuresNumber"), "failuresNumber")
|
||||
val executionTime = elementToString(data.getRequiredField("executionTime"), "executionTime")
|
||||
val compileTime = elementToString(data.getRequiredField("compileTime"), "compileTime")
|
||||
val codeSize = elementToString(data.getRequiredField("codeSize"), "codeSize")
|
||||
val bundleSize = elementToStringOrNull(data.getRequiredField("bundleSize"), "bundleSize")
|
||||
return Build(buildNumber, startTime, finishTime, branch, commits, buildType, failuresNumber, executionTime,
|
||||
compileTime, codeSize, bundleSize)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatTime(time: String, targetZone: Int = 3): String {
|
||||
val matchResult = "^\\d{8}T(\\d{2})(\\d{2})\\d{2}((\\+|-)\\d{2})".toRegex().find(time)?.groupValues
|
||||
matchResult?.let {
|
||||
val timeZone = matchResult[3].toInt()
|
||||
val timeDifference = targetZone - timeZone
|
||||
var hours = (matchResult[1].toInt() + timeDifference)
|
||||
if (hours > 23) {
|
||||
hours -= 24
|
||||
}
|
||||
return "${if (hours < 10) "0$hours" else "$hours"}:${matchResult[2]}"
|
||||
} ?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
|
||||
val date: String by lazy {
|
||||
val matchResult = "^(\\d{4})(\\d{2})(\\d{2})".toRegex().find(startTime)?.groupValues
|
||||
matchResult?.let { "${matchResult[3]}/${matchResult[2]}/${matchResult[1]}" }
|
||||
?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
|
||||
val formattedStartTime: String by lazy {
|
||||
formatTime(startTime)
|
||||
}
|
||||
|
||||
val formattedFinishTime: String by lazy {
|
||||
formatTime(finishTime)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user